From e280f12834190c543be1ad4fddf6a642f65b998a Mon Sep 17 00:00:00 2001 From: Jakub Pawlowski Date: Wed, 5 Apr 2017 09:22:29 -0700 Subject: [PATCH 1/2] 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 --- Android.bp | 282 +- base/DEPS | 16 - base/PRESUBMIT.py | 49 - base/allocator/README.md | 196 - base/allocator/allocator_extension.cc | 6 +- base/allocator/allocator_shim.cc | 18 +- base/allocator/allocator_shim.h | 2 +- ...ault_dispatch_to_linker_wrapped_symbols.cc | 3 + .../allocator_shim_override_cpp_symbols.h | 8 + base/allocator/buildflags.h | 5 + base/allocator/features.h | 15 - base/android/base_jni_onload.h | 23 + base/android/build_info.cc | 79 +- base/android/build_info.h | 72 +- base/android/context_utils.cc | 53 - base/android/context_utils.h | 26 - .../src/org/chromium/base/BuildConfig.java | 21 + .../java/src/org/chromium/base/BuildInfo.java | 244 +- .../src/org/chromium/base/ContextUtils.java | 85 +- .../base/DiscardableReferencePool.java | 90 + .../chromium/base/JavaExceptionReporter.java | 65 + .../src/org/chromium/base/PackageUtils.java | 19 + .../org/chromium/base/StrictModeContext.java | 85 + .../java/src/org/chromium/base/Supplier.java | 18 + .../src/org/chromium/base/ThreadUtils.java | 268 ++ .../src/org/chromium/base/TimezoneUtils.java | 36 + .../base/annotations/CalledByNative.java | 2 +- .../chromium/base/annotations/MainDex.java | 5 +- .../base/annotations/SuppressFBWarnings.java | 20 - base/android/java_exception_reporter.cc | 70 + base/android/java_exception_reporter.h | 33 + .../src/org/chromium/base/AssertsTest.java | 39 + .../src/org/chromium/base/AsyncTaskTest.java | 128 + base/android/jni_android.cc | 77 +- base/android/jni_android.h | 27 +- base/android/jni_array.cc | 311 ++ base/android/jni_array.h | 134 + .../android/jni_generator/AndroidManifest.xml | 13 + base/android/jni_generator/PRESUBMIT.py | 37 + .../example/jni_generator/SampleForTests.java | 90 +- .../jni_generator/jni_exception_list.gni | 13 + base/android/jni_generator/jni_generator.py | 869 +++-- .../jni_generator/jni_generator_helper.h | 24 +- .../jni_generator/jni_generator_tests.py | 223 +- .../jni_registration_generator.py | 340 ++ .../jni_generator/sample_entry_point.cc | 27 + .../android/jni_generator/sample_for_tests.cc | 40 +- base/android/jni_generator/sample_for_tests.h | 42 +- .../jni_generator/testCalledByNatives.golden | 510 ++- .../testConstantsFromJavaP.golden | 2010 +++++----- .../jni_generator/testFromJavaP.golden | 262 +- .../testFromJavaPGenerics.golden | 67 +- .../testInnerClassNatives.golden | 75 +- ...tInnerClassNativesBothInnerAndOuter.golden | 100 +- ...tivesBothInnerAndOuterRegistrations.golden | 109 + .../testInnerClassNativesMultiple.golden | 120 +- .../testMultipleJNIAdditionalImport.golden | 93 +- base/android/jni_generator/testNatives.golden | 308 +- .../jni_generator/testNativesLong.golden | 57 +- .../testNativesRegistrations.golden | 175 + .../testSingleJNIAdditionalImport.golden | 89 +- base/android/jni_generator/testTracing.golden | 99 + .../base/DiscardableReferencePoolTest.java | 80 + base/android/library_loader/README.md | 10 + .../library_loader/anchor_functions.cc | 79 + .../android/library_loader/anchor_functions.h | 32 + .../library_loader/anchor_functions.lds | 7 + base/android/orderfile/BUILD.gn | 34 + .../orderfile/orderfile_instrumentation.cc | 328 ++ .../orderfile/orderfile_instrumentation.h | 56 + .../orderfile_instrumentation_perftest.cc | 135 + .../proguard/disable_all_obfuscation.flags | 8 + .../disable_chromium_obfuscation.flags | 8 + .../android/proguard/enable_obfuscation.flags | 8 + base/android/scoped_hardware_buffer_handle.cc | 114 + base/android/scoped_hardware_buffer_handle.h | 87 + base/android/scoped_java_ref.h | 28 +- base/android/timezone_utils.cc | 23 + base/android/timezone_utils.h | 22 + base/at_exit.cc | 4 +- base/at_exit.h | 5 +- base/at_exit_unittest.cc | 16 +- base/atomic_ref_count.h | 87 +- base/atomic_sequence_num.h | 49 +- base/atomicops.h | 4 +- base/atomicops_internals_x86_msvc.h | 26 +- base/auto_reset.h | 8 +- base/barrier_closure.cc | 51 + base/barrier_closure.h | 28 + base/base64_decode_fuzzer.cc | 15 + base/base64_encode_fuzzer.cc | 20 + base/base_paths.cc | 12 +- base/base_paths.h | 5 +- base/base_paths_posix.cc | 6 +- base/base_switches.cc | 45 +- base/base_switches.h | 16 +- base/big_endian.cc | 105 + base/big_endian.h | 106 + base/big_endian_unittest.cc | 116 + base/bind.h | 405 +- base/bind_helpers.cc | 14 - base/bind_helpers.h | 572 +-- base/bind_internal.h | 710 +++- base/bind_unittest.cc | 125 +- base/bits.h | 168 +- base/bits_unittest.cc | 142 +- base/callback.h | 143 +- base/callback_forward.h | 35 +- base/callback_helpers.cc | 24 +- base/callback_helpers.h | 68 +- base/callback_helpers_unittest.cc | 19 +- base/callback_internal.cc | 49 +- base/callback_internal.h | 54 +- base/callback_list.h | 39 +- base/callback_list_unittest.cc | 2 +- base/callback_unittest.cc | 4 +- base/cancelable_callback.h | 96 +- base/cancelable_callback_unittest.cc | 4 +- base/cfi_buildflags.h | 7 + base/command_line.cc | 67 +- base/command_line.h | 20 +- base/command_line_unittest.cc | 8 +- base/compiler_specific.h | 58 +- base/component_export.h | 87 + base/component_export_unittest.cc | 82 + base/containers/README.md | 295 ++ base/containers/circular_deque.h | 1111 ++++++ base/containers/circular_deque_unittest.cc | 881 +++++ base/containers/flat_map.h | 362 ++ base/containers/flat_set.h | 140 + base/containers/flat_tree.h | 1004 +++++ base/containers/linked_list.h | 22 +- base/containers/mru_cache.h | 22 +- base/containers/queue.h | 23 + base/containers/ring_buffer.h | 133 + base/containers/small_map.h | 278 +- base/containers/span.h | 453 +++ base/containers/span_unittest.cc | 1170 ++++++ base/containers/span_unittest.nc | 167 + base/containers/stack.h | 23 + base/containers/stack_container.h | 55 +- base/containers/vector_buffer.h | 163 + base/containers/vector_buffer_unittest.cc | 89 + base/cpu.cc | 69 +- base/cpu.h | 3 +- base/cpu_unittest.cc | 9 +- base/debug/activity_tracker.cc | 322 +- base/debug/activity_tracker.h | 123 +- base/debug/activity_tracker_unittest.cc | 159 +- base/debug/alias.cc | 4 + base/debug/alias.h | 24 +- base/debug/alias_unittest.cc | 28 + base/debug/crash_logging.cc | 54 + base/debug/crash_logging.h | 104 + base/debug/debugger_posix.cc | 9 +- ...bugging_flags.h => debugging_buildflags.h} | 4 + base/debug/dump_without_crashing.cc | 8 +- base/debug/dump_without_crashing.h | 10 +- base/debug/elf_reader_linux.cc | 132 + base/debug/elf_reader_linux.h | 28 + base/debug/elf_reader_linux_unittest.cc | 70 + base/debug/leak_tracker.h | 2 + base/debug/proc_maps_linux.h | 3 + base/debug/profiler.cc | 49 +- base/debug/profiler.h | 3 - base/debug/stack_trace.cc | 35 +- base/debug/stack_trace.h | 35 +- base/debug/stack_trace_posix.cc | 166 +- base/debug/task_annotator.cc | 101 +- base/debug/task_annotator.h | 32 +- base/debug/task_annotator_unittest.cc | 343 +- base/debug/thread_heap_usage_tracker.h | 4 +- base/environment.cc | 52 +- base/environment.h | 4 +- base/environment_unittest.cc | 34 +- base/export_template.h | 163 + base/feature_list.cc | 133 +- base/feature_list.h | 30 +- base/feature_list_unittest.cc | 5 + base/files/dir_reader_linux.h | 2 +- base/files/dir_reader_posix.h | 4 +- base/files/dir_reader_posix_unittest.cc | 8 +- base/files/file.cc | 32 +- base/files/file.h | 78 +- base/files/file_descriptor_watcher_posix.cc | 57 +- base/files/file_descriptor_watcher_posix.h | 17 +- .../file_descriptor_watcher_posix_unittest.cc | 12 +- base/files/file_enumerator.cc | 8 +- base/files/file_enumerator.h | 62 +- base/files/file_enumerator_posix.cc | 160 +- base/files/file_enumerator_unittest.cc | 312 ++ base/files/file_path.cc | 109 +- base/files/file_path.h | 49 +- base/files/file_path_unittest.cc | 14 +- base/files/file_path_watcher.cc | 2 +- base/files/file_path_watcher_linux.cc | 121 +- base/files/file_path_watcher_unittest.cc | 73 +- base/files/file_posix.cc | 77 +- base/files/file_unittest.cc | 87 + base/files/file_util.cc | 61 +- base/files/file_util.h | 75 +- base/files/file_util_posix.cc | 567 +-- base/files/important_file_writer.cc | 165 +- base/files/important_file_writer.h | 35 +- base/files/important_file_writer_unittest.cc | 132 +- base/files/memory_mapped_file.cc | 34 +- base/files/memory_mapped_file.h | 6 +- base/files/memory_mapped_file_posix.cc | 114 +- base/files/platform_file.h | 43 + base/files/scoped_file.cc | 21 +- base/files/scoped_file.h | 4 +- base/files/scoped_temp_dir.cc | 21 +- base/files/scoped_temp_dir.h | 16 +- base/format_macros.h | 40 +- base/gmock_unittest.cc | 12 +- base/gtest_prod_util.h | 2 +- base/guid.cc | 15 +- base/guid.h | 8 +- base/hash.cc | 94 +- base/hash.h | 95 +- base/i18n/rtl.h | 16 + base/json/json_correctness_fuzzer.cc | 63 + base/json/json_file_value_serializer.cc | 8 +- base/json/json_parser.cc | 541 ++- base/json/json_parser.h | 121 +- base/json/json_parser_unittest.cc | 166 +- base/json/json_reader.cc | 55 +- base/json/json_reader.h | 28 +- base/json/json_reader_fuzzer.cc | 29 + base/json/json_reader_unittest.cc | 1001 +++-- base/json/json_string_value_serializer.cc | 4 +- base/json/json_value_converter.h | 75 +- base/json/json_value_converter_unittest.cc | 6 +- base/json/json_value_serializer_unittest.cc | 4 +- base/json/json_writer.cc | 15 +- base/json/json_writer_unittest.cc | 30 +- base/json/string_escape.cc | 16 +- base/json/string_escape.h | 21 +- base/json/string_escape_fuzzer.cc | 37 + base/json/string_escape_unittest.cc | 16 +- base/lazy_instance.h | 103 +- ...y_instance.cc => lazy_instance_helpers.cc} | 40 +- base/lazy_instance_helpers.h | 101 + base/lazy_instance_unittest.cc | 165 +- base/location.cc | 113 +- base/location.h | 135 +- base/logging.cc | 288 +- base/logging.h | 243 +- base/logging_unittest.cc | 357 +- base/macros.h | 60 +- base/memory/aligned_memory.cc | 4 +- base/memory/aligned_memory.h | 91 +- base/memory/aligned_memory_unittest.cc | 73 +- base/memory/linked_ptr_unittest.cc | 18 +- base/memory/manual_constructor.h | 75 - base/memory/platform_shared_memory_region.cc | 37 + base/memory/platform_shared_memory_region.h | 229 ++ .../platform_shared_memory_region_mac.cc | 233 ++ .../platform_shared_memory_region_posix.cc | 328 ++ .../platform_shared_memory_region_unittest.cc | 347 ++ base/memory/protected_memory.cc | 17 + base/memory/protected_memory.h | 276 ++ base/memory/protected_memory_buildflags.h | 7 + base/memory/protected_memory_cfi.h | 86 + base/memory/protected_memory_posix.cc | 79 + base/memory/protected_memory_unittest.cc | 126 + base/memory/ptr_util.h | 51 - .../raw_scoped_refptr_mismatch_checker.h | 36 +- base/memory/read_only_shared_memory_region.cc | 97 + base/memory/read_only_shared_memory_region.h | 122 + base/memory/ref_counted.cc | 47 +- base/memory/ref_counted.h | 420 +-- base/memory/ref_counted_delete_on_sequence.h | 82 + base/memory/ref_counted_memory.cc | 75 +- base/memory/ref_counted_memory.h | 93 +- base/memory/ref_counted_memory_unittest.cc | 90 +- base/memory/ref_counted_unittest.cc | 203 +- base/memory/scoped_refptr.h | 337 ++ base/memory/scoped_vector.h | 153 - base/memory/scoped_vector_unittest.cc | 338 -- base/memory/shared_memory.h | 166 +- base/memory/shared_memory_android.cc | 36 +- base/memory/shared_memory_handle.cc | 23 + base/memory/shared_memory_handle.h | 239 +- base/memory/shared_memory_handle_android.cc | 115 + base/memory/shared_memory_handle_posix.cc | 71 + base/memory/shared_memory_helper.cc | 81 +- base/memory/shared_memory_helper.h | 13 +- base/memory/shared_memory_mapping.cc | 117 + base/memory/shared_memory_mapping.h | 144 + base/memory/shared_memory_posix.cc | 277 +- base/memory/shared_memory_region_unittest.cc | 280 ++ base/memory/shared_memory_unittest.cc | 366 +- base/memory/singleton.cc | 34 - base/memory/singleton.h | 93 +- base/memory/singleton_unittest.cc | 34 +- base/memory/unsafe_shared_memory_region.cc | 76 + base/memory/unsafe_shared_memory_region.h | 118 + base/memory/weak_ptr.cc | 39 +- base/memory/weak_ptr.h | 92 +- base/memory/weak_ptr_unittest.cc | 66 +- base/memory/weak_ptr_unittest.nc | 23 +- base/memory/writable_shared_memory_region.cc | 93 + base/memory/writable_shared_memory_region.h | 109 + base/message_loop/incoming_task_queue.cc | 340 +- base/message_loop/incoming_task_queue.h | 252 +- base/message_loop/message_loop.cc | 811 ++-- base/message_loop/message_loop.h | 445 +-- base/message_loop/message_loop_current.cc | 248 ++ base/message_loop/message_loop_current.h | 297 ++ .../message_loop_io_posix_unittest.cc | 418 +++ base/message_loop/message_loop_perftest.cc | 254 ++ base/message_loop/message_loop_task_runner.cc | 18 +- base/message_loop/message_loop_task_runner.h | 10 +- .../message_loop_task_runner_perftest.cc | 191 + .../message_loop_task_runner_unittest.cc | 84 +- base/message_loop/message_loop_test.cc | 1041 ------ base/message_loop/message_loop_test.h | 130 - base/message_loop/message_loop_unittest.cc | 2282 +++++++++--- base/message_loop/message_pump.cc | 6 +- base/message_loop/message_pump.h | 6 +- base/message_loop/message_pump_default.cc | 27 +- base/message_loop/message_pump_default.h | 4 + base/message_loop/message_pump_for_io.h | 44 + base/message_loop/message_pump_for_ui.h | 57 + base/message_loop/message_pump_glib.cc | 10 +- .../message_pump_glib_unittest.cc | 234 +- base/message_loop/message_pump_libevent.cc | 70 +- base/message_loop/message_pump_libevent.h | 84 +- .../watchable_io_message_pump_posix.cc | 16 + .../watchable_io_message_pump_posix.h | 88 + base/metrics/bucket_ranges.cc | 39 +- base/metrics/bucket_ranges.h | 24 +- base/metrics/dummy_histogram.cc | 102 + base/metrics/dummy_histogram.h | 61 + base/metrics/field_trial.cc | 429 ++- base/metrics/field_trial.h | 118 +- base/metrics/field_trial_param_associator.cc | 12 +- base/metrics/field_trial_param_associator.h | 5 + base/metrics/field_trial_params_unittest.nc | 47 + base/metrics/field_trial_unittest.cc | 394 +- base/metrics/histogram.cc | 533 ++- base/metrics/histogram.h | 195 +- base/metrics/histogram_base.cc | 149 +- base/metrics/histogram_base.h | 84 +- base/metrics/histogram_base_unittest.cc | 197 +- base/metrics/histogram_delta_serialization.cc | 54 +- base/metrics/histogram_delta_serialization.h | 9 - base/metrics/histogram_flattener.h | 20 +- base/metrics/histogram_functions.cc | 110 + base/metrics/histogram_functions.h | 158 + base/metrics/histogram_macros.h | 131 +- base/metrics/histogram_macros_internal.h | 124 +- base/metrics/histogram_macros_local.h | 18 +- base/metrics/histogram_macros_unittest.cc | 12 +- base/metrics/histogram_samples.cc | 248 +- base/metrics/histogram_samples.h | 157 +- base/metrics/histogram_samples_unittest.cc | 84 + base/metrics/histogram_snapshot_manager.cc | 67 +- base/metrics/histogram_snapshot_manager.h | 32 +- .../histogram_snapshot_manager_unittest.cc | 35 +- base/metrics/histogram_unittest.cc | 170 +- .../metrics/persistent_histogram_allocator.cc | 461 ++- base/metrics/persistent_histogram_allocator.h | 97 +- ...persistent_histogram_allocator_unittest.cc | 115 +- base/metrics/persistent_histogram_storage.cc | 103 + base/metrics/persistent_histogram_storage.h | 68 + .../persistent_histogram_storage_unittest.cc | 78 + base/metrics/persistent_memory_allocator.cc | 182 +- base/metrics/persistent_memory_allocator.h | 108 +- .../persistent_memory_allocator_unittest.cc | 135 +- base/metrics/persistent_sample_map.cc | 66 +- .../metrics/persistent_sample_map_unittest.cc | 16 +- base/metrics/record_histogram_checker.h | 27 + base/metrics/sample_map.cc | 22 +- base/metrics/sample_map_unittest.cc | 8 +- base/metrics/sample_vector.cc | 378 +- base/metrics/sample_vector.h | 118 +- base/metrics/sample_vector_unittest.cc | 301 +- base/metrics/single_sample_metrics.cc | 77 + base/metrics/single_sample_metrics.h | 104 + .../metrics/single_sample_metrics_unittest.cc | 124 + base/metrics/sparse_histogram.cc | 69 +- base/metrics/sparse_histogram.h | 10 +- base/metrics/sparse_histogram_unittest.cc | 108 +- base/metrics/statistics_recorder.cc | 582 ++- base/metrics/statistics_recorder.h | 343 +- base/metrics/statistics_recorder_unittest.cc | 328 +- base/metrics/user_metrics.cc | 2 +- base/metrics/user_metrics_action.h | 2 +- base/native_library.cc | 15 + base/native_library.h | 22 +- base/native_library_posix.cc | 10 +- base/no_destructor.h | 99 + base/no_destructor_unittest.cc | 76 + base/numerics/BUILD.gn | 28 + base/numerics/README.md | 409 +++ base/numerics/checked_math.h | 393 ++ .../{safe_math_impl.h => checked_math_impl.h} | 378 +- base/numerics/clamped_math.h | 262 ++ base/numerics/clamped_math_impl.h | 341 ++ base/numerics/math_constants.h | 15 + base/numerics/ranges.h | 27 + base/numerics/safe_conversions.h | 216 +- base/numerics/safe_conversions_arm_impl.h | 51 + base/numerics/safe_conversions_impl.h | 168 +- base/numerics/safe_math.h | 504 +-- base/numerics/safe_math_arm_impl.h | 122 + base/numerics/safe_math_clang_gcc_impl.h | 157 + base/numerics/safe_math_shared_impl.h | 237 ++ base/numerics/safe_numerics_unittest.cc | 1186 ------ base/numerics/saturated_arithmetic.h | 107 - base/numerics/saturated_arithmetic_arm.h | 108 - base/observer_list.h | 438 +-- base/observer_list_threadsafe.cc | 16 + base/observer_list_threadsafe.h | 309 +- base/observer_list_unittest.cc | 474 ++- base/optional.h | 922 +++-- base/optional_unittest.cc | 887 ++++- base/optional_unittest.nc | 65 + base/path_service.cc | 32 +- base/path_service.h | 3 - base/pending_task.cc | 33 +- base/pending_task.h | 35 +- base/pickle.cc | 111 +- base/pickle.h | 76 +- base/pickle_unittest.cc | 181 +- base/posix/eintr_wrapper.h | 7 +- base/posix/file_descriptor_shuffle.h | 2 +- base/posix/global_descriptors.cc | 8 +- base/posix/safe_strerror.cc | 2 +- ..._socket_linux.cc => unix_domain_socket.cc} | 95 +- ...in_socket_linux.h => unix_domain_socket.h} | 13 +- ...test.cc => unix_domain_socket_unittest.cc} | 64 +- base/power_monitor/power_monitor.h | 5 +- .../power_monitor_device_source.h | 2 + base/power_monitor/power_monitor_source.h | 11 +- base/power_monitor/power_observer.h | 2 +- base/process/internal_aix.cc | 155 + base/process/internal_aix.h | 84 + base/process/internal_linux.cc | 5 + base/process/internal_linux.h | 22 +- base/process/kill.cc | 38 + base/process/kill.h | 59 +- base/process/kill_posix.cc | 106 +- base/process/launch.h | 186 +- base/process/launch_posix.cc | 257 +- base/process/memory.cc | 2 +- base/process/memory.h | 2 +- base/process/memory_linux.cc | 114 +- base/process/process.h | 34 +- base/process/process_handle.cc | 2 +- base/process/process_handle.h | 26 +- base/process/process_handle_linux.cc | 9 + base/process/process_info.h | 12 +- base/process/process_info_unittest.cc | 6 +- base/process/process_iterator.cc | 9 +- base/process/process_iterator.h | 10 +- base/process/process_iterator_linux.cc | 2 +- base/process/process_metrics.cc | 97 +- base/process/process_metrics.h | 399 +- base/process/process_metrics_iocounters.h | 37 + base/process/process_metrics_linux.cc | 786 ++-- base/process/process_metrics_posix.cc | 42 +- base/process/process_metrics_unittest.cc | 550 +-- base/process/process_posix.cc | 105 +- base/profiler/tracked_time.cc | 68 - base/profiler/tracked_time.h | 71 - base/profiler/tracked_time_unittest.cc | 105 - base/rand_util.cc | 6 + base/rand_util.h | 40 +- base/rand_util_posix.cc | 15 +- base/rand_util_unittest.cc | 16 + base/run_loop.cc | 279 +- base/run_loop.h | 262 +- .../benchmark-octane.js | 58 + .../lock_free_address_hash_set.cc | 72 + .../lock_free_address_hash_set.h | 152 + .../lock_free_address_hash_set_unittest.cc | 183 + .../sampling_heap_profiler.cc | 481 +++ .../sampling_heap_profiler.h | 119 + .../sampling_heap_profiler_unittest.cc | 165 + base/scoped_generic.h | 7 + base/scoped_native_library.cc | 43 + base/scoped_native_library.h | 55 + base/security_unittest.cc | 37 +- base/sequence_checker.h | 82 +- base/sequence_checker_impl.cc | 26 +- base/sequence_checker_unittest.cc | 139 +- base/sequence_token.cc | 4 +- base/sequence_token_unittest.cc | 8 +- base/sequenced_task_runner.cc | 12 +- base/sequenced_task_runner.h | 33 +- base/single_thread_task_runner.h | 8 +- base/stl_util.h | 160 +- base/stl_util_unittest.cc | 239 +- base/strings/char_traits.h | 92 + base/strings/char_traits_unittest.cc | 32 + base/strings/nullable_string16.cc | 22 +- base/strings/nullable_string16.h | 29 +- base/strings/old_utf_string_conversions.cc | 262 ++ base/strings/old_utf_string_conversions.h | 64 + base/strings/pattern.cc | 202 +- base/strings/pattern.h | 13 +- base/strings/pattern_unittest.cc | 16 +- base/strings/safe_sprintf.cc | 8 +- base/strings/safe_sprintf.h | 4 +- base/strings/safe_sprintf_unittest.cc | 12 +- base/strings/strcat.cc | 81 + base/strings/strcat.h | 99 + base/strings/strcat_unittest.cc | 67 + base/strings/string16.cc | 11 +- base/strings/string16.h | 21 +- base/strings/string16_unittest.nc | 25 + base/strings/string_number_conversions.cc | 130 +- base/strings/string_number_conversions.h | 89 +- .../string_number_conversions_fuzzer.cc | 67 + .../string_number_conversions_unittest.cc | 51 +- base/strings/string_piece.cc | 3 +- base/strings/string_piece.h | 86 +- base/strings/string_piece_forward.h | 24 + base/strings/string_piece_unittest.cc | 156 +- base/strings/string_tokenizer.h | 24 +- base/strings/string_tokenizer_fuzzer.cc | 56 + base/strings/string_util.cc | 355 +- base/strings/string_util.h | 42 +- base/strings/string_util_unittest.cc | 225 +- base/strings/string_util_win.h | 44 - base/strings/stringprintf_unittest.cc | 2 +- base/strings/sys_string_conversions.h | 7 +- base/strings/sys_string_conversions_posix.cc | 13 +- base/strings/utf_string_conversion_utils.cc | 7 + base/strings/utf_string_conversions.cc | 336 +- base/strings/utf_string_conversions.h | 6 +- base/strings/utf_string_conversions_fuzzer.cc | 56 + ...tf_string_conversions_regression_fuzzer.cc | 105 + .../utf_string_conversions_unittest.cc | 4 +- base/sync_socket.h | 9 +- base/sync_socket_posix.cc | 63 +- base/sync_socket_unittest.cc | 155 +- base/synchronization/atomic_flag_unittest.cc | 26 +- base/synchronization/condition_variable.h | 18 +- .../condition_variable_posix.cc | 7 +- .../condition_variable_unittest.cc | 22 +- base/synchronization/lock.h | 12 +- base/synchronization/lock_impl.h | 27 +- base/synchronization/lock_impl_posix.cc | 73 +- base/synchronization/lock_unittest.cc | 42 + base/synchronization/read_write_lock.h | 105 - base/synchronization/read_write_lock_posix.cc | 40 - .../synchronization_buildflags.h | 4 + base/synchronization/waitable_event.h | 95 +- .../waitable_event_perftest.cc | 178 + base/synchronization/waitable_event_posix.cc | 14 +- .../waitable_event_unittest.cc | 32 + base/synchronization/waitable_event_watcher.h | 55 +- .../waitable_event_watcher_posix.cc | 39 +- base/sys_info.cc | 57 +- base/sys_info.h | 40 +- base/sys_info_chromeos.cc | 22 +- base/sys_info_internal.h | 2 +- base/sys_info_linux.cc | 4 +- base/sys_info_posix.cc | 21 +- base/sys_info_unittest.cc | 51 +- base/task/README.md | 12 + base/task/cancelable_task_tracker.cc | 83 +- base/task/cancelable_task_tracker.h | 68 +- base/task/cancelable_task_tracker_unittest.cc | 76 +- base/task/sequence_manager/enqueue_order.cc | 17 + base/task/sequence_manager/enqueue_order.h | 71 + .../graceful_queue_shutdown_helper.cc | 42 + .../graceful_queue_shutdown_helper.h | 50 + base/task/sequence_manager/intrusive_heap.h | 229 ++ .../intrusive_heap_unittest.cc | 378 ++ .../lazily_deallocated_deque.h | 364 ++ .../lazily_deallocated_deque_unittest.cc | 364 ++ base/task/sequence_manager/lazy_now.cc | 36 + base/task/sequence_manager/lazy_now.h | 41 + .../sequence_manager/moveable_auto_lock.h | 41 + .../task/sequence_manager/real_time_domain.cc | 48 + base/task/sequence_manager/real_time_domain.h | 37 + .../task/sequence_manager/sequence_manager.cc | 26 + base/task/sequence_manager/sequence_manager.h | 132 + .../sequence_manager/sequence_manager_impl.cc | 724 ++++ .../sequence_manager/sequence_manager_impl.h | 341 ++ .../sequence_manager_impl_unittest.cc | 3260 +++++++++++++++++ .../sequence_manager_perftest.cc | 306 ++ .../sequence_manager/sequenced_task_source.h | 37 + base/task/sequence_manager/task_queue.cc | 289 ++ base/task/sequence_manager/task_queue.h | 368 ++ base/task/sequence_manager/task_queue_impl.cc | 1016 +++++ base/task/sequence_manager/task_queue_impl.h | 471 +++ .../sequence_manager/task_queue_selector.cc | 407 ++ .../sequence_manager/task_queue_selector.h | 225 ++ .../task_queue_selector_logic.h | 37 + .../task_queue_selector_unittest.cc | 885 +++++ .../sequence_manager/task_time_observer.h | 32 + base/task/sequence_manager/test/fake_task.cc | 35 + base/task/sequence_manager/test/fake_task.h | 31 + .../test/lazy_thread_controller_for_test.cc | 123 + .../test/lazy_thread_controller_for_test.h | 53 + .../sequence_manager/test/mock_time_domain.cc | 39 + .../sequence_manager/test/mock_time_domain.h | 38 + .../test/sequence_manager_for_test.cc | 79 + .../test/sequence_manager_for_test.h | 46 + .../sequence_manager/test/test_task_queue.cc | 23 + .../sequence_manager/test/test_task_queue.h | 33 + .../test/test_task_time_observer.h | 23 + .../task/sequence_manager/thread_controller.h | 85 + .../thread_controller_impl.cc | 269 ++ .../sequence_manager/thread_controller_impl.h | 130 + ...hread_controller_with_message_pump_impl.cc | 205 ++ ...thread_controller_with_message_pump_impl.h | 109 + base/task/sequence_manager/time_domain.cc | 136 + base/task/sequence_manager/time_domain.h | 139 + .../sequence_manager/time_domain_unittest.cc | 324 ++ base/task/sequence_manager/work_queue.cc | 236 ++ base/task/sequence_manager/work_queue.h | 152 + base/task/sequence_manager/work_queue_sets.cc | 172 + base/task/sequence_manager/work_queue_sets.h | 102 + .../work_queue_sets_unittest.cc | 328 ++ .../sequence_manager/work_queue_unittest.cc | 475 +++ base/task_runner.cc | 17 +- base/task_runner.h | 40 +- base/task_runner_util.h | 35 +- base/task_runner_util_unittest.cc | 1 + .../can_schedule_sequence_observer.h | 27 + base/task_scheduler/environment_config.cc | 45 + base/task_scheduler/environment_config.h | 53 + base/task_scheduler/lazy_task_runner.cc | 122 + base/task_scheduler/lazy_task_runner.h | 218 ++ .../lazy_task_runner_unittest.cc | 199 + base/task_scheduler/post_task.cc | 132 + base/task_scheduler/post_task.h | 225 ++ .../scheduler_worker_observer.h | 27 + base/task_scheduler/scheduler_worker_pool.cc | 219 ++ .../scheduler_worker_pool_unittest.cc | 343 ++ base/task_scheduler/sequence.cc | 31 +- base/task_scheduler/sequence.h | 29 +- base/task_scheduler/sequence_sort_key.h | 3 + base/task_scheduler/sequence_unittest.cc | 265 +- base/task_scheduler/service_thread.cc | 93 + base/task_scheduler/service_thread.h | 60 + .../task_scheduler/service_thread_unittest.cc | 111 + .../single_thread_task_runner_thread_mode.h | 25 + base/task_scheduler/task.cc | 49 +- base/task_scheduler/task.h | 15 +- base/task_scheduler/task_scheduler.h | 248 ++ base/task_scheduler/task_traits.cc | 33 - base/task_scheduler/task_traits.h | 193 +- base/task_scheduler/task_traits_details.h | 128 + base/task_scheduler/task_traits_unittest.cc | 175 + base/task_scheduler/task_traits_unittest.nc | 31 + base/task_scheduler/test_utils.cc | 45 + base/task_scheduler/test_utils.h | 32 + base/task_scheduler/tracked_ref.h | 171 + base/task_scheduler/tracked_ref_unittest.cc | 150 + base/template_util.h | 90 +- base/template_util_unittest.cc | 44 +- base/test/DEPS | 3 - .../android/java_handler_thread_helpers.cc | 39 + .../android/java_handler_thread_helpers.h | 42 + .../base/test/params/ParameterProvider.java | 11 + .../test/util/AnnotationProcessingUtils.java | 259 ++ .../base/test/util/AnnotationRule.java | 139 + .../util/AnnotationProcessingUtilsTest.java | 377 ++ base/test/android/url_utils.cc | 24 + base/test/android/url_utils.h | 23 + base/test/bind_test_util.h | 36 + base/test/copy_only_int.h | 55 + base/test/data/file_util/.gitattributes | 2 + base/test/data/json/bom_feff.json | 18 +- base/test/fontconfig_util_linux.cc | 423 +++ base/test/fontconfig_util_linux.h | 18 + base/test/generate_fontconfig_caches.cc | 24 + base/test/gtest_util.cc | 5 +- base/test/gtest_util.h | 18 - base/test/metrics/histogram_enum_reader.cc | 156 + base/test/metrics/histogram_enum_reader.h | 31 + .../metrics/histogram_enum_reader_unittest.cc | 31 + base/test/metrics/histogram_tester.cc | 231 ++ base/test/metrics/histogram_tester.h | 179 + base/test/mock_entropy_provider.cc | 2 +- base/test/move_only_int.h | 68 + base/test/multiprocess_test.cc | 23 +- base/test/multiprocess_test.h | 26 +- base/test/multiprocess_test_android.cc | 28 +- base/test/opaque_ref_counted.cc | 59 - base/test/opaque_ref_counted.h | 29 - .../scoped_environment_variable_override.cc | 33 + .../scoped_environment_variable_override.h | 40 + base/test/scoped_feature_list.cc | 214 +- base/test/scoped_feature_list.h | 93 +- base/test/scoped_feature_list_unittest.cc | 308 ++ base/test/scoped_locale.cc | 6 +- base/test/scoped_task_environment.cc | 346 ++ base/test/scoped_task_environment.h | 177 + base/test/scoped_task_environment_unittest.cc | 324 ++ base/test/sequenced_worker_pool_owner.cc | 65 - base/test/sequenced_worker_pool_owner.h | 71 - base/test/simple_test_clock.cc | 6 +- base/test/simple_test_clock.h | 4 +- base/test/simple_test_tick_clock.cc | 6 +- base/test/simple_test_tick_clock.h | 4 +- base/test/test_child_process.cc | 43 + base/test/test_file_util.cc | 5 - base/test/test_file_util_linux.cc | 31 + base/test/test_file_util_posix.cc | 8 +- base/test/test_io_thread.cc | 6 +- base/test/test_io_thread.h | 3 +- base/test/test_mock_time_task_runner.cc | 332 +- base/test/test_mock_time_task_runner.h | 136 +- .../test_mock_time_task_runner_unittest.cc | 290 ++ base/test/test_pending_task.cc | 4 +- base/test/test_pending_task.h | 4 +- base/test/test_shared_memory_util.cc | 187 + base/test/test_shared_memory_util.h | 56 + base/test/test_simple_task_runner.cc | 25 +- base/test/test_simple_task_runner.h | 13 +- base/test/test_switches.cc | 9 + base/test/test_switches.h | 2 + base/test/test_timeouts.cc | 97 +- base/test/test_timeouts.h | 9 +- base/third_party/icu/LICENSE | 50 +- base/third_party/icu/README.chromium | 15 +- base/third_party/icu/icu_utf.cc | 260 +- base/third_party/icu/icu_utf.h | 294 +- base/thread_annotations.h | 238 ++ base/thread_annotations_unittest.cc | 58 + base/thread_annotations_unittest.nc | 71 + base/threading/non_thread_safe.h | 62 - base/threading/non_thread_safe_impl.cc | 23 - base/threading/non_thread_safe_impl.h | 39 - base/threading/non_thread_safe_unittest.cc | 148 - base/threading/platform_thread.h | 38 +- base/threading/platform_thread_linux.cc | 14 +- base/threading/platform_thread_posix.cc | 28 +- base/threading/platform_thread_unittest.cc | 40 +- base/threading/post_task_and_reply_impl.cc | 137 +- base/threading/post_task_and_reply_impl.h | 23 +- base/threading/scoped_blocking_call.cc | 72 + base/threading/scoped_blocking_call.h | 140 + .../scoped_blocking_call_unittest.cc | 134 + base/threading/sequence_local_storage_map.cc | 105 + base/threading/sequence_local_storage_map.h | 90 + .../sequence_local_storage_map_unittest.cc | 117 + base/threading/sequence_local_storage_slot.cc | 26 + base/threading/sequence_local_storage_slot.h | 105 + .../sequence_local_storage_slot_unittest.cc | 143 + .../threading/sequenced_task_runner_handle.cc | 58 +- base/threading/sequenced_task_runner_handle.h | 5 +- base/threading/sequenced_worker_pool.cc | 1680 --------- base/threading/sequenced_worker_pool.h | 418 --- base/threading/simple_thread.cc | 35 +- base/threading/simple_thread.h | 49 +- base/threading/simple_thread_unittest.cc | 4 +- base/threading/thread.cc | 14 +- base/threading/thread.h | 17 +- base/threading/thread_checker.h | 116 +- base/threading/thread_checker_impl.h | 6 +- base/threading/thread_checker_unittest.cc | 62 +- base/threading/thread_collision_warner.h | 6 +- .../thread_collision_warner_unittest.cc | 2 +- base/threading/thread_id_name_manager.cc | 35 +- base/threading/thread_id_name_manager.h | 16 +- base/threading/thread_local_storage.cc | 104 +- base/threading/thread_local_storage.h | 123 +- base/threading/thread_local_storage_posix.cc | 4 - .../thread_local_storage_unittest.cc | 158 +- base/threading/thread_local_unittest.cc | 27 +- base/threading/thread_restrictions.cc | 145 +- base/threading/thread_restrictions.h | 379 +- .../threading/thread_restrictions_unittest.cc | 137 + base/threading/thread_task_runner_handle.cc | 43 +- base/threading/thread_task_runner_handle.h | 6 +- base/threading/thread_unittest.cc | 60 +- base/threading/worker_pool.cc | 125 - base/threading/worker_pool.h | 59 - base/threading/worker_pool_posix.cc | 190 - base/threading/worker_pool_posix.h | 90 - base/threading/worker_pool_posix_unittest.cc | 250 -- base/threading/worker_pool_unittest.cc | 118 - base/time/clock.cc | 2 +- base/time/clock.h | 2 +- base/time/default_clock.cc | 12 +- base/time/default_clock.h | 5 +- base/time/default_tick_clock.cc | 12 +- base/time/default_tick_clock.h | 6 +- base/time/pr_time_unittest.cc | 7 +- base/time/tick_clock.cc | 2 +- base/time/tick_clock.h | 2 +- base/time/time.cc | 117 +- base/time/time.h | 340 +- base/time/time_android.cc | 26 + base/time/time_conversion_posix.cc | 67 + .../{time_posix.cc => time_exploded_posix.cc} | 249 +- base/time/time_now_posix.cc | 123 + base/time/time_override.cc | 45 + base/time/time_override.h | 74 + base/time/time_to_iso8601.cc | 20 + base/time/time_to_iso8601.h | 20 + base/time/time_unittest.cc | 803 +++- base/timer/elapsed_timer.cc | 8 + base/timer/elapsed_timer.h | 3 + base/timer/hi_res_timer_manager.h | 12 +- base/timer/hi_res_timer_manager_posix.cc | 7 +- base/timer/hi_res_timer_manager_unittest.cc | 5 +- base/timer/mock_timer.cc | 92 +- base/timer/mock_timer.h | 77 +- base/timer/mock_timer_unittest.cc | 17 +- base/timer/timer.cc | 165 +- base/timer/timer.h | 268 +- base/timer/timer_unittest.cc | 214 +- base/trace_event/common/trace_event_common.h | 45 +- base/trace_event/heap_profiler.h | 30 + base/trace_event/trace_event.h | 91 +- base/tracked_objects.cc | 1085 ------ base/tracked_objects.h | 898 ----- base/tracked_objects_unittest.cc | 1375 ------- base/tracking_info.cc | 28 - base/tracking_info.h | 58 - base/tuple.h | 67 +- base/tuple_unittest.cc | 35 +- base/unguessable_token.cc | 4 +- base/unguessable_token.h | 17 +- base/value_iterators.cc | 228 ++ base/value_iterators.h | 194 + base/value_iterators_unittest.cc | 335 ++ base/values.cc | 807 ++-- base/values.h | 429 ++- base/values_unittest.cc | 1560 +++++--- base/version.cc | 9 +- base/version_unittest.cc | 31 +- base/vlog.cc | 4 +- build/android/gyp/util/build_utils.py | 226 +- build/android/pylib/constants/__init__.py | 145 +- build/android/pylib/constants/host_paths.py | 55 + build/build_config.h | 51 +- build/gn_helpers.py | 2 +- components/timers/DEPS | 10 - components/timers/alarm_timer_chromeos.cc | 112 +- components/timers/alarm_timer_chromeos.h | 54 +- crypto/apple_keychain.h | 70 +- crypto/ec_private_key.h | 3 +- crypto/hmac.cc | 18 +- crypto/hmac.h | 16 +- crypto/nss_crypto_module_delegate.h | 18 +- crypto/nss_util.cc | 317 +- crypto/nss_util.h | 31 +- crypto/nss_util_internal.h | 15 +- crypto/openssl_util.cc | 6 +- crypto/openssl_util.h | 7 +- crypto/p224.cc | 2 +- crypto/p224.h | 2 +- crypto/p224_spake.cc | 9 +- crypto/p224_spake.h | 5 +- crypto/rsa_private_key.cc | 7 +- crypto/scoped_test_nss_db.cc | 13 +- crypto/secure_hash.cc | 4 +- crypto/secure_hash.h | 3 +- crypto/secure_hash_unittest.cc | 25 + crypto/sha2.cc | 6 +- crypto/sha2.h | 7 +- crypto/signature_creator_unittest.cc | 24 +- crypto/signature_verifier.h | 54 +- crypto/signature_verifier_unittest.cc | 1094 +----- crypto/symmetric_key.cc | 5 - crypto/symmetric_key.h | 12 +- crypto/symmetric_key_unittest.cc | 33 +- dbus/DEPS | 3 - dbus/bus.cc | 194 +- dbus/bus.h | 15 +- dbus/exported_object.cc | 33 +- dbus/message.cc | 208 +- dbus/message.h | 41 +- dbus/mock_bus.cc | 3 +- dbus/mock_bus.h | 12 +- dbus/mock_exported_object.cc | 3 +- dbus/mock_exported_object.h | 2 +- dbus/mock_object_manager.cc | 18 - dbus/mock_object_manager.h | 41 - dbus/mock_object_proxy.cc | 32 +- dbus/mock_object_proxy.h | 87 +- dbus/object_manager.cc | 22 +- dbus/object_manager.h | 22 +- dbus/object_proxy.cc | 404 +- dbus/object_proxy.h | 137 +- dbus/property.cc | 89 +- dbus/property.h | 25 +- dbus/util.h | 7 - dbus/values_util.cc | 60 +- device/bluetooth/bluetooth_advertisement.cc | 9 +- device/bluetooth/bluetooth_advertisement.h | 4 +- device/bluetooth/bluetooth_uuid.cc | 36 +- device/bluetooth/bluetooth_uuid.h | 18 + ...bluetooth_service_attribute_value_bluez.cc | 7 +- .../common/common_custom_types__type_mappings | 193 - ipc/ipc.mojom | 27 +- ipc/ipc_buildflags.h | 8 + ipc/ipc_channel.cc | 51 + ipc/ipc_channel.h | 266 ++ ipc/ipc_channel_common.cc | 78 + ipc/ipc_channel_factory.cc | 56 + ipc/ipc_channel_factory.h | 38 + ipc/ipc_channel_mojo.cc | 351 ++ ipc/ipc_channel_mojo.h | 143 + ipc/ipc_channel_mojo_unittest.cc | 1791 +++++++++ ipc/ipc_channel_nacl.cc | 396 ++ ipc/ipc_channel_nacl.h | 114 + ipc/ipc_channel_proxy.cc | 583 +++ ipc/ipc_channel_proxy.h | 425 +++ ipc/ipc_channel_proxy_unittest.cc | 437 +++ ipc/ipc_channel_proxy_unittest_messages.h | 46 + ipc/ipc_channel_reader.cc | 215 ++ ipc/ipc_channel_reader.h | 167 + ipc/ipc_channel_reader_unittest.cc | 249 ++ ipc/ipc_cpu_perftest.cc | 417 +++ ipc/ipc_export.h | 34 - ipc/ipc_fuzzing_tests.cc | 364 ++ ipc/ipc_listener.h | 8 +- ipc/ipc_logging.cc | 313 ++ ipc/ipc_logging.h | 128 + ipc/ipc_message.cc | 22 +- ipc/ipc_message.h | 25 +- ipc/ipc_message_attachment.cc | 145 +- ipc/ipc_message_attachment.h | 21 +- ipc/ipc_message_attachment_set.cc | 12 +- ipc/ipc_message_attachment_set.h | 8 +- ...c_message_attachment_set_posix_unittest.cc | 152 + ipc/ipc_message_macros.h | 560 +++ ipc/ipc_message_null_macros.h | 27 + ipc/ipc_message_pipe_reader.cc | 129 + ipc/ipc_message_pipe_reader.h | 114 + ipc/ipc_message_protobuf_utils.h | 67 + ipc/ipc_message_protobuf_utils_unittest.cc | 170 + ipc/ipc_message_start.h | 55 +- ipc/ipc_message_support_export.h | 31 + ipc/ipc_message_templates.h | 269 ++ ipc/ipc_message_templates_impl.h | 112 + ipc/ipc_message_unittest.cc | 281 ++ ipc/ipc_message_utils.cc | 863 +++-- ipc/ipc_message_utils.h | 560 ++- ipc/ipc_message_utils_unittest.cc | 240 ++ ipc/ipc_mojo_bootstrap.cc | 1035 ++++++ ipc/ipc_mojo_bootstrap.h | 65 + ipc/ipc_mojo_bootstrap_unittest.cc | 207 ++ ipc/ipc_mojo_handle_attachment.cc | 3 +- ipc/ipc_mojo_handle_attachment.h | 5 +- ipc/ipc_mojo_message_helper.cc | 5 +- ipc/ipc_mojo_message_helper.h | 4 +- ipc/ipc_mojo_param_traits.cc | 58 +- ipc/ipc_mojo_param_traits.h | 17 +- ipc/ipc_mojo_perftest.cc | 864 +++++ ipc/ipc_param_traits.h | 11 + ipc/ipc_perftest_messages.cc | 7 + ipc/ipc_perftest_messages.h | 16 + ipc/ipc_perftest_util.cc | 145 + ipc/ipc_perftest_util.h | 119 + ipc/ipc_platform_file.cc | 76 + ipc/ipc_platform_file.h | 86 + ipc/ipc_platform_file_attachment_posix.cc | 3 +- ipc/ipc_platform_file_attachment_posix.h | 5 +- ipc/ipc_security_test_util.cc | 25 + ipc/ipc_security_test_util.h | 40 + ipc/ipc_send_fds_test.cc | 218 ++ ipc/ipc_sender.h | 28 + ipc/ipc_sync_channel.cc | 730 ++++ ipc/ipc_sync_channel.h | 263 ++ ipc/ipc_sync_channel_unittest.cc | 1884 ++++++++++ ipc/ipc_sync_message.cc | 116 + ipc/ipc_sync_message.h | 7 +- ipc/ipc_sync_message_filter.cc | 198 + ipc/ipc_sync_message_filter.h | 101 + ipc/ipc_sync_message_unittest.cc | 317 ++ ipc/ipc_sync_message_unittest.h | 120 + ipc/ipc_test_base.cc | 80 + ipc/ipc_test_base.h | 111 + ipc/ipc_test_channel_listener.cc | 63 + ipc/ipc_test_channel_listener.h | 46 + ipc/ipc_test_message_generator.cc | 33 + ipc/ipc_test_message_generator.h | 7 + ipc/ipc_test_messages.h | 38 + ipc/ipc_test_sink.cc | 86 + ipc/ipc_test_sink.h | 141 + ipc/message_filter.cc | 37 + ipc/message_filter.h | 69 + ipc/message_filter_router.cc | 96 + ipc/message_filter_router.h | 42 + ipc/message_mojom_traits.cc | 40 + ipc/message_mojom_traits.h | 31 + ipc/message_router.cc | 59 + ipc/message_router.h | 74 + ipc/message_view.cc | 30 + ipc/message_view.h | 56 + ipc/native_handle_type_converters.cc | 49 + ipc/native_handle_type_converters.h | 30 + ipc/param_traits_log_macros.h | 52 + ipc/param_traits_macros.h | 65 + ipc/param_traits_read_macros.h | 47 + ipc/param_traits_write_macros.h | 40 + ipc/run_all_perftests.cc | 25 + ipc/run_all_unittests.cc | 35 + ipc/struct_constructor_macros.h | 21 + ipc/struct_destructor_macros.h | 17 + ipc/sync_socket_unittest.cc | 306 ++ .../jni_registration_generator_helper.sh | 40 + libchrome_tools/mojom_source_generator.sh | 144 - libchrome_tools/patch/580fcef.patch | 112 + libchrome_tools/patch/8fbafc9.patch | 25 + libchrome_tools/patch/ContextUtils.patch | 18 + ...ge-Add-a-function-to-destroy-pthread.patch | 0 libchrome_tools/patch/allocator_shim.patch | 2 +- libchrome_tools/patch/buildflag_header.patch | 95 +- libchrome_tools/patch/c7ce19d.patch | 28 + libchrome_tools/patch/dmg_fp.patch | 74 +- libchrome_tools/patch/file_path_mojom.patch | 19 + libchrome_tools/patch/file_posix.patch | 11 +- libchrome_tools/patch/handle_table.patch | 176 + libchrome_tools/patch/hash.patch | 17 +- .../patch/jni_registration_generator.patch | 13 + libchrome_tools/patch/lazy_instance.patch | 18 - libchrome_tools/patch/logging.patch | 20 +- libchrome_tools/patch/macros.patch | 45 +- libchrome_tools/patch/memory_linux.patch | 17 + libchrome_tools/patch/message_loop.patch | 32 +- .../patch/message_loop_unittest.patch | 125 + .../patch/message_pump_for_ui.patch | 28 + ...o-handle-unhandled-RuntimeExceptions.patch | 129 - ...sh-when-NodeController-pending-invit.patch | 44 - libchrome_tools/patch/mojo.patch | 507 +-- .../mojom_disable_trace_and_mem_dump.patch | 299 ++ .../patch/observer_list_unittest.patch | 65 + libchrome_tools/patch/path_service.patch | 13 +- .../patch/shared_memory_handle.patch | 22 + .../patch/shared_memory_mapping.patch | 33 + .../patch/shared_memory_posix.patch | 74 +- libchrome_tools/patch/ssl.patch | 46 +- libchrome_tools/patch/statfs_f_type.patch | 2 +- libchrome_tools/patch/subprocess.patch | 36 +- libchrome_tools/patch/task_annotator.patch | 12 + libchrome_tools/patch/task_scheduler.patch | 121 - libchrome_tools/patch/time.patch | 19 - libchrome_tools/patch/trace_event.patch | 28 +- libchrome_tools/patch/values.patch | 63 + libchrome_tools/update_libchrome.py | 20 +- mojo/BUILD.gn | 50 +- mojo/DEPS | 7 - mojo/README.md | 135 +- mojo/android/DEPS | 3 - mojo/android/javatests/DEPS | 4 - mojo/android/javatests/init_library.cc | 50 - mojo/android/javatests/mojo_test_case.h | 20 - .../src/org/chromium/mojo/MojoTestCase.java | 66 - .../mojo/bindings/BindingsHelperTest.java | 55 - mojo/android/javatests/validation_test_util.h | 20 - mojo/android/system/base_run_loop.h | 20 - mojo/android/system/core_impl.h | 20 - mojo/android/system/watcher_impl.h | 20 - mojo/common/BUILD.gn | 93 - mojo/common/DEPS | 6 - .../common_custom_types_struct_traits.cc | 108 - .../common_custom_types_struct_traits.h | 85 - mojo/common/common_custom_types_unittest.cc | 433 --- mojo/common/file.typemap | 13 - mojo/common/file_path.mojom | 8 - mojo/common/file_path.typemap | 12 - mojo/common/mojo_common_export.h | 32 - mojo/common/string16.mojom | 12 - mojo/common/string16.typemap | 12 - mojo/common/struct_traits_unittest.cc | 57 - mojo/common/test_common_custom_types.mojom | 61 - mojo/common/text_direction.typemap | 12 - mojo/common/time.typemap | 20 - mojo/common/time_struct_traits.h | 55 - mojo/common/traits_test_service.mojom | 14 - mojo/common/typemaps.gni | 14 - mojo/common/unguessable_token.typemap | 12 - mojo/common/values.mojom | 32 - mojo/common/values.typemap | 25 - mojo/common/values_struct_traits.cc | 109 - mojo/common/values_struct_traits.h | 257 -- mojo/common/version.mojom | 10 - mojo/common/version.typemap | 12 - mojo/core/BUILD.gn | 325 ++ mojo/{edk/system => core}/atomic_flag.h | 18 +- mojo/core/broker.h | 55 + mojo/core/broker_host.cc | 180 + mojo/core/broker_host.h | 73 + mojo/{edk/system => core}/broker_messages.h | 31 +- mojo/core/broker_posix.cc | 148 + mojo/{edk/system => core}/broker_win.cc | 95 +- mojo/{edk/system => core}/channel.cc | 281 +- mojo/{edk/system => core}/channel.h | 145 +- mojo/core/channel_fuchsia.cc | 466 +++ mojo/core/channel_posix.cc | 768 ++++ mojo/{edk/system => core}/channel_unittest.cc | 121 +- mojo/core/channel_win.cc | 377 ++ mojo/core/configuration.cc | 15 + mojo/{edk/system => core}/configuration.h | 18 +- mojo/core/connection_params.cc | 31 + mojo/core/connection_params.h | 49 + mojo/core/core.cc | 1513 ++++++++ mojo/core/core.h | 385 ++ mojo/{edk/system => core}/core_test_base.cc | 64 +- mojo/{edk/system => core}/core_test_base.h | 13 +- mojo/core/core_unittest.cc | 459 +++ .../data_pipe_consumer_dispatcher.cc | 252 +- .../data_pipe_consumer_dispatcher.h | 67 +- .../data_pipe_control_message.cc | 26 +- .../data_pipe_control_message.h | 13 +- .../data_pipe_producer_dispatcher.cc | 231 +- .../data_pipe_producer_dispatcher.h | 68 +- .../system => core}/data_pipe_unittest.cc | 559 +-- mojo/{edk/system => core}/dispatcher.cc | 102 +- mojo/{edk/system => core}/dispatcher.h | 118 +- mojo/core/embedder/BUILD.gn | 29 + mojo/core/embedder/README.md | 86 + mojo/core/embedder/configuration.h | 47 + mojo/core/embedder/embedder.cc | 50 + mojo/core/embedder/embedder.h | 65 + mojo/core/embedder/process_error_callback.h | 21 + .../embedder/scoped_ipc_support.cc | 25 +- .../embedder/scoped_ipc_support.h | 22 +- mojo/core/embedder_unittest.cc | 429 +++ mojo/core/entrypoints.cc | 414 +++ mojo/core/entrypoints.h | 25 + mojo/core/export_only_thunks_api.lst | 12 + mojo/core/handle_signals_state.h | 111 + mojo/{edk/system => core}/handle_table.cc | 89 +- mojo/{edk/system => core}/handle_table.h | 44 +- mojo/core/handle_table_unittest.cc | 73 + mojo/core/invitation_dispatcher.cc | 78 + mojo/core/invitation_dispatcher.h | 49 + mojo/core/invitation_unittest.cc | 874 +++++ mojo/{edk/system => core}/mach_port_relay.cc | 160 +- mojo/{edk/system => core}/mach_port_relay.h | 48 +- .../message_pipe_dispatcher.cc | 306 +- .../system => core}/message_pipe_dispatcher.h | 51 +- .../system => core}/message_pipe_perftest.cc | 58 +- .../system => core}/message_pipe_unittest.cc | 285 +- mojo/core/message_unittest.cc | 956 +++++ mojo/core/mojo_core.cc | 42 + mojo/core/mojo_core.def | 10 + mojo/core/mojo_core_unittest.cc | 39 + .../multiprocess_message_pipe_unittest.cc | 571 ++- mojo/{edk/system => core}/node_channel.cc | 542 +-- mojo/{edk/system => core}/node_channel.h | 127 +- mojo/core/node_controller.cc | 1272 +++++++ mojo/{edk/system => core}/node_controller.h | 244 +- .../{edk/system => core}/options_validation.h | 12 +- .../options_validation_unittest.cc | 6 +- .../platform_handle_dispatcher.cc | 24 +- .../platform_handle_dispatcher.h | 24 +- .../platform_handle_dispatcher_unittest.cc | 36 +- mojo/core/platform_handle_in_transit.cc | 156 + mojo/core/platform_handle_in_transit.h | 107 + mojo/core/platform_handle_utils.cc | 67 + mojo/core/platform_handle_utils.h | 35 + mojo/core/platform_shared_memory_mapping.cc | 104 + mojo/core/platform_shared_memory_mapping.h | 60 + .../platform_wrapper_unittest.cc | 135 +- mojo/{edk/system => core}/ports/BUILD.gn | 19 +- mojo/core/ports/event.cc | 383 ++ mojo/core/ports/event.h | 284 ++ .../system => core}/ports/message_filter.h | 18 +- .../system => core}/ports/message_queue.cc | 41 +- .../system => core}/ports/message_queue.h | 45 +- mojo/{edk/system => core}/ports/name.cc | 10 +- mojo/{edk/system => core}/ports/name.h | 30 +- mojo/core/ports/name_unittest.cc | 75 + mojo/core/ports/node.cc | 1388 +++++++ mojo/{edk/system => core}/ports/node.h | 172 +- mojo/core/ports/node_delegate.h | 38 + mojo/{edk/system => core}/ports/port.cc | 6 +- mojo/core/ports/port.h | 175 + mojo/core/ports/port_locker.cc | 74 + mojo/core/ports/port_locker.h | 86 + mojo/core/ports/port_ref.cc | 30 + mojo/{edk/system => core}/ports/port_ref.h | 26 +- .../system => core}/ports/ports_unittest.cc | 436 ++- mojo/{edk/system => core}/ports/user_data.h | 10 +- mojo/core/ports/user_message.cc | 25 + mojo/core/ports/user_message.h | 56 + mojo/core/quota_unittest.cc | 314 ++ mojo/{edk/system => core}/request_context.cc | 22 +- mojo/{edk/system => core}/request_context.h | 19 +- mojo/core/run_all_core_unittests.cc | 18 + mojo/core/scoped_process_handle.cc | 90 + mojo/core/scoped_process_handle.h | 65 + mojo/core/shared_buffer_dispatcher.cc | 445 +++ .../shared_buffer_dispatcher.h | 59 +- .../shared_buffer_dispatcher_unittest.cc | 160 +- .../system => core}/shared_buffer_unittest.cc | 95 +- mojo/core/signals_unittest.cc | 222 ++ .../{edk/system => core}/system_impl_export.h | 10 +- mojo/{edk => core}/test/BUILD.gn | 61 +- mojo/{edk => core}/test/mojo_test_base.cc | 171 +- mojo/{edk => core}/test/mojo_test_base.h | 119 +- .../test/multiprocess_test_helper.cc | 179 +- .../test/multiprocess_test_helper.h | 28 +- mojo/{edk => core}/test/run_all_perftests.cc | 16 +- mojo/{edk => core}/test/run_all_unittests.cc | 17 +- mojo/{edk => core}/test/test_support_impl.cc | 14 +- mojo/{edk => core}/test/test_support_impl.h | 10 +- mojo/core/test/test_utils.cc | 33 + mojo/core/test/test_utils.h | 30 + mojo/core/test/test_utils_win.cc | 49 + mojo/{edk/system => core}/test_utils.cc | 12 +- mojo/{edk/system => core}/test_utils.h | 10 +- mojo/core/trap_unittest.cc | 1763 +++++++++ mojo/core/user_message_impl.cc | 686 ++++ mojo/core/user_message_impl.h | 218 ++ mojo/{edk/system => core}/watch.cc | 41 +- mojo/{edk/system => core}/watch.h | 21 +- .../system => core}/watcher_dispatcher.cc | 101 +- .../{edk/system => core}/watcher_dispatcher.h | 41 +- mojo/{edk/system => core}/watcher_set.cc | 6 +- mojo/{edk/system => core}/watcher_set.h | 19 +- mojo/edk/DEPS | 14 - mojo/edk/embedder/BUILD.gn | 147 - mojo/edk/embedder/README.md | 346 -- mojo/edk/embedder/configuration.h | 65 - mojo/edk/embedder/connection_params.cc | 28 - mojo/edk/embedder/connection_params.h | 34 - mojo/edk/embedder/embedder.cc | 158 - mojo/edk/embedder/embedder.h | 173 - mojo/edk/embedder/embedder_internal.h | 44 - mojo/edk/embedder/embedder_unittest.cc | 603 --- mojo/edk/embedder/entrypoints.cc | 283 -- mojo/edk/embedder/entrypoints.h | 22 - .../embedder/named_platform_channel_pair.h | 73 - .../named_platform_channel_pair_win.cc | 89 - mojo/edk/embedder/named_platform_handle.h | 51 - .../embedder/named_platform_handle_utils.h | 56 - .../named_platform_handle_utils_posix.cc | 141 - .../named_platform_handle_utils_win.cc | 95 - .../embedder/pending_process_connection.cc | 50 - .../edk/embedder/pending_process_connection.h | 124 - mojo/edk/embedder/platform_channel_pair.cc | 34 - mojo/edk/embedder/platform_channel_pair.h | 106 - .../embedder/platform_channel_pair_posix.cc | 172 - .../platform_channel_pair_posix_unittest.cc | 261 -- .../edk/embedder/platform_channel_pair_win.cc | 123 - .../embedder/platform_channel_utils_posix.cc | 282 -- .../embedder/platform_channel_utils_posix.h | 87 - mojo/edk/embedder/platform_handle.cc | 74 - mojo/edk/embedder/platform_handle.h | 90 - mojo/edk/embedder/platform_handle_utils.h | 33 - .../embedder/platform_handle_utils_posix.cc | 24 - .../edk/embedder/platform_handle_utils_win.cc | 28 - mojo/edk/embedder/platform_handle_vector.h | 35 - mojo/edk/embedder/platform_shared_buffer.cc | 325 -- mojo/edk/embedder/platform_shared_buffer.h | 178 - .../platform_shared_buffer_unittest.cc | 227 -- mojo/edk/embedder/scoped_platform_handle.h | 63 - mojo/edk/embedder/test_embedder.cc | 46 - mojo/edk/embedder/test_embedder.h | 28 - mojo/edk/js/BUILD.gn | 35 - mojo/edk/js/core.cc | 454 --- mojo/edk/js/core.h | 25 - mojo/edk/js/drain_data.cc | 129 - mojo/edk/js/drain_data.h | 65 - mojo/edk/js/handle.cc | 85 - mojo/edk/js/handle.h | 107 - mojo/edk/js/handle_close_observer.h | 24 - mojo/edk/js/handle_unittest.cc | 92 - mojo/edk/js/js_export.h | 32 - mojo/edk/js/mojo_runner_delegate.cc | 80 - mojo/edk/js/mojo_runner_delegate.h | 36 - mojo/edk/js/support.cc | 77 - mojo/edk/js/support.h | 25 - mojo/edk/js/tests/BUILD.gn | 68 - mojo/edk/js/tests/js_to_cpp.mojom | 54 - mojo/edk/js/tests/js_to_cpp_tests.cc | 455 --- mojo/edk/js/tests/js_to_cpp_tests.js | 223 -- mojo/edk/js/tests/run_js_unittests.cc | 61 - mojo/edk/js/threading.cc | 49 - mojo/edk/js/threading.h | 28 - mojo/edk/js/waiting_callback.cc | 95 - mojo/edk/js/waiting_callback.h | 67 - mojo/edk/system/BUILD.gn | 205 -- mojo/edk/system/broker.h | 52 - mojo/edk/system/broker_host.cc | 153 - mojo/edk/system/broker_host.h | 64 - mojo/edk/system/broker_posix.cc | 125 - mojo/edk/system/channel_posix.cc | 572 --- mojo/edk/system/channel_win.cc | 360 -- mojo/edk/system/configuration.cc | 25 - mojo/edk/system/core.cc | 1019 ------ mojo/edk/system/core.h | 297 -- mojo/edk/system/core_unittest.cc | 971 ----- mojo/edk/system/handle_signals_state.h | 13 - mojo/edk/system/mapping_table.cc | 48 - mojo/edk/system/mapping_table.h | 57 - mojo/edk/system/message_for_transit.cc | 136 - mojo/edk/system/message_for_transit.h | 115 - mojo/edk/system/node_controller.cc | 1475 -------- mojo/edk/system/ports/event.cc | 46 - mojo/edk/system/ports/event.h | 111 - mojo/edk/system/ports/message.cc | 100 - mojo/edk/system/ports/message.h | 93 - mojo/edk/system/ports/node.cc | 1385 ------- mojo/edk/system/ports/node_delegate.h | 48 - mojo/edk/system/ports/port.h | 60 - mojo/edk/system/ports/port_ref.cc | 36 - mojo/edk/system/ports_message.cc | 62 - mojo/edk/system/ports_message.h | 69 - mojo/edk/system/shared_buffer_dispatcher.cc | 339 -- mojo/edk/system/signals_unittest.cc | 76 - mojo/edk/system/watcher_unittest.cc | 1637 --------- .../test/multiprocess_test_helper_unittest.cc | 165 - mojo/edk/test/test_utils.h | 55 - mojo/edk/test/test_utils_posix.cc | 94 - mojo/edk/test/test_utils_win.cc | 115 - mojo/public/BUILD.gn | 7 +- mojo/public/DEPS | 11 - mojo/public/c/system/BUILD.gn | 35 +- mojo/public/c/system/README.md | 507 +-- mojo/public/c/system/buffer.h | 210 +- mojo/public/c/system/core.h | 4 +- mojo/public/c/system/data_pipe.h | 325 +- mojo/public/c/system/functions.h | 33 +- mojo/public/c/system/invitation.h | 456 +++ mojo/public/c/system/macros.h | 9 + mojo/public/c/system/message_pipe.h | 650 +++- mojo/public/c/system/platform_handle.h | 277 +- mojo/public/c/system/quota.h | 126 + mojo/public/c/system/set_thunks_for_app.cc | 20 - mojo/public/c/system/tests/BUILD.gn | 4 +- ...{core_unittest.cc => core_api_unittest.cc} | 139 +- mojo/public/c/system/tests/core_perftest.cc | 46 +- .../c/system/tests/core_unittest_pure_c.c | 23 +- mojo/public/c/system/thunks.cc | 533 ++- mojo/public/c/system/thunks.h | 242 +- mojo/public/c/system/trap.h | 322 ++ mojo/public/c/system/types.h | 76 +- mojo/public/c/system/watcher.h | 184 - mojo/public/cpp/base/BUILD.gn | 85 + mojo/public/cpp/base/README.md | 5 + mojo/public/cpp/base/big_buffer.cc | 155 + mojo/public/cpp/base/big_buffer.h | 179 + mojo/public/cpp/base/big_buffer.typemap | 12 + .../cpp/base/big_buffer_mojom_traits.cc | 146 + .../public/cpp/base/big_buffer_mojom_traits.h | 64 + mojo/public/cpp/base/big_buffer_unittest.cc | 69 + mojo/public/cpp/base/big_string.typemap | 17 + .../cpp/base/big_string_mojom_traits.cc | 33 + .../public/cpp/base/big_string_mojom_traits.h | 27 + mojo/public/cpp/base/big_string_unittest.cc | 43 + mojo/public/cpp/base/file.typemap | 14 + mojo/public/cpp/base/file_error.typemap | 12 + .../public/cpp/base/file_error_mojom_traits.h | 120 + mojo/public/cpp/base/file_info.typemap | 13 + .../public/cpp/base/file_info_mojom_traits.cc | 28 + mojo/public/cpp/base/file_info_mojom_traits.h | 49 + mojo/public/cpp/base/file_mojom_traits.cc | 30 + mojo/public/cpp/base/file_mojom_traits.h | 28 + mojo/public/cpp/base/file_path.typemap | 12 + .../public/cpp/base/file_path_mojom_traits.cc | 28 + mojo/public/cpp/base/file_path_mojom_traits.h | 38 + mojo/public/cpp/base/file_path_unittest.cc | 24 + mojo/public/cpp/base/file_unittest.cc | 73 + mojo/public/cpp/base/logfont_win.typemap | 18 + .../cpp/base/logfont_win_mojom_traits.cc | 39 + .../cpp/base/logfont_win_mojom_traits.h | 30 + ...y_allocator_dump_cross_process_uid.typemap | 13 + ...tor_dump_cross_process_uid_mojom_traits.cc | 22 + ...ator_dump_cross_process_uid_mojom_traits.h | 29 + ...locator_dump_cross_process_uid_unittest.cc | 42 + mojo/public/cpp/base/process_id.typemap | 14 + .../cpp/base/process_id_mojom_traits.cc | 16 + .../public/cpp/base/process_id_mojom_traits.h | 27 + mojo/public/cpp/base/process_id_unittest.cc | 23 + mojo/public/cpp/base/read_only_buffer.typemap | 13 + .../cpp/base/read_only_buffer_mojom_traits.cc | 23 + .../cpp/base/read_only_buffer_mojom_traits.h | 26 + .../cpp/base/read_only_buffer_unittest.cc | 37 + .../cpp/base/ref_counted_memory.typemap | 17 + .../base/ref_counted_memory_mojom_traits.cc | 46 + .../base/ref_counted_memory_mojom_traits.h | 34 + .../cpp/base/ref_counted_memory_unittest.cc | 40 + mojo/public/cpp/base/shared_memory.typemap | 24 + .../cpp/base/shared_memory_mojom_traits.cc | 105 + .../cpp/base/shared_memory_mojom_traits.h | 56 + .../public/cpp/base/shared_memory_unittest.cc | 53 + mojo/public/cpp/base/string16.typemap | 19 + mojo/public/cpp/base/string16_mojom_traits.cc | 44 + mojo/public/cpp/base/string16_mojom_traits.h | 51 + mojo/public/cpp/base/string16_unittest.cc | 60 + mojo/public/cpp/base/text_direction.typemap | 15 + .../cpp/base/text_direction_mojom_traits.cc | 43 + .../cpp/base/text_direction_mojom_traits.h | 25 + .../cpp/base/text_direction_unittest.cc | 31 + mojo/public/cpp/base/thread_priority.typemap | 13 + .../cpp/base/thread_priority_mojom_traits.cc | 48 + .../cpp/base/thread_priority_mojom_traits.h | 24 + .../cpp/base/thread_priority_unittest.cc | 31 + mojo/public/cpp/base/time.typemap | 17 + mojo/public/cpp/base/time_mojom_traits.cc | 49 + mojo/public/cpp/base/time_mojom_traits.h | 42 + mojo/public/cpp/base/time_unittest.cc | 38 + mojo/public/cpp/base/typemaps.gni | 24 + .../public/cpp/base/unguessable_token.typemap | 13 + .../base/unguessable_token_mojom_traits.cc | 25 + .../cpp/base/unguessable_token_mojom_traits.h | 37 + .../cpp/base/unguessable_token_unittest.cc | 23 + mojo/public/cpp/base/values.typemap | 16 + mojo/public/cpp/base/values_mojom_traits.cc | 94 + mojo/public/cpp/base/values_mojom_traits.h | 130 + mojo/public/cpp/base/values_unittest.cc | 160 + mojo/public/cpp/bindings/BUILD.gn | 208 +- mojo/public/cpp/bindings/DEPS | 3 - mojo/public/cpp/bindings/README.md | 577 ++- mojo/public/cpp/bindings/array_traits.h | 10 +- .../public/cpp/bindings/array_traits_carray.h | 76 - mojo/public/cpp/bindings/array_traits_span.h | 47 + .../cpp/bindings/array_traits_wtf_vector.h | 31 +- mojo/public/cpp/bindings/associated_binding.h | 66 +- mojo/public/cpp/bindings/associated_group.h | 4 +- .../bindings/associated_group_controller.h | 16 +- .../cpp/bindings/associated_interface_ptr.h | 83 +- .../bindings/associated_interface_ptr_info.h | 2 + .../bindings/associated_interface_request.h | 22 +- mojo/public/cpp/bindings/binding.h | 133 +- mojo/public/cpp/bindings/binding_set.h | 120 +- mojo/public/cpp/bindings/callback_helpers.h | 124 + mojo/public/cpp/bindings/clone_traits.h | 8 +- .../cpp/bindings/connection_error_callback.h | 11 +- mojo/public/cpp/bindings/connector.h | 109 +- mojo/public/cpp/bindings/enum_traits.h | 8 +- .../cpp/bindings/{lib => }/equals_traits.h | 30 +- mojo/public/cpp/bindings/filter_chain.h | 5 +- .../cpp/bindings/interface_endpoint_client.h | 34 +- .../bindings/interface_endpoint_controller.h | 4 +- mojo/public/cpp/bindings/interface_id.h | 4 + mojo/public/cpp/bindings/interface_ptr.h | 85 +- mojo/public/cpp/bindings/interface_ptr_info.h | 7 +- mojo/public/cpp/bindings/interface_ptr_set.h | 63 +- mojo/public/cpp/bindings/interface_request.h | 50 +- mojo/public/cpp/bindings/lib/array_internal.h | 57 +- .../cpp/bindings/lib/array_serialization.h | 156 +- .../cpp/bindings/lib/associated_binding.cc | 16 +- .../bindings/lib/associated_interface_ptr.cc | 8 +- .../lib/associated_interface_ptr_state.cc | 81 + .../lib/associated_interface_ptr_state.h | 143 +- mojo/public/cpp/bindings/lib/binding_state.cc | 32 +- mojo/public/cpp/bindings/lib/binding_state.h | 28 +- .../cpp/bindings/lib/bindings_internal.h | 25 +- mojo/public/cpp/bindings/lib/buffer.cc | 136 + mojo/public/cpp/bindings/lib/buffer.h | 129 +- mojo/public/cpp/bindings/lib/connector.cc | 173 +- .../bindings/lib/control_message_handler.cc | 22 +- .../bindings/lib/control_message_handler.h | 2 +- .../cpp/bindings/lib/control_message_proxy.cc | 43 +- mojo/public/cpp/bindings/lib/fixed_buffer.cc | 16 +- mojo/public/cpp/bindings/lib/fixed_buffer.h | 15 +- .../cpp/bindings/lib/handle_serialization.h | 35 + .../bindings/lib/interface_endpoint_client.cc | 91 +- .../cpp/bindings/lib/interface_ptr_state.cc | 94 + .../cpp/bindings/lib/interface_ptr_state.h | 214 +- ...ialization.h => interface_serialization.h} | 116 +- .../cpp/bindings/lib/map_data_internal.h | 34 +- .../cpp/bindings/lib/map_serialization.h | 63 +- mojo/public/cpp/bindings/lib/may_auto_lock.h | 5 +- mojo/public/cpp/bindings/lib/message.cc | 462 ++- .../public/cpp/bindings/lib/message_buffer.cc | 52 - mojo/public/cpp/bindings/lib/message_buffer.h | 43 - .../cpp/bindings/lib/message_builder.cc | 69 - .../public/cpp/bindings/lib/message_builder.h | 45 - .../public/cpp/bindings/lib/message_dumper.cc | 96 + .../bindings/lib/message_header_validator.cc | 11 +- .../cpp/bindings/lib/message_internal.cc | 45 + .../cpp/bindings/lib/message_internal.h | 14 +- .../cpp/bindings/lib/multiplex_router.cc | 346 +- .../cpp/bindings/lib/multiplex_router.h | 100 +- mojo/public/cpp/bindings/lib/native_struct.cc | 34 - .../cpp/bindings/lib/native_struct_data.cc | 22 - .../cpp/bindings/lib/native_struct_data.h | 38 - .../lib/native_struct_serialization.cc | 122 +- .../lib/native_struct_serialization.h | 109 +- .../lib/pipe_control_message_handler.cc | 1 - .../lib/pipe_control_message_proxy.cc | 19 +- .../lib/scoped_interface_endpoint_handle.cc | 25 +- .../lib/sequence_local_sync_event_watcher.cc | 286 ++ mojo/public/cpp/bindings/lib/serialization.h | 140 +- .../cpp/bindings/lib/serialization_context.cc | 78 +- .../cpp/bindings/lib/serialization_context.h | 91 +- .../cpp/bindings/lib/serialization_forward.h | 38 +- .../cpp/bindings/lib/serialization_util.h | 25 +- .../cpp/bindings/lib/string_serialization.h | 32 +- .../bindings/lib/string_traits_string16.cc | 42 - .../cpp/bindings/lib/string_traits_wtf.cc | 13 +- .../bindings/lib/sync_call_restrictions.cc | 86 +- .../cpp/bindings/lib/sync_event_watcher.cc | 32 +- .../cpp/bindings/lib/sync_handle_registry.cc | 151 +- .../cpp/bindings/lib/sync_handle_watcher.cc | 6 +- .../cpp/bindings/lib/task_runner_helper.cc | 24 + .../cpp/bindings/lib/task_runner_helper.h | 28 + mojo/public/cpp/bindings/lib/template_util.h | 5 + mojo/public/cpp/bindings/lib/union_accessor.h | 33 - .../lib/unserialized_message_context.cc | 24 + .../lib/unserialized_message_context.h | 63 + .../cpp/bindings/lib/validation_context.h | 4 +- .../cpp/bindings/lib/validation_errors.h | 34 +- .../cpp/bindings/lib/validation_util.cc | 45 +- .../public/cpp/bindings/lib/validation_util.h | 109 +- .../cpp/bindings/lib/wtf_clone_equals_util.h | 24 +- mojo/public/cpp/bindings/lib/wtf_hash_util.h | 36 +- mojo/public/cpp/bindings/map.h | 19 +- mojo/public/cpp/bindings/map_traits.h | 10 +- .../public/cpp/bindings/map_traits_flat_map.h | 56 + mojo/public/cpp/bindings/map_traits_stl.h | 30 +- .../cpp/bindings/map_traits_wtf_hash_map.h | 4 +- mojo/public/cpp/bindings/message.h | 179 +- mojo/public/cpp/bindings/message_dumper.h | 43 + .../cpp/bindings/message_header_validator.h | 6 +- mojo/public/cpp/bindings/mojo_buildflags.h | 6 + mojo/public/cpp/bindings/native_struct.h | 51 - .../cpp/bindings/native_struct_data_view.h | 36 - .../bindings/pipe_control_message_handler.h | 2 +- .../cpp/bindings/pipe_control_message_proxy.h | 4 +- .../scoped_interface_endpoint_handle.h | 12 +- .../sequence_local_sync_event_watcher.h | 69 + mojo/public/cpp/bindings/string_traits.h | 8 +- .../cpp/bindings/string_traits_string16.h | 37 - mojo/public/cpp/bindings/string_traits_wtf.h | 4 +- .../cpp/bindings/strong_associated_binding.h | 30 +- .../bindings/strong_associated_binding_set.h | 25 + mojo/public/cpp/bindings/strong_binding.h | 37 +- mojo/public/cpp/bindings/strong_binding_set.h | 12 +- mojo/public/cpp/bindings/struct_ptr.h | 11 +- mojo/public/cpp/bindings/struct_traits.h | 32 +- .../cpp/bindings/sync_call_restrictions.h | 87 +- mojo/public/cpp/bindings/sync_event_watcher.h | 21 +- .../cpp/bindings/sync_handle_registry.h | 44 +- .../public/cpp/bindings/sync_handle_watcher.h | 16 +- mojo/public/cpp/bindings/tests/BUILD.gn | 18 +- .../tests/associated_interface_unittest.cc | 118 +- .../tests/bind_task_runner_unittest.cc | 15 +- .../bindings/tests/binding_set_unittest.cc | 293 +- .../cpp/bindings/tests/binding_unittest.cc | 139 +- .../cpp/bindings/tests/bindings_perftest.cc | 14 +- .../cpp/bindings/tests/bindings_test_base.cc | 40 + .../cpp/bindings/tests/bindings_test_base.h | 51 + .../cpp/bindings/tests/buffer_unittest.cc | 68 +- .../tests/callback_helpers_unittest.cc | 202 + .../cpp/bindings/tests/connector_unittest.cc | 117 +- .../cpp/bindings/tests/constant_unittest.cc | 2 +- .../cpp/bindings/tests/data_view_unittest.cc | 45 +- .../public/cpp/bindings/tests/e2e_perftest.cc | 32 +- .../bindings/tests/handle_passing_unittest.cc | 78 +- .../cpp/bindings/tests/hash_unittest.cc | 8 - .../bindings/tests/interface_ptr_unittest.cc | 199 +- .../tests/lazy_serialization_unittest.cc | 166 + .../public/cpp/bindings/tests/map_unittest.cc | 6 +- .../public/cpp/bindings/tests/message_queue.h | 5 +- .../tests/multiplex_router_unittest.cc | 35 +- .../bindings/tests/native_struct_unittest.cc | 98 + .../cpp/bindings/tests/pickle_unittest.cc | 32 +- .../cpp/bindings/tests/pickled_types_blink.cc | 10 - .../cpp/bindings/tests/pickled_types_blink.h | 2 - .../bindings/tests/pickled_types_chromium.cc | 10 - .../bindings/tests/pickled_types_chromium.h | 2 - .../tests/report_bad_message_unittest.cc | 39 +- .../tests/request_response_unittest.cc | 17 +- .../cpp/bindings/tests/router_test_util.cc | 21 +- .../cpp/bindings/tests/router_test_util.h | 2 +- .../bindings/tests/sample_service_unittest.cc | 17 +- .../tests/serialization_warning_unittest.cc | 30 +- .../bindings/tests/struct_traits_unittest.cc | 26 +- .../cpp/bindings/tests/struct_unittest.cc | 157 +- .../bindings/tests/struct_with_traits.typemap | 3 + .../bindings/tests/struct_with_traits_impl.cc | 22 +- .../bindings/tests/struct_with_traits_impl.h | 48 + .../tests/struct_with_traits_impl_traits.cc | 30 +- .../tests/struct_with_traits_impl_traits.h | 57 +- .../tests/sync_handle_registry_unittest.cc | 258 ++ .../bindings/tests/sync_method_unittest.cc | 541 ++- .../bindings/tests/test_helpers_unittest.cc | 128 + .../cpp/bindings/tests/test_native_types.cc | 99 + .../cpp/bindings/tests/test_native_types.h | 89 + .../tests/test_native_types_chromium.typemap | 8 +- .../cpp/bindings/tests/union_unittest.cc | 796 ++-- .../cpp/bindings/tests/validation_unittest.cc | 28 +- .../cpp/bindings/tests/variant_test_util.h | 4 +- .../bindings/tests/versioning_test_service.cc | 25 +- .../cpp/bindings/tests/wtf_hash_unittest.cc | 22 +- .../cpp/bindings/tests/wtf_types_unittest.cc | 100 +- .../cpp/bindings/thread_safe_interface_ptr.h | 96 +- mojo/public/cpp/bindings/union_traits.h | 8 +- .../cpp/bindings/unique_ptr_impl_ref_traits.h | 4 +- mojo/public/cpp/platform/BUILD.gn | 50 + mojo/public/cpp/platform/README.md | 75 + .../cpp/platform/named_platform_channel.cc | 59 + .../cpp/platform/named_platform_channel.h | 122 + .../named_platform_channel_fuchsia.cc | 26 + .../platform/named_platform_channel_posix.cc | 151 + .../platform/named_platform_channel_win.cc | 110 + mojo/public/cpp/platform/platform_channel.cc | 282 ++ mojo/public/cpp/platform/platform_channel.h | 117 + .../cpp/platform/platform_channel_endpoint.cc | 30 + .../cpp/platform/platform_channel_endpoint.h | 45 + .../platform_channel_server_endpoint.cc | 31 + .../platform_channel_server_endpoint.h | 46 + mojo/public/cpp/platform/platform_handle.cc | 252 ++ mojo/public/cpp/platform/platform_handle.h | 189 + .../public/cpp/platform/socket_utils_posix.cc | 194 + mojo/public/cpp/platform/socket_utils_posix.h | 80 + mojo/public/cpp/platform/tests/BUILD.gn | 19 + .../tests/platform_handle_unittest.cc | 262 ++ mojo/public/cpp/system/BUILD.gn | 22 +- mojo/public/cpp/system/README.md | 224 +- mojo/public/cpp/system/buffer.cc | 20 +- mojo/public/cpp/system/buffer.h | 8 +- mojo/public/cpp/system/data_pipe.h | 118 +- .../cpp/system}/data_pipe_drainer.cc | 14 +- .../cpp/system}/data_pipe_drainer.h | 12 +- .../cpp/system}/data_pipe_utils.cc | 33 +- .../cpp/system}/data_pipe_utils.h | 22 +- .../cpp/system/file_data_pipe_producer.cc | 289 ++ .../cpp/system/file_data_pipe_producer.h | 114 + mojo/public/cpp/system/handle.h | 20 +- .../cpp/system/handle_signal_tracker.cc | 70 + .../public/cpp/system/handle_signal_tracker.h | 69 + mojo/public/cpp/system/handle_signals_state.h | 39 +- mojo/public/cpp/system/invitation.cc | 286 ++ mojo/public/cpp/system/invitation.h | 186 + mojo/public/cpp/system/isolated_connection.cc | 41 + mojo/public/cpp/system/isolated_connection.h | 60 + mojo/public/cpp/system/message.cc | 13 - mojo/public/cpp/system/message.h | 46 +- mojo/public/cpp/system/message_pipe.cc | 88 + mojo/public/cpp/system/message_pipe.h | 79 +- mojo/public/cpp/system/platform_handle.cc | 343 +- mojo/public/cpp/system/platform_handle.h | 120 +- .../cpp/system/scope_to_message_pipe.cc | 46 + .../public/cpp/system/scope_to_message_pipe.h | 65 + mojo/public/cpp/system/simple_watcher.cc | 160 +- mojo/public/cpp/system/simple_watcher.h | 90 +- .../cpp/system/string_data_pipe_producer.cc | 138 + .../cpp/system/string_data_pipe_producer.h | 90 + mojo/public/cpp/system/tests/BUILD.gn | 12 + mojo/public/cpp/system/tests/core_unittest.cc | 157 +- .../tests/data_pipe_drainer_unittest.cc | 62 + .../tests/file_data_pipe_producer_unittest.cc | 338 ++ .../tests/handle_signal_tracker_unittest.cc | 116 + .../tests/handle_signals_state_unittest.cc | 8 +- .../cpp/system/tests/invitation_unittest.cc | 318 ++ .../tests/scope_to_message_pipe_unittest.cc | 69 + .../system/tests/simple_watcher_unittest.cc | 61 +- .../string_data_pipe_producer_unittest.cc | 208 ++ .../cpp/system/tests/wait_set_unittest.cc | 17 +- mojo/public/cpp/system/tests/wait_unittest.cc | 24 +- .../public/cpp/system/{watcher.cc => trap.cc} | 10 +- mojo/public/cpp/system/trap.h | 36 + mojo/public/cpp/system/wait.cc | 98 +- mojo/public/cpp/system/wait.h | 28 +- mojo/public/cpp/system/wait_set.cc | 106 +- mojo/public/cpp/system/wait_set.h | 5 +- mojo/public/cpp/system/watcher.h | 37 - mojo/public/cpp/test_support/BUILD.gn | 1 + .../public/cpp/test_support/lib/test_utils.cc | 57 +- mojo/public/cpp/test_support/test_utils.h | 14 + mojo/public/interfaces/bindings/BUILD.gn | 29 +- .../interfaces/bindings/native_struct.mojom | 26 + .../interface_control_messages.mojom | 67 - .../new_bindings/pipe_control_messages.mojom | 46 - .../public/interfaces/bindings/tests/BUILD.gn | 66 +- .../interfaces/bindings/tests/echo.mojom | 2 +- .../tests/{ => echo_import}/echo_import.mojom | 0 .../bindings/tests/ping_service.mojom | 7 + .../bindings/tests/sample_factory.mojom | 2 +- .../bindings/tests/sample_import.mojom | 5 + .../bindings/tests/sample_import2.mojom | 2 +- .../bindings/tests/sample_service.mojom | 4 +- .../bindings/tests/struct_with_traits.mojom | 29 + .../tests/test_associated_interfaces.mojom | 15 + .../bindings/tests/test_name_generator.mojom | 27 + .../bindings/tests/test_native_types.mojom | 12 + .../bindings/tests/test_structs.mojom | 4 +- .../bindings/tests/test_unions.mojom | 10 + .../bindings/tests/test_wtf_types.mojom | 6 + mojo/public/java/BUILD.gn | 14 + .../org/chromium/mojo_base/BigBufferUtil.java | 55 + mojo/public/java/bindings/README.md | 4 +- .../mojo/bindings/AutoCloseableRouter.java | 13 +- .../org/chromium/mojo/bindings/Connector.java | 27 +- .../mojo/bindings/ExecutorFactory.java | 2 +- .../org/chromium/mojo/bindings/Message.java | 7 +- .../chromium/mojo/bindings/RouterImpl.java | 41 +- .../src/org/chromium/mojo/bindings/Union.java | 13 + mojo/{android => public/java/system}/BUILD.gn | 94 +- mojo/public/java/system/README.md | 4 +- .../java}/system/base_run_loop.cc | 51 +- .../java}/system/core_impl.cc | 221 +- .../system}/javatests/AndroidManifest.xml | 5 +- .../java/system}/javatests/apk/.empty | 0 .../java/system/javatests/init_library.cc | 17 + .../java/system/javatests/mojo_test_rule.cc} | 37 +- .../src/org/chromium/mojo/HandleMock.java | 20 +- .../src/org/chromium/mojo/MojoTestRule.java | 81 + .../src/org/chromium/mojo/TestUtils.java | 2 - .../mojo/bindings/BindingsHelperTest.java | 60 + .../chromium/mojo/bindings/BindingsTest.java | 89 +- .../mojo/bindings/BindingsTestUtils.java | 19 +- .../mojo/bindings/BindingsVersioningTest.java | 70 +- .../chromium/mojo/bindings/CallbacksTest.java | 21 +- .../chromium/mojo/bindings/ConnectorTest.java | 67 +- .../mojo/bindings/ExecutorFactoryTest.java | 45 +- .../bindings/InterfaceControlMessageTest.java | 57 +- .../mojo/bindings/InterfacesTest.java | 76 +- .../mojo/bindings/MessageHeaderTest.java | 41 +- .../mojo/bindings/NameGeneratorTest.java | 80 + .../bindings/ReadAndDispatchMessageTest.java | 54 +- .../chromium/mojo/bindings/RouterTest.java | 70 +- .../mojo/bindings/SerializationTest.java | 47 +- .../mojo/bindings/ValidationTest.java | 62 +- .../mojo/bindings/ValidationTestUtil.java | 1 - .../mojo/bindings/ValidationTestUtilTest.java | 43 +- .../IntegrationTestInterfaceTestHelper.java | 4 +- .../mojo/system/impl/CoreImplTest.java | 200 +- .../mojo/system/impl/WatcherImplTest.java | 112 +- .../system}/javatests/validation_test_util.cc | 18 +- .../chromium/mojo/system/InvalidHandle.java | 5 +- .../mojo/system/MessagePipeHandle.java | 70 +- .../mojo/system/impl/BaseRunLoop.java | 4 +- .../chromium/mojo/system/impl/CoreImpl.java | 45 +- .../impl/DataPipeConsumerHandleImpl.java | 2 - .../impl/DataPipeProducerHandleImpl.java | 2 - .../chromium/mojo/system/impl/HandleBase.java | 5 +- .../system/impl/MessagePipeHandleImpl.java | 9 +- .../system/impl/SharedBufferHandleImpl.java | 2 - .../mojo/system/impl/UntypedHandleImpl.java | 2 - .../mojo/system/impl/WatcherImpl.java | 0 .../java}/system/watcher_impl.cc | 45 +- mojo/public/js/BUILD.gn | 91 +- mojo/public/js/README.md | 285 +- mojo/public/js/{new_bindings => }/base.js | 51 +- mojo/public/js/bindings.js | 352 +- mojo/public/js/buffer.js | 156 - mojo/public/js/codec.js | 926 ----- mojo/public/js/connector.js | 113 - mojo/public/js/constants.cc | 33 - mojo/public/js/constants.h | 30 - mojo/public/js/core.js | 304 -- mojo/public/js/interface_types.js | 63 +- .../public/js/{new_bindings => lib}/buffer.js | 0 mojo/public/js/{new_bindings => lib}/codec.js | 290 +- .../js/{new_bindings => lib}/connector.js | 109 +- mojo/public/js/lib/control_message_handler.js | 90 +- mojo/public/js/lib/control_message_proxy.js | 67 +- .../js/lib/interface_endpoint_client.js | 75 +- .../js/lib/interface_endpoint_handle.js | 62 +- .../js/lib/pipe_control_message_handler.js | 39 +- .../js/lib/pipe_control_message_proxy.js | 36 +- mojo/public/js/{ => lib}/router.js | 208 +- .../js/{new_bindings => lib}/unicode.js | 16 +- .../js/{new_bindings => lib}/validator.js | 199 +- mojo/public/js/mojo_bindings_resources.grd | 21 + mojo/public/js/new_bindings/bindings.js | 275 -- .../public/js/new_bindings/interface_types.js | 46 - .../lib/control_message_handler.js | 106 - .../new_bindings/lib/control_message_proxy.js | 97 - mojo/public/js/new_bindings/router.js | 190 - mojo/public/js/support.js | 53 - mojo/public/js/tests/core_unittest.js | 223 -- .../js/tests/validation_test_input_parser.js | 299 -- mojo/public/js/tests/validation_unittest.js | 334 -- mojo/public/js/threading.js | 21 - mojo/public/js/unicode.js | 51 - mojo/public/js/validator.js | 560 --- mojo/public/mojom/base/BUILD.gn | 45 + mojo/public/mojom/base/big_buffer.mojom | 18 + mojo/public/mojom/base/big_string.mojom | 19 + mojo/{common => public/mojom/base}/file.mojom | 3 +- mojo/public/mojom/base/file_error.mojom | 27 + mojo/public/mojom/base/file_info.mojom | 17 + mojo/public/mojom/base/file_path.mojom | 24 + mojo/public/mojom/base/logfont_win.mojom | 11 + ...ory_allocator_dump_cross_process_uid.mojom | 10 + mojo/public/mojom/base/process_id.mojom | 12 + mojo/public/mojom/base/read_only_buffer.mojom | 12 + .../mojom/base/ref_counted_memory.mojom | 14 + mojo/public/mojom/base/shared_memory.mojom | 25 + mojo/public/mojom/base/string16.mojom | 27 + .../mojom/base}/text_direction.mojom | 12 +- mojo/public/mojom/base/thread_priority.mojom | 17 + mojo/{common => public/mojom/base}/time.mojom | 2 +- .../mojom/base}/unguessable_token.mojom | 2 +- mojo/public/mojom/base/values.mojom | 38 + mojo/public/tools/bindings/BUILD.gn | 11 +- mojo/public/tools/bindings/README.md | 81 +- .../bindings/blink_bindings_configuration.gni | 17 +- .../chromium_bindings_configuration.gni | 70 +- .../tools/bindings/gen_data_files_list.py | 48 + .../tools/bindings/generate_type_mappings.py | 22 +- .../tools/bindings/generators/__init__.py | 0 .../generators/cpp_templates/enum_macros.tmpl | 22 +- .../cpp_templates/interface_declaration.tmpl | 26 + .../cpp_templates/interface_definition.tmpl | 421 ++- .../cpp_templates/interface_macros.tmpl | 168 +- .../interface_proxy_declaration.tmpl | 4 +- ...terface_request_validator_declaration.tmpl | 2 +- ...erface_response_validator_declaration.tmpl | 2 +- .../interface_stub_declaration.tmpl | 2 +- .../module-shared-internal.h.tmpl | 20 +- .../module-shared-message-ids.h.tmpl | 35 + .../cpp_templates/module-shared.cc.tmpl | 1 - .../cpp_templates/module-shared.h.tmpl | 20 +- .../generators/cpp_templates/module.cc.tmpl | 9 +- .../generators/cpp_templates/module.h.tmpl | 55 +- .../struct_data_view_declaration.tmpl | 4 +- .../cpp_templates/struct_declaration.tmpl | 31 +- .../cpp_templates/struct_definition.tmpl | 4 +- .../cpp_templates/struct_macros.tmpl | 80 +- .../struct_serialization_declaration.tmpl | 32 +- .../struct_unserialized_message_context.tmpl | 33 + .../cpp_templates/union_declaration.tmpl | 39 +- .../cpp_templates/union_definition.tmpl | 2 +- .../union_serialization_declaration.tmpl | 69 +- .../union_traits_definition.tmpl | 28 +- .../cpp_templates/validation_macros.tmpl | 19 +- .../wrapper_class_declaration.tmpl | 54 +- .../wrapper_class_definition.tmpl | 2 +- .../wrapper_class_template_definition.tmpl | 4 +- .../wrapper_union_class_declaration.tmpl | 39 +- .../wrapper_union_class_definition.tmpl | 69 +- ...apper_union_class_template_definition.tmpl | 4 +- .../java_templates/data_types_definition.tmpl | 177 +- .../java_templates/header.java.tmpl | 3 +- .../java_templates/interface_definition.tmpl | 7 +- .../externs/interface_definition.tmpl | 34 + .../js_templates/externs/module.externs.tmpl | 37 + .../externs/struct_definition.tmpl | 8 + .../generators/js_templates/fuzzing.tmpl | 125 + .../js_templates/interface_definition.tmpl | 130 +- .../generators/js_templates/module.amd.tmpl | 54 +- .../js_templates/module_definition.tmpl | 9 +- .../js_templates/struct_definition.tmpl | 57 +- .../js_templates/union_definition.tmpl | 66 +- .../js_templates/validation_macros.tmpl | 30 +- .../generators/mojom_cpp_generator.py | 1120 +++--- .../generators/mojom_java_generator.py | 150 +- .../bindings/generators/mojom_js_generator.py | 783 ++-- mojo/public/tools/bindings/mojom.gni | 777 +++- .../bindings/mojom_bindings_generator.py | 353 +- .../mojom_bindings_generator_unittest.py | 30 + .../pylib/mojom/generate/generator.py | 217 +- .../mojom/generate/generator_unittest.py | 24 - .../bindings/pylib/mojom/generate/module.py | 352 +- .../bindings/pylib/mojom/generate/pack.py | 3 +- .../pylib/mojom/generate/template_expander.py | 23 +- .../pylib/mojom/generate/translate.py | 180 +- .../tools/bindings/pylib/mojom/parse/ast.py | 74 +- .../pylib/mojom/parse/conditional_features.py | 80 + .../bindings/pylib/mojom/parse/parser.py | 27 +- .../generate/generator_unittest.py | 12 +- .../parse/conditional_features_unittest.py | 232 ++ .../pylib/mojom_tests/parse/lexer_unittest.py | 5 +- .../mojom_tests/parse/parser_unittest.py | 66 +- mojo/public/tools/fuzzers/BUILD.gn | 67 + mojo/public/tools/fuzzers/fuzz.mojom | 78 + mojo/public/tools/fuzzers/fuzz_impl.cc | 45 + mojo/public/tools/fuzzers/fuzz_impl.h | 45 + .../fuzzers/message_corpus/message_0.mojomsg | Bin 0 -> 32 bytes .../fuzzers/message_corpus/message_1.mojomsg | Bin 0 -> 40 bytes .../fuzzers/message_corpus/message_10.mojomsg | Bin 0 -> 32 bytes .../fuzzers/message_corpus/message_11.mojomsg | Bin 0 -> 72 bytes .../fuzzers/message_corpus/message_2.mojomsg | Bin 0 -> 40 bytes .../fuzzers/message_corpus/message_3.mojomsg | Bin 0 -> 328 bytes .../fuzzers/message_corpus/message_4.mojomsg | Bin 0 -> 1736 bytes .../fuzzers/message_corpus/message_5.mojomsg | Bin 0 -> 1744 bytes .../fuzzers/message_corpus/message_6.mojomsg | Bin 0 -> 1744 bytes .../fuzzers/message_corpus/message_7.mojomsg | Bin 0 -> 1744 bytes .../fuzzers/message_corpus/message_8.mojomsg | Bin 0 -> 1744 bytes .../fuzzers/message_corpus/message_9.mojomsg | Bin 0 -> 80 bytes mojo/public/tools/fuzzers/mojo_fuzzer.proto | 7 + .../tools/fuzzers/mojo_fuzzer_message_dump.cc | 266 ++ .../fuzzers/mojo_parse_message_fuzzer.cc | 61 + .../07775ad8fdb79599024caefbe7889501dfee9e06 | 1 + .../2ce2f91669a46921ebf4e47679c86dd2bf5b1496 | 1 + .../32a65dcd84debde03d51f8b8ace2cdcc87461d34 | 1 + .../7cbf9144ec3980eb121eedc679ebc56a3ddd22a6 | 1 + .../9ccc6b5c0a61672816dc252194c3d722c18107bc | 1 + .../9e0a62bdd4b08cb777bee9449a22b3ad6702b106 | 1 + .../a74241101f97704b96c9ba11b4781651e236ad8f | 1 + .../be66c5d078fbf574388b7b1d25a29ff2d16df67e | 1 + .../e4be6bde72d04c5cda7d4939a80e5890c5c01374 | 1 + .../mojo_parse_message_proto_fuzzer.cc | 70 + soong/bindings_generator.go | 26 +- third_party/catapult/LICENSE | 27 - third_party/catapult/devil/PRESUBMIT.py | 81 - third_party/catapult/devil/README.md | 37 - .../catapult/devil/bin/generate_md_docs | 45 - .../catapult/devil/bin/run_py_devicetests | 32 - third_party/catapult/devil/bin/run_py_tests | 22 - third_party/catapult/devil/devil/__init__.py | 7 - .../catapult/devil/devil/android/__init__.py | 3 - .../devil/devil/android/apk_helper.py | 164 - .../devil/devil/android/apk_helper_test.py | 169 - .../catapult/devil/devil/android/app_ui.py | 243 -- .../devil/devil/android/app_ui_test.py | 191 - .../devil/devil/android/battery_utils.py | 699 ---- .../devil/devil/android/battery_utils_test.py | 694 ---- .../devil/devil/android/constants/__init__.py | 3 - .../devil/devil/android/constants/chrome.py | 57 - .../devil/android/constants/file_system.py | 5 - .../devil/devil/android/decorators.py | 176 - .../devil/devil/android/decorators_test.py | 332 -- .../devil/devil/android/device_blacklist.py | 80 - .../devil/android/device_blacklist_test.py | 38 - .../devil/devil/android/device_errors.py | 180 - .../devil/devil/android/device_errors_test.py | 72 - .../devil/devil/android/device_list.py | 52 - .../devil/devil/android/device_signal.py | 41 - .../devil/devil/android/device_temp_file.py | 63 - .../devil/devil/android/device_test_case.py | 54 - .../devil/devil/android/device_utils.py | 2640 ------------- .../devil/android/device_utils_devicetest.py | 229 -- .../devil/devil/android/device_utils_test.py | 2900 --------------- .../devil/devil/android/fastboot_utils.py | 256 -- .../devil/android/fastboot_utils_test.py | 375 -- .../devil/devil/android/flag_changer.py | 300 -- .../devil/android/flag_changer_devicetest.py | 88 - .../devil/devil/android/flag_changer_test.py | 135 - .../catapult/devil/devil/android/forwarder.py | 464 --- .../devil/devil/android/install_commands.py | 57 - .../devil/devil/android/logcat_monitor.py | 255 -- .../devil/android/logcat_monitor_test.py | 230 -- .../catapult/devil/devil/android/md5sum.py | 120 - .../devil/devil/android/md5sum_test.py | 237 -- .../devil/devil/android/perf/__init__.py | 3 - .../devil/devil/android/perf/cache_control.py | 15 - .../devil/devil/android/perf/perf_control.py | 210 -- .../android/perf/perf_control_devicetest.py | 38 - .../android/perf/surface_stats_collector.py | 186 - .../devil/android/perf/thermal_throttle.py | 135 - .../catapult/devil/devil/android/ports.py | 178 - .../devil/devil/android/sdk/__init__.py | 6 - .../catapult/devil/devil/android/sdk/aapt.py | 43 - .../sdk/adb_compatibility_devicetest.py | 230 -- .../devil/devil/android/sdk/adb_wrapper.py | 917 ----- .../android/sdk/adb_wrapper_devicetest.py | 118 - .../devil/android/sdk/adb_wrapper_test.py | 59 - .../devil/devil/android/sdk/build_tools.py | 51 - .../devil/devil/android/sdk/dexdump.py | 31 - .../devil/devil/android/sdk/fastboot.py | 98 - .../devil/android/sdk/gce_adb_wrapper.py | 154 - .../devil/devil/android/sdk/intent.py | 129 - .../devil/devil/android/sdk/keyevent.py | 63 - .../devil/devil/android/sdk/shared_prefs.py | 420 --- .../devil/android/sdk/shared_prefs_test.py | 171 - .../devil/devil/android/sdk/split_select.py | 63 - .../push_directory_contents.txt | 1 - .../devil/android/sdk/test/data/push_file.txt | 1 - .../devil/devil/android/sdk/version_codes.py | 20 - .../catapult/devil/devil/android/settings.py | 273 -- .../devil/devil/android/tools/__init__.py | 3 - .../devil/android/tools/adb_run_shell_cmd.py | 61 - .../devil/devil/android/tools/cpufreq.py | 87 - .../devil/android/tools/device_monitor.py | 231 -- .../android/tools/device_monitor_test.py | 168 - .../devil/android/tools/device_recovery.py | 208 -- .../devil/android/tools/device_status.py | 313 -- .../devil/devil/android/tools/flash_device.py | 70 - .../devil/devil/android/tools/keyboard.py | 129 - .../devil/android/tools/provision_devices.py | 637 ---- .../devil/devil/android/tools/screenshot.py | 59 - .../devil/android/tools/script_common.py | 29 - .../devil/android/tools/script_common_test.py | 58 - .../devil/android/tools/video_recorder.py | 175 - .../devil/android/tools/wait_for_devices.py | 50 - .../devil/android/valgrind_tools/__init__.py | 21 - .../devil/android/valgrind_tools/base_tool.py | 53 - .../catapult/devil/devil/base_error.py | 24 - .../devil/devil/constants/__init__.py | 3 - .../devil/devil/constants/exit_codes.py | 9 - .../devil/devil/devil_dependencies.json | 127 - third_party/catapult/devil/devil/devil_env.py | 194 - .../catapult/devil/devil/devil_env_test.py | 63 - .../catapult/devil/devil/utils/__init__.py | 23 - .../devil/utils/battor_device_mapping.py | 309 -- .../catapult/devil/devil/utils/cmd_helper.py | 394 -- .../devil/devil/utils/cmd_helper_test.py | 262 -- .../catapult/devil/devil/utils/file_utils.py | 31 - .../devil/devil/utils/find_usb_devices.py | 532 --- .../devil/utils/find_usb_devices_test.py | 379 -- .../catapult/devil/devil/utils/geometry.py | 75 - .../devil/devil/utils/geometry_test.py | 61 - .../catapult/devil/devil/utils/host_utils.py | 16 - .../devil/devil/utils/lazy/__init__.py | 5 - .../devil/devil/utils/lazy/weak_constant.py | 29 - .../catapult/devil/devil/utils/lsusb.py | 174 - .../catapult/devil/devil/utils/lsusb_test.py | 250 -- .../catapult/devil/devil/utils/markdown.py | 320 -- .../devil/devil/utils/markdown_test.py | 121 - .../catapult/devil/devil/utils/mock_calls.py | 180 - .../devil/devil/utils/mock_calls_test.py | 173 - .../devil/devil/utils/parallelizer.py | 238 -- .../devil/devil/utils/parallelizer_test.py | 162 - .../devil/devil/utils/reraiser_thread.py | 228 -- .../devil/utils/reraiser_thread_unittest.py | 117 - .../catapult/devil/devil/utils/reset_usb.py | 111 - .../devil/devil/utils/run_tests_helper.py | 44 - .../devil/devil/utils/signal_handler.py | 48 - .../utils/test/data/test_serial_map.json | 1 - .../devil/devil/utils/timeout_retry.py | 175 - .../devil/utils/timeout_retry_unittest.py | 79 - .../devil/devil/utils/update_mapping.py | 47 - .../catapult/devil/devil/utils/usb_hubs.py | 165 - .../devil/devil/utils/watchdog_timer.py | 47 - .../catapult/devil/devil/utils/zip_utils.py | 33 - .../catapult/devil/docs/adb_wrapper.md | 388 -- .../catapult/devil/docs/device_blacklist.md | 59 - .../catapult/devil/docs/device_utils.md | 1041 ------ third_party/catapult/devil/docs/markdown.md | 139 - .../devil/docs/persistent_device_list.md | 41 - third_party/catapult/devil/pylintrc | 68 - third_party/jinja2/AUTHORS | 1 + third_party/jinja2/Jinja2-2.10.tar.gz.md5 | 1 + third_party/jinja2/Jinja2-2.10.tar.gz.sha512 | 1 + third_party/jinja2/Jinja2-2.8.tar.gz.md5 | 1 - third_party/jinja2/Jinja2-2.8.tar.gz.sha512 | 1 - third_party/jinja2/README.chromium | 15 +- third_party/jinja2/__init__.py | 21 +- third_party/jinja2/_compat.py | 22 +- third_party/jinja2/_identifier.py | 2 + third_party/jinja2/_stringdefs.py | 132 - third_party/jinja2/asyncfilters.py | 146 + third_party/jinja2/asyncsupport.py | 256 ++ third_party/jinja2/bccache.py | 6 +- third_party/jinja2/compiler.py | 1135 +++--- third_party/jinja2/constants.py | 2 +- third_party/jinja2/debug.py | 42 +- third_party/jinja2/defaults.py | 19 +- third_party/jinja2/environment.py | 177 +- third_party/jinja2/exceptions.py | 2 +- third_party/jinja2/ext.py | 55 +- third_party/jinja2/filters.py | 416 ++- third_party/jinja2/get_jinja2.sh | 18 +- third_party/jinja2/idtracking.py | 286 ++ third_party/jinja2/jinja2.gni | 31 + third_party/jinja2/lexer.py | 57 +- third_party/jinja2/loaders.py | 6 +- third_party/jinja2/meta.py | 11 +- third_party/jinja2/nativetypes.py | 220 ++ third_party/jinja2/nodes.py | 184 +- third_party/jinja2/optimizer.py | 25 +- third_party/jinja2/parser.py | 142 +- third_party/jinja2/runtime.py | 288 +- third_party/jinja2/sandbox.py | 128 +- third_party/jinja2/tests.py | 52 +- third_party/jinja2/utils.py | 140 +- third_party/jinja2/visitor.py | 2 +- ui/gfx/geometry/insets_f.h | 5 + ui/gfx/geometry/mojo/BUILD.gn | 2 + ui/gfx/geometry/mojo/DEPS | 4 - ui/gfx/geometry/mojo/geometry.mojom | 12 + ui/gfx/geometry/mojo/geometry.typemap | 2 + ui/gfx/geometry/mojo/geometry_struct_traits.h | 13 + .../mojo/geometry_struct_traits_unittest.cc | 50 +- ui/gfx/geometry/point.h | 18 +- ui/gfx/geometry/rect.cc | 18 +- ui/gfx/geometry/rect.h | 39 +- ui/gfx/geometry/rect_f.cc | 52 +- ui/gfx/geometry/scroll_offset.cc | 15 + ui/gfx/geometry/scroll_offset.h | 129 + ui/gfx/geometry/size.cc | 6 +- ui/gfx/geometry/vector2d.cc | 10 +- ui/gfx/geometry/vector2d.h | 9 +- ui/gfx/geometry/vector2d_f.h | 6 +- ui/gfx/range/BUILD.gn | 4 +- ui/gfx/range/mojo/DEPS | 4 - .../mojo/range_struct_traits_unittest.cc | 15 +- ui/gfx/range/range.h | 5 +- 2075 files changed, 151713 insertions(+), 98964 deletions(-) delete mode 100644 base/DEPS delete mode 100644 base/PRESUBMIT.py delete mode 100644 base/allocator/README.md create mode 100644 base/allocator/buildflags.h delete mode 100644 base/allocator/features.h create mode 100644 base/android/base_jni_onload.h delete mode 100644 base/android/context_utils.cc delete mode 100644 base/android/context_utils.h create mode 100644 base/android/java/src/org/chromium/base/BuildConfig.java create mode 100644 base/android/java/src/org/chromium/base/DiscardableReferencePool.java create mode 100644 base/android/java/src/org/chromium/base/JavaExceptionReporter.java create mode 100644 base/android/java/src/org/chromium/base/StrictModeContext.java create mode 100644 base/android/java/src/org/chromium/base/Supplier.java create mode 100644 base/android/java/src/org/chromium/base/ThreadUtils.java create mode 100644 base/android/java/src/org/chromium/base/TimezoneUtils.java delete mode 100644 base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java create mode 100644 base/android/java_exception_reporter.cc create mode 100644 base/android/java_exception_reporter.h create mode 100644 base/android/javatests/src/org/chromium/base/AssertsTest.java create mode 100644 base/android/javatests/src/org/chromium/base/AsyncTaskTest.java create mode 100644 base/android/jni_array.cc create mode 100644 base/android/jni_array.h create mode 100644 base/android/jni_generator/AndroidManifest.xml create mode 100644 base/android/jni_generator/PRESUBMIT.py create mode 100644 base/android/jni_generator/jni_exception_list.gni create mode 100755 base/android/jni_generator/jni_registration_generator.py create mode 100644 base/android/jni_generator/sample_entry_point.cc create mode 100644 base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden create mode 100644 base/android/jni_generator/testNativesRegistrations.golden create mode 100644 base/android/jni_generator/testTracing.golden create mode 100644 base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java create mode 100644 base/android/library_loader/README.md create mode 100644 base/android/library_loader/anchor_functions.cc create mode 100644 base/android/library_loader/anchor_functions.h create mode 100644 base/android/library_loader/anchor_functions.lds create mode 100644 base/android/orderfile/BUILD.gn create mode 100644 base/android/orderfile/orderfile_instrumentation.cc create mode 100644 base/android/orderfile/orderfile_instrumentation.h create mode 100644 base/android/orderfile/orderfile_instrumentation_perftest.cc create mode 100644 base/android/proguard/disable_all_obfuscation.flags create mode 100644 base/android/proguard/disable_chromium_obfuscation.flags create mode 100644 base/android/proguard/enable_obfuscation.flags create mode 100644 base/android/scoped_hardware_buffer_handle.cc create mode 100644 base/android/scoped_hardware_buffer_handle.h create mode 100644 base/android/timezone_utils.cc create mode 100644 base/android/timezone_utils.h create mode 100644 base/barrier_closure.cc create mode 100644 base/barrier_closure.h create mode 100644 base/base64_decode_fuzzer.cc create mode 100644 base/base64_encode_fuzzer.cc create mode 100644 base/big_endian.cc create mode 100644 base/big_endian.h create mode 100644 base/big_endian_unittest.cc delete mode 100644 base/bind_helpers.cc create mode 100644 base/cfi_buildflags.h create mode 100644 base/component_export.h create mode 100644 base/component_export_unittest.cc create mode 100644 base/containers/README.md create mode 100644 base/containers/circular_deque.h create mode 100644 base/containers/circular_deque_unittest.cc create mode 100644 base/containers/flat_map.h create mode 100644 base/containers/flat_set.h create mode 100644 base/containers/flat_tree.h create mode 100644 base/containers/queue.h create mode 100644 base/containers/ring_buffer.h create mode 100644 base/containers/span.h create mode 100644 base/containers/span_unittest.cc create mode 100644 base/containers/span_unittest.nc create mode 100644 base/containers/stack.h create mode 100644 base/containers/vector_buffer.h create mode 100644 base/containers/vector_buffer_unittest.cc create mode 100644 base/debug/alias_unittest.cc create mode 100644 base/debug/crash_logging.cc create mode 100644 base/debug/crash_logging.h rename base/debug/{debugging_flags.h => debugging_buildflags.h} (58%) create mode 100644 base/debug/elf_reader_linux.cc create mode 100644 base/debug/elf_reader_linux.h create mode 100644 base/debug/elf_reader_linux_unittest.cc create mode 100644 base/export_template.h create mode 100644 base/files/file_enumerator_unittest.cc create mode 100644 base/files/platform_file.h create mode 100644 base/json/json_correctness_fuzzer.cc create mode 100644 base/json/json_reader_fuzzer.cc create mode 100644 base/json/string_escape_fuzzer.cc rename base/{lazy_instance.cc => lazy_instance_helpers.cc} (50%) create mode 100644 base/lazy_instance_helpers.h delete mode 100644 base/memory/manual_constructor.h create mode 100644 base/memory/platform_shared_memory_region.cc create mode 100644 base/memory/platform_shared_memory_region.h create mode 100644 base/memory/platform_shared_memory_region_mac.cc create mode 100644 base/memory/platform_shared_memory_region_posix.cc create mode 100644 base/memory/platform_shared_memory_region_unittest.cc create mode 100644 base/memory/protected_memory.cc create mode 100644 base/memory/protected_memory.h create mode 100644 base/memory/protected_memory_buildflags.h create mode 100644 base/memory/protected_memory_cfi.h create mode 100644 base/memory/protected_memory_posix.cc create mode 100644 base/memory/protected_memory_unittest.cc create mode 100644 base/memory/read_only_shared_memory_region.cc create mode 100644 base/memory/read_only_shared_memory_region.h create mode 100644 base/memory/ref_counted_delete_on_sequence.h create mode 100644 base/memory/scoped_refptr.h delete mode 100644 base/memory/scoped_vector.h delete mode 100644 base/memory/scoped_vector_unittest.cc create mode 100644 base/memory/shared_memory_handle.cc create mode 100644 base/memory/shared_memory_handle_android.cc create mode 100644 base/memory/shared_memory_handle_posix.cc create mode 100644 base/memory/shared_memory_mapping.cc create mode 100644 base/memory/shared_memory_mapping.h create mode 100644 base/memory/shared_memory_region_unittest.cc delete mode 100644 base/memory/singleton.cc create mode 100644 base/memory/unsafe_shared_memory_region.cc create mode 100644 base/memory/unsafe_shared_memory_region.h create mode 100644 base/memory/writable_shared_memory_region.cc create mode 100644 base/memory/writable_shared_memory_region.h create mode 100644 base/message_loop/message_loop_current.cc create mode 100644 base/message_loop/message_loop_current.h create mode 100644 base/message_loop/message_loop_io_posix_unittest.cc create mode 100644 base/message_loop/message_loop_perftest.cc create mode 100644 base/message_loop/message_loop_task_runner_perftest.cc delete mode 100644 base/message_loop/message_loop_test.cc delete mode 100644 base/message_loop/message_loop_test.h create mode 100644 base/message_loop/message_pump_for_io.h create mode 100644 base/message_loop/message_pump_for_ui.h create mode 100644 base/message_loop/watchable_io_message_pump_posix.cc create mode 100644 base/message_loop/watchable_io_message_pump_posix.h create mode 100644 base/metrics/dummy_histogram.cc create mode 100644 base/metrics/dummy_histogram.h create mode 100644 base/metrics/field_trial_params_unittest.nc create mode 100644 base/metrics/histogram_functions.cc create mode 100644 base/metrics/histogram_functions.h create mode 100644 base/metrics/histogram_samples_unittest.cc create mode 100644 base/metrics/persistent_histogram_storage.cc create mode 100644 base/metrics/persistent_histogram_storage.h create mode 100644 base/metrics/persistent_histogram_storage_unittest.cc create mode 100644 base/metrics/record_histogram_checker.h create mode 100644 base/metrics/single_sample_metrics.cc create mode 100644 base/metrics/single_sample_metrics.h create mode 100644 base/metrics/single_sample_metrics_unittest.cc create mode 100644 base/native_library.cc create mode 100644 base/no_destructor.h create mode 100644 base/no_destructor_unittest.cc create mode 100644 base/numerics/BUILD.gn create mode 100644 base/numerics/README.md create mode 100644 base/numerics/checked_math.h rename base/numerics/{safe_math_impl.h => checked_math_impl.h} (63%) create mode 100644 base/numerics/clamped_math.h create mode 100644 base/numerics/clamped_math_impl.h create mode 100644 base/numerics/math_constants.h create mode 100644 base/numerics/ranges.h create mode 100644 base/numerics/safe_conversions_arm_impl.h create mode 100644 base/numerics/safe_math_arm_impl.h create mode 100644 base/numerics/safe_math_clang_gcc_impl.h create mode 100644 base/numerics/safe_math_shared_impl.h delete mode 100644 base/numerics/safe_numerics_unittest.cc delete mode 100644 base/numerics/saturated_arithmetic.h delete mode 100644 base/numerics/saturated_arithmetic_arm.h create mode 100644 base/observer_list_threadsafe.cc create mode 100644 base/optional_unittest.nc rename base/posix/{unix_domain_socket_linux.cc => unix_domain_socket.cc} (72%) rename base/posix/{unix_domain_socket_linux.h => unix_domain_socket.h} (90%) rename base/posix/{unix_domain_socket_linux_unittest.cc => unix_domain_socket_unittest.cc} (70%) create mode 100644 base/process/internal_aix.cc create mode 100644 base/process/internal_aix.h create mode 100644 base/process/process_metrics_iocounters.h delete mode 100644 base/profiler/tracked_time.cc delete mode 100644 base/profiler/tracked_time.h delete mode 100644 base/profiler/tracked_time_unittest.cc create mode 100644 base/sampling_heap_profiler/benchmark-octane.js create mode 100644 base/sampling_heap_profiler/lock_free_address_hash_set.cc create mode 100644 base/sampling_heap_profiler/lock_free_address_hash_set.h create mode 100644 base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc create mode 100644 base/sampling_heap_profiler/sampling_heap_profiler.cc create mode 100644 base/sampling_heap_profiler/sampling_heap_profiler.h create mode 100644 base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc create mode 100644 base/scoped_native_library.cc create mode 100644 base/scoped_native_library.h create mode 100644 base/strings/char_traits.h create mode 100644 base/strings/char_traits_unittest.cc create mode 100644 base/strings/old_utf_string_conversions.cc create mode 100644 base/strings/old_utf_string_conversions.h create mode 100644 base/strings/strcat.cc create mode 100644 base/strings/strcat.h create mode 100644 base/strings/strcat_unittest.cc create mode 100644 base/strings/string16_unittest.nc create mode 100644 base/strings/string_number_conversions_fuzzer.cc create mode 100644 base/strings/string_piece_forward.h create mode 100644 base/strings/string_tokenizer_fuzzer.cc delete mode 100644 base/strings/string_util_win.h create mode 100644 base/strings/utf_string_conversions_fuzzer.cc create mode 100644 base/strings/utf_string_conversions_regression_fuzzer.cc delete mode 100644 base/synchronization/read_write_lock.h delete mode 100644 base/synchronization/read_write_lock_posix.cc create mode 100644 base/synchronization/synchronization_buildflags.h create mode 100644 base/synchronization/waitable_event_perftest.cc create mode 100644 base/task/README.md create mode 100644 base/task/sequence_manager/enqueue_order.cc create mode 100644 base/task/sequence_manager/enqueue_order.h create mode 100644 base/task/sequence_manager/graceful_queue_shutdown_helper.cc create mode 100644 base/task/sequence_manager/graceful_queue_shutdown_helper.h create mode 100644 base/task/sequence_manager/intrusive_heap.h create mode 100644 base/task/sequence_manager/intrusive_heap_unittest.cc create mode 100644 base/task/sequence_manager/lazily_deallocated_deque.h create mode 100644 base/task/sequence_manager/lazily_deallocated_deque_unittest.cc create mode 100644 base/task/sequence_manager/lazy_now.cc create mode 100644 base/task/sequence_manager/lazy_now.h create mode 100644 base/task/sequence_manager/moveable_auto_lock.h create mode 100644 base/task/sequence_manager/real_time_domain.cc create mode 100644 base/task/sequence_manager/real_time_domain.h create mode 100644 base/task/sequence_manager/sequence_manager.cc create mode 100644 base/task/sequence_manager/sequence_manager.h create mode 100644 base/task/sequence_manager/sequence_manager_impl.cc create mode 100644 base/task/sequence_manager/sequence_manager_impl.h create mode 100644 base/task/sequence_manager/sequence_manager_impl_unittest.cc create mode 100644 base/task/sequence_manager/sequence_manager_perftest.cc create mode 100644 base/task/sequence_manager/sequenced_task_source.h create mode 100644 base/task/sequence_manager/task_queue.cc create mode 100644 base/task/sequence_manager/task_queue.h create mode 100644 base/task/sequence_manager/task_queue_impl.cc create mode 100644 base/task/sequence_manager/task_queue_impl.h create mode 100644 base/task/sequence_manager/task_queue_selector.cc create mode 100644 base/task/sequence_manager/task_queue_selector.h create mode 100644 base/task/sequence_manager/task_queue_selector_logic.h create mode 100644 base/task/sequence_manager/task_queue_selector_unittest.cc create mode 100644 base/task/sequence_manager/task_time_observer.h create mode 100644 base/task/sequence_manager/test/fake_task.cc create mode 100644 base/task/sequence_manager/test/fake_task.h create mode 100644 base/task/sequence_manager/test/lazy_thread_controller_for_test.cc create mode 100644 base/task/sequence_manager/test/lazy_thread_controller_for_test.h create mode 100644 base/task/sequence_manager/test/mock_time_domain.cc create mode 100644 base/task/sequence_manager/test/mock_time_domain.h create mode 100644 base/task/sequence_manager/test/sequence_manager_for_test.cc create mode 100644 base/task/sequence_manager/test/sequence_manager_for_test.h create mode 100644 base/task/sequence_manager/test/test_task_queue.cc create mode 100644 base/task/sequence_manager/test/test_task_queue.h create mode 100644 base/task/sequence_manager/test/test_task_time_observer.h create mode 100644 base/task/sequence_manager/thread_controller.h create mode 100644 base/task/sequence_manager/thread_controller_impl.cc create mode 100644 base/task/sequence_manager/thread_controller_impl.h create mode 100644 base/task/sequence_manager/thread_controller_with_message_pump_impl.cc create mode 100644 base/task/sequence_manager/thread_controller_with_message_pump_impl.h create mode 100644 base/task/sequence_manager/time_domain.cc create mode 100644 base/task/sequence_manager/time_domain.h create mode 100644 base/task/sequence_manager/time_domain_unittest.cc create mode 100644 base/task/sequence_manager/work_queue.cc create mode 100644 base/task/sequence_manager/work_queue.h create mode 100644 base/task/sequence_manager/work_queue_sets.cc create mode 100644 base/task/sequence_manager/work_queue_sets.h create mode 100644 base/task/sequence_manager/work_queue_sets_unittest.cc create mode 100644 base/task/sequence_manager/work_queue_unittest.cc create mode 100644 base/task_scheduler/can_schedule_sequence_observer.h create mode 100644 base/task_scheduler/environment_config.cc create mode 100644 base/task_scheduler/environment_config.h create mode 100644 base/task_scheduler/lazy_task_runner.cc create mode 100644 base/task_scheduler/lazy_task_runner.h create mode 100644 base/task_scheduler/lazy_task_runner_unittest.cc create mode 100644 base/task_scheduler/post_task.cc create mode 100644 base/task_scheduler/post_task.h create mode 100644 base/task_scheduler/scheduler_worker_observer.h create mode 100644 base/task_scheduler/scheduler_worker_pool.cc create mode 100644 base/task_scheduler/scheduler_worker_pool_unittest.cc create mode 100644 base/task_scheduler/service_thread.cc create mode 100644 base/task_scheduler/service_thread.h create mode 100644 base/task_scheduler/service_thread_unittest.cc create mode 100644 base/task_scheduler/single_thread_task_runner_thread_mode.h create mode 100644 base/task_scheduler/task_scheduler.h create mode 100644 base/task_scheduler/task_traits_details.h create mode 100644 base/task_scheduler/task_traits_unittest.cc create mode 100644 base/task_scheduler/task_traits_unittest.nc create mode 100644 base/task_scheduler/test_utils.cc create mode 100644 base/task_scheduler/tracked_ref.h create mode 100644 base/task_scheduler/tracked_ref_unittest.cc delete mode 100644 base/test/DEPS create mode 100644 base/test/android/java_handler_thread_helpers.cc create mode 100644 base/test/android/java_handler_thread_helpers.h create mode 100644 base/test/android/javatests/src/org/chromium/base/test/params/ParameterProvider.java create mode 100644 base/test/android/javatests/src/org/chromium/base/test/util/AnnotationProcessingUtils.java create mode 100644 base/test/android/javatests/src/org/chromium/base/test/util/AnnotationRule.java create mode 100644 base/test/android/junit/src/org/chromium/base/test/util/AnnotationProcessingUtilsTest.java create mode 100644 base/test/android/url_utils.cc create mode 100644 base/test/android/url_utils.h create mode 100644 base/test/bind_test_util.h create mode 100644 base/test/copy_only_int.h create mode 100644 base/test/data/file_util/.gitattributes create mode 100644 base/test/fontconfig_util_linux.cc create mode 100644 base/test/fontconfig_util_linux.h create mode 100644 base/test/generate_fontconfig_caches.cc create mode 100644 base/test/metrics/histogram_enum_reader.cc create mode 100644 base/test/metrics/histogram_enum_reader.h create mode 100644 base/test/metrics/histogram_enum_reader_unittest.cc create mode 100644 base/test/metrics/histogram_tester.cc create mode 100644 base/test/metrics/histogram_tester.h create mode 100644 base/test/move_only_int.h delete mode 100644 base/test/opaque_ref_counted.cc delete mode 100644 base/test/opaque_ref_counted.h create mode 100644 base/test/scoped_environment_variable_override.cc create mode 100644 base/test/scoped_environment_variable_override.h create mode 100644 base/test/scoped_feature_list_unittest.cc create mode 100644 base/test/scoped_task_environment.cc create mode 100644 base/test/scoped_task_environment.h create mode 100644 base/test/scoped_task_environment_unittest.cc delete mode 100644 base/test/sequenced_worker_pool_owner.cc delete mode 100644 base/test/sequenced_worker_pool_owner.h create mode 100644 base/test/test_child_process.cc create mode 100644 base/test/test_mock_time_task_runner_unittest.cc create mode 100644 base/test/test_shared_memory_util.cc create mode 100644 base/test/test_shared_memory_util.h create mode 100644 base/thread_annotations.h create mode 100644 base/thread_annotations_unittest.cc create mode 100644 base/thread_annotations_unittest.nc delete mode 100644 base/threading/non_thread_safe.h delete mode 100644 base/threading/non_thread_safe_impl.cc delete mode 100644 base/threading/non_thread_safe_impl.h delete mode 100644 base/threading/non_thread_safe_unittest.cc create mode 100644 base/threading/scoped_blocking_call.cc create mode 100644 base/threading/scoped_blocking_call.h create mode 100644 base/threading/scoped_blocking_call_unittest.cc create mode 100644 base/threading/sequence_local_storage_map.cc create mode 100644 base/threading/sequence_local_storage_map.h create mode 100644 base/threading/sequence_local_storage_map_unittest.cc create mode 100644 base/threading/sequence_local_storage_slot.cc create mode 100644 base/threading/sequence_local_storage_slot.h create mode 100644 base/threading/sequence_local_storage_slot_unittest.cc delete mode 100644 base/threading/sequenced_worker_pool.cc delete mode 100644 base/threading/sequenced_worker_pool.h create mode 100644 base/threading/thread_restrictions_unittest.cc delete mode 100644 base/threading/worker_pool.cc delete mode 100644 base/threading/worker_pool.h delete mode 100644 base/threading/worker_pool_posix.cc delete mode 100644 base/threading/worker_pool_posix.h delete mode 100644 base/threading/worker_pool_posix_unittest.cc delete mode 100644 base/threading/worker_pool_unittest.cc create mode 100644 base/time/time_android.cc create mode 100644 base/time/time_conversion_posix.cc rename base/time/{time_posix.cc => time_exploded_posix.cc} (56%) create mode 100644 base/time/time_now_posix.cc create mode 100644 base/time/time_override.cc create mode 100644 base/time/time_override.h create mode 100644 base/time/time_to_iso8601.cc create mode 100644 base/time/time_to_iso8601.h delete mode 100644 base/tracked_objects.cc delete mode 100644 base/tracked_objects.h delete mode 100644 base/tracked_objects_unittest.cc delete mode 100644 base/tracking_info.cc delete mode 100644 base/tracking_info.h create mode 100644 base/value_iterators.cc create mode 100644 base/value_iterators.h create mode 100644 base/value_iterators_unittest.cc delete mode 100644 components/timers/DEPS delete mode 100644 dbus/DEPS delete mode 100644 dbus/mock_object_manager.cc delete mode 100644 dbus/mock_object_manager.h delete mode 100644 gen/mojo/common/common_custom_types__type_mappings create mode 100644 ipc/ipc_buildflags.h create mode 100644 ipc/ipc_channel.cc create mode 100644 ipc/ipc_channel.h create mode 100644 ipc/ipc_channel_common.cc create mode 100644 ipc/ipc_channel_factory.cc create mode 100644 ipc/ipc_channel_factory.h create mode 100644 ipc/ipc_channel_mojo.cc create mode 100644 ipc/ipc_channel_mojo.h create mode 100644 ipc/ipc_channel_mojo_unittest.cc create mode 100644 ipc/ipc_channel_nacl.cc create mode 100644 ipc/ipc_channel_nacl.h create mode 100644 ipc/ipc_channel_proxy.cc create mode 100644 ipc/ipc_channel_proxy.h create mode 100644 ipc/ipc_channel_proxy_unittest.cc create mode 100644 ipc/ipc_channel_proxy_unittest_messages.h create mode 100644 ipc/ipc_channel_reader.cc create mode 100644 ipc/ipc_channel_reader.h create mode 100644 ipc/ipc_channel_reader_unittest.cc create mode 100644 ipc/ipc_cpu_perftest.cc delete mode 100644 ipc/ipc_export.h create mode 100644 ipc/ipc_fuzzing_tests.cc create mode 100644 ipc/ipc_logging.cc create mode 100644 ipc/ipc_logging.h create mode 100644 ipc/ipc_message_attachment_set_posix_unittest.cc create mode 100644 ipc/ipc_message_macros.h create mode 100644 ipc/ipc_message_null_macros.h create mode 100644 ipc/ipc_message_pipe_reader.cc create mode 100644 ipc/ipc_message_pipe_reader.h create mode 100644 ipc/ipc_message_protobuf_utils.h create mode 100644 ipc/ipc_message_protobuf_utils_unittest.cc create mode 100644 ipc/ipc_message_support_export.h create mode 100644 ipc/ipc_message_templates.h create mode 100644 ipc/ipc_message_templates_impl.h create mode 100644 ipc/ipc_message_unittest.cc create mode 100644 ipc/ipc_message_utils_unittest.cc create mode 100644 ipc/ipc_mojo_bootstrap.cc create mode 100644 ipc/ipc_mojo_bootstrap.h create mode 100644 ipc/ipc_mojo_bootstrap_unittest.cc create mode 100644 ipc/ipc_mojo_perftest.cc create mode 100644 ipc/ipc_perftest_messages.cc create mode 100644 ipc/ipc_perftest_messages.h create mode 100644 ipc/ipc_perftest_util.cc create mode 100644 ipc/ipc_perftest_util.h create mode 100644 ipc/ipc_platform_file.cc create mode 100644 ipc/ipc_platform_file.h create mode 100644 ipc/ipc_security_test_util.cc create mode 100644 ipc/ipc_security_test_util.h create mode 100644 ipc/ipc_send_fds_test.cc create mode 100644 ipc/ipc_sender.h create mode 100644 ipc/ipc_sync_channel.cc create mode 100644 ipc/ipc_sync_channel.h create mode 100644 ipc/ipc_sync_channel_unittest.cc create mode 100644 ipc/ipc_sync_message.cc create mode 100644 ipc/ipc_sync_message_filter.cc create mode 100644 ipc/ipc_sync_message_filter.h create mode 100644 ipc/ipc_sync_message_unittest.cc create mode 100644 ipc/ipc_sync_message_unittest.h create mode 100644 ipc/ipc_test_base.cc create mode 100644 ipc/ipc_test_base.h create mode 100644 ipc/ipc_test_channel_listener.cc create mode 100644 ipc/ipc_test_channel_listener.h create mode 100644 ipc/ipc_test_message_generator.cc create mode 100644 ipc/ipc_test_message_generator.h create mode 100644 ipc/ipc_test_messages.h create mode 100644 ipc/ipc_test_sink.cc create mode 100644 ipc/ipc_test_sink.h create mode 100644 ipc/message_filter.cc create mode 100644 ipc/message_filter.h create mode 100644 ipc/message_filter_router.cc create mode 100644 ipc/message_filter_router.h create mode 100644 ipc/message_mojom_traits.cc create mode 100644 ipc/message_mojom_traits.h create mode 100644 ipc/message_router.cc create mode 100644 ipc/message_router.h create mode 100644 ipc/message_view.cc create mode 100644 ipc/message_view.h create mode 100644 ipc/native_handle_type_converters.cc create mode 100644 ipc/native_handle_type_converters.h create mode 100644 ipc/param_traits_log_macros.h create mode 100644 ipc/param_traits_macros.h create mode 100644 ipc/param_traits_read_macros.h create mode 100644 ipc/param_traits_write_macros.h create mode 100644 ipc/run_all_perftests.cc create mode 100644 ipc/run_all_unittests.cc create mode 100644 ipc/struct_constructor_macros.h create mode 100644 ipc/struct_destructor_macros.h create mode 100644 ipc/sync_socket_unittest.cc create mode 100755 libchrome_tools/jni_registration_generator_helper.sh delete mode 100755 libchrome_tools/mojom_source_generator.sh create mode 100644 libchrome_tools/patch/580fcef.patch create mode 100644 libchrome_tools/patch/8fbafc9.patch create mode 100644 libchrome_tools/patch/ContextUtils.patch rename ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch => libchrome_tools/patch/ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch (100%) create mode 100644 libchrome_tools/patch/c7ce19d.patch create mode 100644 libchrome_tools/patch/file_path_mojom.patch create mode 100644 libchrome_tools/patch/handle_table.patch create mode 100644 libchrome_tools/patch/jni_registration_generator.patch delete mode 100644 libchrome_tools/patch/lazy_instance.patch create mode 100644 libchrome_tools/patch/memory_linux.patch create mode 100644 libchrome_tools/patch/message_loop_unittest.patch create mode 100644 libchrome_tools/patch/message_pump_for_ui.patch delete mode 100644 libchrome_tools/patch/mojo-Add-a-way-to-handle-unhandled-RuntimeExceptions.patch delete mode 100644 libchrome_tools/patch/mojo-Avoid-a-crash-when-NodeController-pending-invit.patch create mode 100644 libchrome_tools/patch/mojom_disable_trace_and_mem_dump.patch create mode 100644 libchrome_tools/patch/observer_list_unittest.patch create mode 100644 libchrome_tools/patch/shared_memory_handle.patch create mode 100644 libchrome_tools/patch/shared_memory_mapping.patch create mode 100644 libchrome_tools/patch/task_annotator.patch delete mode 100644 libchrome_tools/patch/task_scheduler.patch delete mode 100644 libchrome_tools/patch/time.patch create mode 100644 libchrome_tools/patch/values.patch delete mode 100644 mojo/DEPS delete mode 100644 mojo/android/DEPS delete mode 100644 mojo/android/javatests/DEPS delete mode 100644 mojo/android/javatests/init_library.cc delete mode 100644 mojo/android/javatests/mojo_test_case.h delete mode 100644 mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java delete mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java delete mode 100644 mojo/android/javatests/validation_test_util.h delete mode 100644 mojo/android/system/base_run_loop.h delete mode 100644 mojo/android/system/core_impl.h delete mode 100644 mojo/android/system/watcher_impl.h delete mode 100644 mojo/common/BUILD.gn delete mode 100644 mojo/common/DEPS delete mode 100644 mojo/common/common_custom_types_struct_traits.cc delete mode 100644 mojo/common/common_custom_types_struct_traits.h delete mode 100644 mojo/common/common_custom_types_unittest.cc delete mode 100644 mojo/common/file.typemap delete mode 100644 mojo/common/file_path.mojom delete mode 100644 mojo/common/file_path.typemap delete mode 100644 mojo/common/mojo_common_export.h delete mode 100644 mojo/common/string16.mojom delete mode 100644 mojo/common/string16.typemap delete mode 100644 mojo/common/struct_traits_unittest.cc delete mode 100644 mojo/common/test_common_custom_types.mojom delete mode 100644 mojo/common/text_direction.typemap delete mode 100644 mojo/common/time.typemap delete mode 100644 mojo/common/time_struct_traits.h delete mode 100644 mojo/common/traits_test_service.mojom delete mode 100644 mojo/common/typemaps.gni delete mode 100644 mojo/common/unguessable_token.typemap delete mode 100644 mojo/common/values.mojom delete mode 100644 mojo/common/values.typemap delete mode 100644 mojo/common/values_struct_traits.cc delete mode 100644 mojo/common/values_struct_traits.h delete mode 100644 mojo/common/version.mojom delete mode 100644 mojo/common/version.typemap create mode 100644 mojo/core/BUILD.gn rename mojo/{edk/system => core}/atomic_flag.h (75%) create mode 100644 mojo/core/broker.h create mode 100644 mojo/core/broker_host.cc create mode 100644 mojo/core/broker_host.h rename mojo/{edk/system => core}/broker_messages.h (71%) create mode 100644 mojo/core/broker_posix.cc rename mojo/{edk/system => core}/broker_win.cc (54%) rename mojo/{edk/system => core}/channel.cc (71%) rename mojo/{edk/system => core}/channel.h (67%) create mode 100644 mojo/core/channel_fuchsia.cc create mode 100644 mojo/core/channel_posix.cc rename mojo/{edk/system => core}/channel_unittest.cc (58%) create mode 100644 mojo/core/channel_win.cc create mode 100644 mojo/core/configuration.cc rename mojo/{edk/system => core}/configuration.h (53%) create mode 100644 mojo/core/connection_params.cc create mode 100644 mojo/core/connection_params.h create mode 100644 mojo/core/core.cc create mode 100644 mojo/core/core.h rename mojo/{edk/system => core}/core_test_base.cc (78%) rename mojo/{edk/system => core}/core_test_base.h (90%) create mode 100644 mojo/core/core_unittest.cc rename mojo/{edk/system => core}/data_pipe_consumer_dispatcher.cc (67%) rename mojo/{edk/system => core}/data_pipe_consumer_dispatcher.h (62%) rename mojo/{edk/system => core}/data_pipe_control_message.cc (51%) rename mojo/{edk/system => core}/data_pipe_control_message.h (75%) rename mojo/{edk/system => core}/data_pipe_producer_dispatcher.cc (67%) rename mojo/{edk/system => core}/data_pipe_producer_dispatcher.h (61%) rename mojo/{edk/system => core}/data_pipe_unittest.cc (80%) rename mojo/{edk/system => core}/dispatcher.cc (59%) rename mojo/{edk/system => core}/dispatcher.h (66%) create mode 100644 mojo/core/embedder/BUILD.gn create mode 100644 mojo/core/embedder/README.md create mode 100644 mojo/core/embedder/configuration.h create mode 100644 mojo/core/embedder/embedder.cc create mode 100644 mojo/core/embedder/embedder.h create mode 100644 mojo/core/embedder/process_error_callback.h rename mojo/{edk => core}/embedder/scoped_ipc_support.cc (64%) rename mojo/{edk => core}/embedder/scoped_ipc_support.h (89%) create mode 100644 mojo/core/embedder_unittest.cc create mode 100644 mojo/core/entrypoints.cc create mode 100644 mojo/core/entrypoints.h create mode 100644 mojo/core/export_only_thunks_api.lst create mode 100644 mojo/core/handle_signals_state.h rename mojo/{edk/system => core}/handle_table.cc (56%) rename mojo/{edk/system => core}/handle_table.h (62%) create mode 100644 mojo/core/handle_table_unittest.cc create mode 100644 mojo/core/invitation_dispatcher.cc create mode 100644 mojo/core/invitation_dispatcher.h create mode 100644 mojo/core/invitation_unittest.cc rename mojo/{edk/system => core}/mach_port_relay.cc (51%) rename mojo/{edk/system => core}/mach_port_relay.h (66%) rename mojo/{edk/system => core}/message_pipe_dispatcher.cc (53%) rename mojo/{edk/system => core}/message_pipe_dispatcher.h (70%) rename mojo/{edk/system => core}/message_pipe_perftest.cc (71%) rename mojo/{edk/system => core}/message_pipe_unittest.cc (65%) create mode 100644 mojo/core/message_unittest.cc create mode 100644 mojo/core/mojo_core.cc create mode 100644 mojo/core/mojo_core.def create mode 100644 mojo/core/mojo_core_unittest.cc rename mojo/{edk/system => core}/multiprocess_message_pipe_unittest.cc (72%) rename mojo/{edk/system => core}/node_channel.cc (54%) rename mojo/{edk/system => core}/node_channel.h (61%) create mode 100644 mojo/core/node_controller.cc rename mojo/{edk/system => core}/node_controller.h (55%) rename mojo/{edk/system => core}/options_validation.h (93%) rename mojo/{edk/system => core}/options_validation_unittest.cc (98%) rename mojo/{edk/system => core}/platform_handle_dispatcher.cc (80%) rename mojo/{edk/system => core}/platform_handle_dispatcher.h (71%) rename mojo/{edk/system => core}/platform_handle_dispatcher_unittest.cc (78%) create mode 100644 mojo/core/platform_handle_in_transit.cc create mode 100644 mojo/core/platform_handle_in_transit.h create mode 100644 mojo/core/platform_handle_utils.cc create mode 100644 mojo/core/platform_handle_utils.h create mode 100644 mojo/core/platform_shared_memory_mapping.cc create mode 100644 mojo/core/platform_shared_memory_mapping.h rename mojo/{edk/system => core}/platform_wrapper_unittest.cc (59%) rename mojo/{edk/system => core}/ports/BUILD.gn (72%) create mode 100644 mojo/core/ports/event.cc create mode 100644 mojo/core/ports/event.h rename mojo/{edk/system => core}/ports/message_filter.h (51%) rename mojo/{edk/system => core}/ports/message_queue.cc (59%) rename mojo/{edk/system => core}/ports/message_queue.h (55%) rename mojo/{edk/system => core}/ports/name.cc (74%) rename mojo/{edk/system => core}/ports/name.h (57%) create mode 100644 mojo/core/ports/name_unittest.cc create mode 100644 mojo/core/ports/node.cc rename mojo/{edk/system => core}/ports/node.h (57%) create mode 100644 mojo/core/ports/node_delegate.h rename mojo/{edk/system => core}/ports/port.cc (88%) create mode 100644 mojo/core/ports/port.h create mode 100644 mojo/core/ports/port_locker.cc create mode 100644 mojo/core/ports/port_locker.h create mode 100644 mojo/core/ports/port_ref.cc rename mojo/{edk/system => core}/ports/port_ref.h (58%) rename mojo/{edk/system => core}/ports/ports_unittest.cc (75%) rename mojo/{edk/system => core}/ports/user_data.h (72%) create mode 100644 mojo/core/ports/user_message.cc create mode 100644 mojo/core/ports/user_message.h create mode 100644 mojo/core/quota_unittest.cc rename mojo/{edk/system => core}/request_context.cc (82%) rename mojo/{edk/system => core}/request_context.h (91%) create mode 100644 mojo/core/run_all_core_unittests.cc create mode 100644 mojo/core/scoped_process_handle.cc create mode 100644 mojo/core/scoped_process_handle.h create mode 100644 mojo/core/shared_buffer_dispatcher.cc rename mojo/{edk/system => core}/shared_buffer_dispatcher.h (73%) rename mojo/{edk/system => core}/shared_buffer_dispatcher_unittest.cc (63%) rename mojo/{edk/system => core}/shared_buffer_unittest.cc (80%) create mode 100644 mojo/core/signals_unittest.cc rename mojo/{edk/system => core}/system_impl_export.h (77%) rename mojo/{edk => core}/test/BUILD.gn (56%) rename mojo/{edk => core}/test/mojo_test_base.cc (61%) rename mojo/{edk => core}/test/mojo_test_base.h (72%) rename mojo/{edk => core}/test/multiprocess_test_helper.cc (55%) rename mojo/{edk => core}/test/multiprocess_test_helper.h (82%) rename mojo/{edk => core}/test/run_all_perftests.cc (59%) rename mojo/{edk => core}/test/run_all_unittests.cc (78%) rename mojo/{edk => core}/test/test_support_impl.cc (91%) rename mojo/{edk => core}/test/test_support_impl.h (83%) create mode 100644 mojo/core/test/test_utils.cc create mode 100644 mojo/core/test/test_utils.h create mode 100644 mojo/core/test/test_utils_win.cc rename mojo/{edk/system => core}/test_utils.cc (93%) rename mojo/{edk/system => core}/test_utils.h (91%) create mode 100644 mojo/core/trap_unittest.cc create mode 100644 mojo/core/user_message_impl.cc create mode 100644 mojo/core/user_message_impl.h rename mojo/{edk/system => core}/watch.cc (71%) rename mojo/{edk/system => core}/watch.h (90%) rename mojo/{edk/system => core}/watcher_dispatcher.cc (68%) rename mojo/{edk/system => core}/watcher_dispatcher.h (74%) rename mojo/{edk/system => core}/watcher_set.cc (96%) rename mojo/{edk/system => core}/watcher_set.h (83%) delete mode 100644 mojo/edk/DEPS delete mode 100644 mojo/edk/embedder/BUILD.gn delete mode 100644 mojo/edk/embedder/README.md delete mode 100644 mojo/edk/embedder/configuration.h delete mode 100644 mojo/edk/embedder/connection_params.cc delete mode 100644 mojo/edk/embedder/connection_params.h delete mode 100644 mojo/edk/embedder/embedder.cc delete mode 100644 mojo/edk/embedder/embedder.h delete mode 100644 mojo/edk/embedder/embedder_internal.h delete mode 100644 mojo/edk/embedder/embedder_unittest.cc delete mode 100644 mojo/edk/embedder/entrypoints.cc delete mode 100644 mojo/edk/embedder/entrypoints.h delete mode 100644 mojo/edk/embedder/named_platform_channel_pair.h delete mode 100644 mojo/edk/embedder/named_platform_channel_pair_win.cc delete mode 100644 mojo/edk/embedder/named_platform_handle.h delete mode 100644 mojo/edk/embedder/named_platform_handle_utils.h delete mode 100644 mojo/edk/embedder/named_platform_handle_utils_posix.cc delete mode 100644 mojo/edk/embedder/named_platform_handle_utils_win.cc delete mode 100644 mojo/edk/embedder/pending_process_connection.cc delete mode 100644 mojo/edk/embedder/pending_process_connection.h delete mode 100644 mojo/edk/embedder/platform_channel_pair.cc delete mode 100644 mojo/edk/embedder/platform_channel_pair.h delete mode 100644 mojo/edk/embedder/platform_channel_pair_posix.cc delete mode 100644 mojo/edk/embedder/platform_channel_pair_posix_unittest.cc delete mode 100644 mojo/edk/embedder/platform_channel_pair_win.cc delete mode 100644 mojo/edk/embedder/platform_channel_utils_posix.cc delete mode 100644 mojo/edk/embedder/platform_channel_utils_posix.h delete mode 100644 mojo/edk/embedder/platform_handle.cc delete mode 100644 mojo/edk/embedder/platform_handle.h delete mode 100644 mojo/edk/embedder/platform_handle_utils.h delete mode 100644 mojo/edk/embedder/platform_handle_utils_posix.cc delete mode 100644 mojo/edk/embedder/platform_handle_utils_win.cc delete mode 100644 mojo/edk/embedder/platform_handle_vector.h delete mode 100644 mojo/edk/embedder/platform_shared_buffer.cc delete mode 100644 mojo/edk/embedder/platform_shared_buffer.h delete mode 100644 mojo/edk/embedder/platform_shared_buffer_unittest.cc delete mode 100644 mojo/edk/embedder/scoped_platform_handle.h delete mode 100644 mojo/edk/embedder/test_embedder.cc delete mode 100644 mojo/edk/embedder/test_embedder.h delete mode 100644 mojo/edk/js/BUILD.gn delete mode 100644 mojo/edk/js/core.cc delete mode 100644 mojo/edk/js/core.h delete mode 100644 mojo/edk/js/drain_data.cc delete mode 100644 mojo/edk/js/drain_data.h delete mode 100644 mojo/edk/js/handle.cc delete mode 100644 mojo/edk/js/handle.h delete mode 100644 mojo/edk/js/handle_close_observer.h delete mode 100644 mojo/edk/js/handle_unittest.cc delete mode 100644 mojo/edk/js/js_export.h delete mode 100644 mojo/edk/js/mojo_runner_delegate.cc delete mode 100644 mojo/edk/js/mojo_runner_delegate.h delete mode 100644 mojo/edk/js/support.cc delete mode 100644 mojo/edk/js/support.h delete mode 100644 mojo/edk/js/tests/BUILD.gn delete mode 100644 mojo/edk/js/tests/js_to_cpp.mojom delete mode 100644 mojo/edk/js/tests/js_to_cpp_tests.cc delete mode 100644 mojo/edk/js/tests/js_to_cpp_tests.js delete mode 100644 mojo/edk/js/tests/run_js_unittests.cc delete mode 100644 mojo/edk/js/threading.cc delete mode 100644 mojo/edk/js/threading.h delete mode 100644 mojo/edk/js/waiting_callback.cc delete mode 100644 mojo/edk/js/waiting_callback.h delete mode 100644 mojo/edk/system/BUILD.gn delete mode 100644 mojo/edk/system/broker.h delete mode 100644 mojo/edk/system/broker_host.cc delete mode 100644 mojo/edk/system/broker_host.h delete mode 100644 mojo/edk/system/broker_posix.cc delete mode 100644 mojo/edk/system/channel_posix.cc delete mode 100644 mojo/edk/system/channel_win.cc delete mode 100644 mojo/edk/system/configuration.cc delete mode 100644 mojo/edk/system/core.cc delete mode 100644 mojo/edk/system/core.h delete mode 100644 mojo/edk/system/core_unittest.cc delete mode 100644 mojo/edk/system/handle_signals_state.h delete mode 100644 mojo/edk/system/mapping_table.cc delete mode 100644 mojo/edk/system/mapping_table.h delete mode 100644 mojo/edk/system/message_for_transit.cc delete mode 100644 mojo/edk/system/message_for_transit.h delete mode 100644 mojo/edk/system/node_controller.cc delete mode 100644 mojo/edk/system/ports/event.cc delete mode 100644 mojo/edk/system/ports/event.h delete mode 100644 mojo/edk/system/ports/message.cc delete mode 100644 mojo/edk/system/ports/message.h delete mode 100644 mojo/edk/system/ports/node.cc delete mode 100644 mojo/edk/system/ports/node_delegate.h delete mode 100644 mojo/edk/system/ports/port.h delete mode 100644 mojo/edk/system/ports/port_ref.cc delete mode 100644 mojo/edk/system/ports_message.cc delete mode 100644 mojo/edk/system/ports_message.h delete mode 100644 mojo/edk/system/shared_buffer_dispatcher.cc delete mode 100644 mojo/edk/system/signals_unittest.cc delete mode 100644 mojo/edk/system/watcher_unittest.cc delete mode 100644 mojo/edk/test/multiprocess_test_helper_unittest.cc delete mode 100644 mojo/edk/test/test_utils.h delete mode 100644 mojo/edk/test/test_utils_posix.cc delete mode 100644 mojo/edk/test/test_utils_win.cc delete mode 100644 mojo/public/DEPS create mode 100644 mojo/public/c/system/invitation.h create mode 100644 mojo/public/c/system/quota.h delete mode 100644 mojo/public/c/system/set_thunks_for_app.cc rename mojo/public/c/system/tests/{core_unittest.cc => core_api_unittest.cc} (68%) create mode 100644 mojo/public/c/system/trap.h delete mode 100644 mojo/public/c/system/watcher.h create mode 100644 mojo/public/cpp/base/BUILD.gn create mode 100644 mojo/public/cpp/base/README.md create mode 100644 mojo/public/cpp/base/big_buffer.cc create mode 100644 mojo/public/cpp/base/big_buffer.h create mode 100644 mojo/public/cpp/base/big_buffer.typemap create mode 100644 mojo/public/cpp/base/big_buffer_mojom_traits.cc create mode 100644 mojo/public/cpp/base/big_buffer_mojom_traits.h create mode 100644 mojo/public/cpp/base/big_buffer_unittest.cc create mode 100644 mojo/public/cpp/base/big_string.typemap create mode 100644 mojo/public/cpp/base/big_string_mojom_traits.cc create mode 100644 mojo/public/cpp/base/big_string_mojom_traits.h create mode 100644 mojo/public/cpp/base/big_string_unittest.cc create mode 100644 mojo/public/cpp/base/file.typemap create mode 100644 mojo/public/cpp/base/file_error.typemap create mode 100644 mojo/public/cpp/base/file_error_mojom_traits.h create mode 100644 mojo/public/cpp/base/file_info.typemap create mode 100644 mojo/public/cpp/base/file_info_mojom_traits.cc create mode 100644 mojo/public/cpp/base/file_info_mojom_traits.h create mode 100644 mojo/public/cpp/base/file_mojom_traits.cc create mode 100644 mojo/public/cpp/base/file_mojom_traits.h create mode 100644 mojo/public/cpp/base/file_path.typemap create mode 100644 mojo/public/cpp/base/file_path_mojom_traits.cc create mode 100644 mojo/public/cpp/base/file_path_mojom_traits.h create mode 100644 mojo/public/cpp/base/file_path_unittest.cc create mode 100644 mojo/public/cpp/base/file_unittest.cc create mode 100644 mojo/public/cpp/base/logfont_win.typemap create mode 100644 mojo/public/cpp/base/logfont_win_mojom_traits.cc create mode 100644 mojo/public/cpp/base/logfont_win_mojom_traits.h create mode 100644 mojo/public/cpp/base/memory_allocator_dump_cross_process_uid.typemap create mode 100644 mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.cc create mode 100644 mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h create mode 100644 mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_unittest.cc create mode 100644 mojo/public/cpp/base/process_id.typemap create mode 100644 mojo/public/cpp/base/process_id_mojom_traits.cc create mode 100644 mojo/public/cpp/base/process_id_mojom_traits.h create mode 100644 mojo/public/cpp/base/process_id_unittest.cc create mode 100644 mojo/public/cpp/base/read_only_buffer.typemap create mode 100644 mojo/public/cpp/base/read_only_buffer_mojom_traits.cc create mode 100644 mojo/public/cpp/base/read_only_buffer_mojom_traits.h create mode 100644 mojo/public/cpp/base/read_only_buffer_unittest.cc create mode 100644 mojo/public/cpp/base/ref_counted_memory.typemap create mode 100644 mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc create mode 100644 mojo/public/cpp/base/ref_counted_memory_mojom_traits.h create mode 100644 mojo/public/cpp/base/ref_counted_memory_unittest.cc create mode 100644 mojo/public/cpp/base/shared_memory.typemap create mode 100644 mojo/public/cpp/base/shared_memory_mojom_traits.cc create mode 100644 mojo/public/cpp/base/shared_memory_mojom_traits.h create mode 100644 mojo/public/cpp/base/shared_memory_unittest.cc create mode 100644 mojo/public/cpp/base/string16.typemap create mode 100644 mojo/public/cpp/base/string16_mojom_traits.cc create mode 100644 mojo/public/cpp/base/string16_mojom_traits.h create mode 100644 mojo/public/cpp/base/string16_unittest.cc create mode 100644 mojo/public/cpp/base/text_direction.typemap create mode 100644 mojo/public/cpp/base/text_direction_mojom_traits.cc create mode 100644 mojo/public/cpp/base/text_direction_mojom_traits.h create mode 100644 mojo/public/cpp/base/text_direction_unittest.cc create mode 100644 mojo/public/cpp/base/thread_priority.typemap create mode 100644 mojo/public/cpp/base/thread_priority_mojom_traits.cc create mode 100644 mojo/public/cpp/base/thread_priority_mojom_traits.h create mode 100644 mojo/public/cpp/base/thread_priority_unittest.cc create mode 100644 mojo/public/cpp/base/time.typemap create mode 100644 mojo/public/cpp/base/time_mojom_traits.cc create mode 100644 mojo/public/cpp/base/time_mojom_traits.h create mode 100644 mojo/public/cpp/base/time_unittest.cc create mode 100644 mojo/public/cpp/base/typemaps.gni create mode 100644 mojo/public/cpp/base/unguessable_token.typemap create mode 100644 mojo/public/cpp/base/unguessable_token_mojom_traits.cc create mode 100644 mojo/public/cpp/base/unguessable_token_mojom_traits.h create mode 100644 mojo/public/cpp/base/unguessable_token_unittest.cc create mode 100644 mojo/public/cpp/base/values.typemap create mode 100644 mojo/public/cpp/base/values_mojom_traits.cc create mode 100644 mojo/public/cpp/base/values_mojom_traits.h create mode 100644 mojo/public/cpp/base/values_unittest.cc delete mode 100644 mojo/public/cpp/bindings/DEPS delete mode 100644 mojo/public/cpp/bindings/array_traits_carray.h create mode 100644 mojo/public/cpp/bindings/array_traits_span.h create mode 100644 mojo/public/cpp/bindings/callback_helpers.h rename mojo/public/cpp/bindings/{lib => }/equals_traits.h (67%) create mode 100644 mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc create mode 100644 mojo/public/cpp/bindings/lib/buffer.cc create mode 100644 mojo/public/cpp/bindings/lib/handle_serialization.h create mode 100644 mojo/public/cpp/bindings/lib/interface_ptr_state.cc rename mojo/public/cpp/bindings/lib/{handle_interface_serialization.h => interface_serialization.h} (51%) delete mode 100644 mojo/public/cpp/bindings/lib/message_buffer.cc delete mode 100644 mojo/public/cpp/bindings/lib/message_buffer.h delete mode 100644 mojo/public/cpp/bindings/lib/message_builder.cc delete mode 100644 mojo/public/cpp/bindings/lib/message_builder.h create mode 100644 mojo/public/cpp/bindings/lib/message_dumper.cc create mode 100644 mojo/public/cpp/bindings/lib/message_internal.cc delete mode 100644 mojo/public/cpp/bindings/lib/native_struct.cc delete mode 100644 mojo/public/cpp/bindings/lib/native_struct_data.cc delete mode 100644 mojo/public/cpp/bindings/lib/native_struct_data.h create mode 100644 mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc delete mode 100644 mojo/public/cpp/bindings/lib/string_traits_string16.cc create mode 100644 mojo/public/cpp/bindings/lib/task_runner_helper.cc create mode 100644 mojo/public/cpp/bindings/lib/task_runner_helper.h delete mode 100644 mojo/public/cpp/bindings/lib/union_accessor.h create mode 100644 mojo/public/cpp/bindings/lib/unserialized_message_context.cc create mode 100644 mojo/public/cpp/bindings/lib/unserialized_message_context.h create mode 100644 mojo/public/cpp/bindings/map_traits_flat_map.h create mode 100644 mojo/public/cpp/bindings/message_dumper.h create mode 100644 mojo/public/cpp/bindings/mojo_buildflags.h delete mode 100644 mojo/public/cpp/bindings/native_struct.h delete mode 100644 mojo/public/cpp/bindings/native_struct_data_view.h create mode 100644 mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h delete mode 100644 mojo/public/cpp/bindings/string_traits_string16.h create mode 100644 mojo/public/cpp/bindings/strong_associated_binding_set.h create mode 100644 mojo/public/cpp/bindings/tests/bindings_test_base.cc create mode 100644 mojo/public/cpp/bindings/tests/bindings_test_base.h create mode 100644 mojo/public/cpp/bindings/tests/callback_helpers_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/lazy_serialization_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/native_struct_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/test_helpers_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/test_native_types.cc create mode 100644 mojo/public/cpp/bindings/tests/test_native_types.h create mode 100644 mojo/public/cpp/platform/BUILD.gn create mode 100644 mojo/public/cpp/platform/README.md create mode 100644 mojo/public/cpp/platform/named_platform_channel.cc create mode 100644 mojo/public/cpp/platform/named_platform_channel.h create mode 100644 mojo/public/cpp/platform/named_platform_channel_fuchsia.cc create mode 100644 mojo/public/cpp/platform/named_platform_channel_posix.cc create mode 100644 mojo/public/cpp/platform/named_platform_channel_win.cc create mode 100644 mojo/public/cpp/platform/platform_channel.cc create mode 100644 mojo/public/cpp/platform/platform_channel.h create mode 100644 mojo/public/cpp/platform/platform_channel_endpoint.cc create mode 100644 mojo/public/cpp/platform/platform_channel_endpoint.h create mode 100644 mojo/public/cpp/platform/platform_channel_server_endpoint.cc create mode 100644 mojo/public/cpp/platform/platform_channel_server_endpoint.h create mode 100644 mojo/public/cpp/platform/platform_handle.cc create mode 100644 mojo/public/cpp/platform/platform_handle.h create mode 100644 mojo/public/cpp/platform/socket_utils_posix.cc create mode 100644 mojo/public/cpp/platform/socket_utils_posix.h create mode 100644 mojo/public/cpp/platform/tests/BUILD.gn create mode 100644 mojo/public/cpp/platform/tests/platform_handle_unittest.cc rename mojo/{common => public/cpp/system}/data_pipe_drainer.cc (76%) rename mojo/{common => public/cpp/system}/data_pipe_drainer.h (78%) rename mojo/{common => public/cpp/system}/data_pipe_utils.cc (75%) rename mojo/{common => public/cpp/system}/data_pipe_utils.h (51%) create mode 100644 mojo/public/cpp/system/file_data_pipe_producer.cc create mode 100644 mojo/public/cpp/system/file_data_pipe_producer.h create mode 100644 mojo/public/cpp/system/handle_signal_tracker.cc create mode 100644 mojo/public/cpp/system/handle_signal_tracker.h create mode 100644 mojo/public/cpp/system/invitation.cc create mode 100644 mojo/public/cpp/system/invitation.h create mode 100644 mojo/public/cpp/system/isolated_connection.cc create mode 100644 mojo/public/cpp/system/isolated_connection.h delete mode 100644 mojo/public/cpp/system/message.cc create mode 100644 mojo/public/cpp/system/message_pipe.cc create mode 100644 mojo/public/cpp/system/scope_to_message_pipe.cc create mode 100644 mojo/public/cpp/system/scope_to_message_pipe.h create mode 100644 mojo/public/cpp/system/string_data_pipe_producer.cc create mode 100644 mojo/public/cpp/system/string_data_pipe_producer.h create mode 100644 mojo/public/cpp/system/tests/data_pipe_drainer_unittest.cc create mode 100644 mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc create mode 100644 mojo/public/cpp/system/tests/handle_signal_tracker_unittest.cc create mode 100644 mojo/public/cpp/system/tests/invitation_unittest.cc create mode 100644 mojo/public/cpp/system/tests/scope_to_message_pipe_unittest.cc create mode 100644 mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc rename mojo/public/cpp/system/{watcher.cc => trap.cc} (54%) create mode 100644 mojo/public/cpp/system/trap.h delete mode 100644 mojo/public/cpp/system/watcher.h create mode 100644 mojo/public/interfaces/bindings/native_struct.mojom delete mode 100644 mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom delete mode 100644 mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom rename mojo/public/interfaces/bindings/tests/{ => echo_import}/echo_import.mojom (100%) create mode 100644 mojo/public/interfaces/bindings/tests/test_name_generator.mojom create mode 100644 mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java rename mojo/{android => public/java/system}/BUILD.gn (63%) rename mojo/{android => public/java}/system/base_run_loop.cc (53%) rename mojo/{android => public/java}/system/core_impl.cc (54%) rename mojo/{android => public/java/system}/javatests/AndroidManifest.xml (87%) rename mojo/{android => public/java/system}/javatests/apk/.empty (100%) create mode 100644 mojo/public/java/system/javatests/init_library.cc rename mojo/{android/javatests/mojo_test_case.cc => public/java/system/javatests/mojo_test_rule.cc} (63%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/HandleMock.java (90%) create mode 100644 mojo/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/TestUtils.java (99%) create mode 100644 mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/BindingsTest.java (77%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java (86%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java (74%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java (76%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java (57%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java (68%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java (70%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java (80%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java (56%) create mode 100644 mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java (68%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/RouterTest.java (80%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/SerializationTest.java (82%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/ValidationTest.java (80%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java (99%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java (81%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java (95%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java (71%) rename mojo/{android => public/java/system}/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java (65%) rename mojo/{android => public/java/system}/javatests/validation_test_util.cc (77%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java (96%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/CoreImpl.java (92%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java (99%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java (99%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/HandleBase.java (99%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java (82%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java (99%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java (99%) rename mojo/{android => public/java}/system/src/org/chromium/mojo/system/impl/WatcherImpl.java (100%) rename mojo/{android => public/java}/system/watcher_impl.cc (66%) rename mojo/public/js/{new_bindings => }/base.js (70%) delete mode 100644 mojo/public/js/buffer.js delete mode 100644 mojo/public/js/codec.js delete mode 100644 mojo/public/js/connector.js delete mode 100644 mojo/public/js/constants.cc delete mode 100644 mojo/public/js/constants.h delete mode 100644 mojo/public/js/core.js rename mojo/public/js/{new_bindings => lib}/buffer.js (100%) rename mojo/public/js/{new_bindings => lib}/codec.js (70%) rename mojo/public/js/{new_bindings => lib}/connector.js (54%) rename mojo/public/js/{ => lib}/router.js (50%) rename mojo/public/js/{new_bindings => lib}/unicode.js (77%) rename mojo/public/js/{new_bindings => lib}/validator.js (74%) create mode 100644 mojo/public/js/mojo_bindings_resources.grd delete mode 100644 mojo/public/js/new_bindings/bindings.js delete mode 100644 mojo/public/js/new_bindings/interface_types.js delete mode 100644 mojo/public/js/new_bindings/lib/control_message_handler.js delete mode 100644 mojo/public/js/new_bindings/lib/control_message_proxy.js delete mode 100644 mojo/public/js/new_bindings/router.js delete mode 100644 mojo/public/js/support.js delete mode 100644 mojo/public/js/tests/core_unittest.js delete mode 100644 mojo/public/js/tests/validation_test_input_parser.js delete mode 100644 mojo/public/js/tests/validation_unittest.js delete mode 100644 mojo/public/js/threading.js delete mode 100644 mojo/public/js/unicode.js delete mode 100644 mojo/public/js/validator.js create mode 100644 mojo/public/mojom/base/BUILD.gn create mode 100644 mojo/public/mojom/base/big_buffer.mojom create mode 100644 mojo/public/mojom/base/big_string.mojom rename mojo/{common => public/mojom/base}/file.mojom (86%) create mode 100644 mojo/public/mojom/base/file_error.mojom create mode 100644 mojo/public/mojom/base/file_info.mojom create mode 100644 mojo/public/mojom/base/file_path.mojom create mode 100644 mojo/public/mojom/base/logfont_win.mojom create mode 100644 mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom create mode 100644 mojo/public/mojom/base/process_id.mojom create mode 100644 mojo/public/mojom/base/read_only_buffer.mojom create mode 100644 mojo/public/mojom/base/ref_counted_memory.mojom create mode 100644 mojo/public/mojom/base/shared_memory.mojom create mode 100644 mojo/public/mojom/base/string16.mojom rename mojo/{common => public/mojom/base}/text_direction.mojom (68%) create mode 100644 mojo/public/mojom/base/thread_priority.mojom rename mojo/{common => public/mojom/base}/time.mojom (95%) rename mojo/{common => public/mojom/base}/unguessable_token.mojom (91%) create mode 100644 mojo/public/mojom/base/values.mojom create mode 100644 mojo/public/tools/bindings/gen_data_files_list.py create mode 100644 mojo/public/tools/bindings/generators/__init__.py create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/module-shared-message-ids.h.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_unserialized_message_context.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/externs/interface_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/externs/module.externs.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/externs/struct_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl delete mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/parse/conditional_features.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/parse/conditional_features_unittest.py create mode 100644 mojo/public/tools/fuzzers/BUILD.gn create mode 100644 mojo/public/tools/fuzzers/fuzz.mojom create mode 100644 mojo/public/tools/fuzzers/fuzz_impl.cc create mode 100644 mojo/public/tools/fuzzers/fuzz_impl.h create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_0.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_1.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_10.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_11.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_2.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_3.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_4.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_5.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_6.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_7.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_8.mojomsg create mode 100644 mojo/public/tools/fuzzers/message_corpus/message_9.mojomsg create mode 100644 mojo/public/tools/fuzzers/mojo_fuzzer.proto create mode 100644 mojo/public/tools/fuzzers/mojo_fuzzer_message_dump.cc create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_fuzzer.cc create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/07775ad8fdb79599024caefbe7889501dfee9e06 create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/2ce2f91669a46921ebf4e47679c86dd2bf5b1496 create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/32a65dcd84debde03d51f8b8ace2cdcc87461d34 create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/7cbf9144ec3980eb121eedc679ebc56a3ddd22a6 create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9ccc6b5c0a61672816dc252194c3d722c18107bc create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9e0a62bdd4b08cb777bee9449a22b3ad6702b106 create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/a74241101f97704b96c9ba11b4781651e236ad8f create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/be66c5d078fbf574388b7b1d25a29ff2d16df67e create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/e4be6bde72d04c5cda7d4939a80e5890c5c01374 create mode 100644 mojo/public/tools/fuzzers/mojo_parse_message_proto_fuzzer.cc delete mode 100644 third_party/catapult/LICENSE delete mode 100644 third_party/catapult/devil/PRESUBMIT.py delete mode 100644 third_party/catapult/devil/README.md delete mode 100755 third_party/catapult/devil/bin/generate_md_docs delete mode 100755 third_party/catapult/devil/bin/run_py_devicetests delete mode 100755 third_party/catapult/devil/bin/run_py_tests delete mode 100644 third_party/catapult/devil/devil/__init__.py delete mode 100644 third_party/catapult/devil/devil/android/__init__.py delete mode 100644 third_party/catapult/devil/devil/android/apk_helper.py delete mode 100755 third_party/catapult/devil/devil/android/apk_helper_test.py delete mode 100644 third_party/catapult/devil/devil/android/app_ui.py delete mode 100644 third_party/catapult/devil/devil/android/app_ui_test.py delete mode 100644 third_party/catapult/devil/devil/android/battery_utils.py delete mode 100755 third_party/catapult/devil/devil/android/battery_utils_test.py delete mode 100644 third_party/catapult/devil/devil/android/constants/__init__.py delete mode 100644 third_party/catapult/devil/devil/android/constants/chrome.py delete mode 100644 third_party/catapult/devil/devil/android/constants/file_system.py delete mode 100644 third_party/catapult/devil/devil/android/decorators.py delete mode 100644 third_party/catapult/devil/devil/android/decorators_test.py delete mode 100644 third_party/catapult/devil/devil/android/device_blacklist.py delete mode 100644 third_party/catapult/devil/devil/android/device_blacklist_test.py delete mode 100644 third_party/catapult/devil/devil/android/device_errors.py delete mode 100755 third_party/catapult/devil/devil/android/device_errors_test.py delete mode 100644 third_party/catapult/devil/devil/android/device_list.py delete mode 100644 third_party/catapult/devil/devil/android/device_signal.py delete mode 100644 third_party/catapult/devil/devil/android/device_temp_file.py delete mode 100644 third_party/catapult/devil/devil/android/device_test_case.py delete mode 100644 third_party/catapult/devil/devil/android/device_utils.py delete mode 100755 third_party/catapult/devil/devil/android/device_utils_devicetest.py delete mode 100755 third_party/catapult/devil/devil/android/device_utils_test.py delete mode 100644 third_party/catapult/devil/devil/android/fastboot_utils.py delete mode 100755 third_party/catapult/devil/devil/android/fastboot_utils_test.py delete mode 100644 third_party/catapult/devil/devil/android/flag_changer.py delete mode 100644 third_party/catapult/devil/devil/android/flag_changer_devicetest.py delete mode 100755 third_party/catapult/devil/devil/android/flag_changer_test.py delete mode 100644 third_party/catapult/devil/devil/android/forwarder.py delete mode 100644 third_party/catapult/devil/devil/android/install_commands.py delete mode 100644 third_party/catapult/devil/devil/android/logcat_monitor.py delete mode 100755 third_party/catapult/devil/devil/android/logcat_monitor_test.py delete mode 100644 third_party/catapult/devil/devil/android/md5sum.py delete mode 100755 third_party/catapult/devil/devil/android/md5sum_test.py delete mode 100644 third_party/catapult/devil/devil/android/perf/__init__.py delete mode 100644 third_party/catapult/devil/devil/android/perf/cache_control.py delete mode 100644 third_party/catapult/devil/devil/android/perf/perf_control.py delete mode 100644 third_party/catapult/devil/devil/android/perf/perf_control_devicetest.py delete mode 100644 third_party/catapult/devil/devil/android/perf/surface_stats_collector.py delete mode 100644 third_party/catapult/devil/devil/android/perf/thermal_throttle.py delete mode 100644 third_party/catapult/devil/devil/android/ports.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/__init__.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/aapt.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/adb_compatibility_devicetest.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/adb_wrapper.py delete mode 100755 third_party/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py delete mode 100755 third_party/catapult/devil/devil/android/sdk/adb_wrapper_test.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/build_tools.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/dexdump.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/fastboot.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/gce_adb_wrapper.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/intent.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/keyevent.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/shared_prefs.py delete mode 100755 third_party/catapult/devil/devil/android/sdk/shared_prefs_test.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/split_select.py delete mode 100644 third_party/catapult/devil/devil/android/sdk/test/data/push_directory/push_directory_contents.txt delete mode 100644 third_party/catapult/devil/devil/android/sdk/test/data/push_file.txt delete mode 100644 third_party/catapult/devil/devil/android/sdk/version_codes.py delete mode 100644 third_party/catapult/devil/devil/android/settings.py delete mode 100644 third_party/catapult/devil/devil/android/tools/__init__.py delete mode 100755 third_party/catapult/devil/devil/android/tools/adb_run_shell_cmd.py delete mode 100755 third_party/catapult/devil/devil/android/tools/cpufreq.py delete mode 100755 third_party/catapult/devil/devil/android/tools/device_monitor.py delete mode 100755 third_party/catapult/devil/devil/android/tools/device_monitor_test.py delete mode 100755 third_party/catapult/devil/devil/android/tools/device_recovery.py delete mode 100755 third_party/catapult/devil/devil/android/tools/device_status.py delete mode 100755 third_party/catapult/devil/devil/android/tools/flash_device.py delete mode 100755 third_party/catapult/devil/devil/android/tools/keyboard.py delete mode 100755 third_party/catapult/devil/devil/android/tools/provision_devices.py delete mode 100755 third_party/catapult/devil/devil/android/tools/screenshot.py delete mode 100644 third_party/catapult/devil/devil/android/tools/script_common.py delete mode 100755 third_party/catapult/devil/devil/android/tools/script_common_test.py delete mode 100755 third_party/catapult/devil/devil/android/tools/video_recorder.py delete mode 100755 third_party/catapult/devil/devil/android/tools/wait_for_devices.py delete mode 100644 third_party/catapult/devil/devil/android/valgrind_tools/__init__.py delete mode 100644 third_party/catapult/devil/devil/android/valgrind_tools/base_tool.py delete mode 100644 third_party/catapult/devil/devil/base_error.py delete mode 100644 third_party/catapult/devil/devil/constants/__init__.py delete mode 100644 third_party/catapult/devil/devil/constants/exit_codes.py delete mode 100644 third_party/catapult/devil/devil/devil_dependencies.json delete mode 100644 third_party/catapult/devil/devil/devil_env.py delete mode 100755 third_party/catapult/devil/devil/devil_env_test.py delete mode 100644 third_party/catapult/devil/devil/utils/__init__.py delete mode 100755 third_party/catapult/devil/devil/utils/battor_device_mapping.py delete mode 100644 third_party/catapult/devil/devil/utils/cmd_helper.py delete mode 100755 third_party/catapult/devil/devil/utils/cmd_helper_test.py delete mode 100644 third_party/catapult/devil/devil/utils/file_utils.py delete mode 100755 third_party/catapult/devil/devil/utils/find_usb_devices.py delete mode 100755 third_party/catapult/devil/devil/utils/find_usb_devices_test.py delete mode 100644 third_party/catapult/devil/devil/utils/geometry.py delete mode 100644 third_party/catapult/devil/devil/utils/geometry_test.py delete mode 100644 third_party/catapult/devil/devil/utils/host_utils.py delete mode 100644 third_party/catapult/devil/devil/utils/lazy/__init__.py delete mode 100644 third_party/catapult/devil/devil/utils/lazy/weak_constant.py delete mode 100644 third_party/catapult/devil/devil/utils/lsusb.py delete mode 100755 third_party/catapult/devil/devil/utils/lsusb_test.py delete mode 100755 third_party/catapult/devil/devil/utils/markdown.py delete mode 100755 third_party/catapult/devil/devil/utils/markdown_test.py delete mode 100644 third_party/catapult/devil/devil/utils/mock_calls.py delete mode 100755 third_party/catapult/devil/devil/utils/mock_calls_test.py delete mode 100644 third_party/catapult/devil/devil/utils/parallelizer.py delete mode 100644 third_party/catapult/devil/devil/utils/parallelizer_test.py delete mode 100644 third_party/catapult/devil/devil/utils/reraiser_thread.py delete mode 100644 third_party/catapult/devil/devil/utils/reraiser_thread_unittest.py delete mode 100755 third_party/catapult/devil/devil/utils/reset_usb.py delete mode 100644 third_party/catapult/devil/devil/utils/run_tests_helper.py delete mode 100644 third_party/catapult/devil/devil/utils/signal_handler.py delete mode 100644 third_party/catapult/devil/devil/utils/test/data/test_serial_map.json delete mode 100644 third_party/catapult/devil/devil/utils/timeout_retry.py delete mode 100755 third_party/catapult/devil/devil/utils/timeout_retry_unittest.py delete mode 100755 third_party/catapult/devil/devil/utils/update_mapping.py delete mode 100644 third_party/catapult/devil/devil/utils/usb_hubs.py delete mode 100644 third_party/catapult/devil/devil/utils/watchdog_timer.py delete mode 100644 third_party/catapult/devil/devil/utils/zip_utils.py delete mode 100644 third_party/catapult/devil/docs/adb_wrapper.md delete mode 100644 third_party/catapult/devil/docs/device_blacklist.md delete mode 100644 third_party/catapult/devil/docs/device_utils.md delete mode 100644 third_party/catapult/devil/docs/markdown.md delete mode 100644 third_party/catapult/devil/docs/persistent_device_list.md delete mode 100644 third_party/catapult/devil/pylintrc create mode 100644 third_party/jinja2/Jinja2-2.10.tar.gz.md5 create mode 100644 third_party/jinja2/Jinja2-2.10.tar.gz.sha512 delete mode 100644 third_party/jinja2/Jinja2-2.8.tar.gz.md5 delete mode 100644 third_party/jinja2/Jinja2-2.8.tar.gz.sha512 create mode 100644 third_party/jinja2/_identifier.py delete mode 100644 third_party/jinja2/_stringdefs.py create mode 100644 third_party/jinja2/asyncfilters.py create mode 100644 third_party/jinja2/asyncsupport.py create mode 100644 third_party/jinja2/idtracking.py create mode 100644 third_party/jinja2/jinja2.gni create mode 100644 third_party/jinja2/nativetypes.py delete mode 100644 ui/gfx/geometry/mojo/DEPS create mode 100644 ui/gfx/geometry/scroll_offset.cc create mode 100644 ui/gfx/geometry/scroll_offset.h delete mode 100644 ui/gfx/range/mojo/DEPS diff --git a/Android.bp b/Android.bp index 8f10867..ec721a7 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 4b25f3f..0000000 --- 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 7fc8107..0000000 --- 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 a211732..0000000 --- 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 9a3d114..b6ddbaa 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 0caeb73..faffae2 100644 --- a/base/allocator/allocator_shim.cc +++ b/base/allocator/allocator_shim.cc @@ -41,10 +41,6 @@ subtle::AtomicWord g_chain_head = reinterpret_cast( 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 65ac1eb..527e414 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 e33754a..c351a7c 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 +// 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 3313687..b1e6ee2 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 0000000..c547e2d --- /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 ea479cd..0000000 --- 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 0000000..5d7cc0a --- /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 +#include + +#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 869c703..bebf901 100644 --- a/base/android/build_info.cc +++ b/base/android/build_info.cc @@ -7,28 +7,39 @@ #include #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& java_string) { - std::string str = ConvertJavaStringToUTF8(java_string); - return strdup(str.c_str()); +const char* StrDupParam(const std::vector& params, int index) { + return strdup(params[index].c_str()); } -} // namespace +int GetIntParam(const std::vector& 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 params_objs = Java_BuildInfo_getAll(env); + std::vector params; + AppendJavaStringArrayToStringVector(env, params_objs.obj(), ¶ms); + 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& 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::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 cce74f4..bfe4db2 100644 --- a/base/android/build_info.h +++ b/base/android/build_info.h @@ -8,6 +8,7 @@ #include #include +#include #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& 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 e2c4ed0..0000000 --- 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 - -#include "base/android/scoped_java_ref.h" -#include "base/lazy_instance.h" -#include "jni/ContextUtils_jni.h" - -using base::android::JavaRef; - -namespace base { -namespace android { - -namespace { - -// Leak the global app context, as it is used from a non-joinable worker thread -// that may still be running at shutdown. There is no harm in doing this. -base::LazyInstance>::Leaky - g_application_context = LAZY_INSTANCE_INITIALIZER; - -void SetNativeApplicationContext(JNIEnv* env, const JavaRef& context) { - if (env->IsSameObject(g_application_context.Get().obj(), context.obj())) { - // It's safe to set the context more than once if it's the same context. - return; - } - DCHECK(g_application_context.Get().is_null()); - g_application_context.Get().Reset(context); -} - -} // namespace - -const JavaRef& GetApplicationContext() { - DCHECK(!g_application_context.Get().is_null()); - return g_application_context.Get(); -} - -static void InitNativeSideApplicationContext( - JNIEnv* env, - const JavaParamRef& clazz, - const JavaParamRef& context) { - SetNativeApplicationContext(env, context); -} - -bool RegisterContextUtils(JNIEnv* env) { - return RegisterNativesImpl(env); -} - -} // namespace android -} // namespace base diff --git a/base/android/context_utils.h b/base/android/context_utils.h deleted file mode 100644 index c5289f1..0000000 --- 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 - -#include "base/android/scoped_java_ref.h" -#include "base/base_export.h" - -namespace base { -namespace android { - -// Gets a global ref to the application context set with -// InitApplicationContext(). Ownership is retained by the function - the caller -// must NOT release it. -BASE_EXPORT const JavaRef& GetApplicationContext(); - -bool RegisterContextUtils(JNIEnv* env); - -} // namespace android -} // namespace base - -#endif // BASE_ANDROID_CONTEXT_UTILS_H_ diff --git a/base/android/java/src/org/chromium/base/BuildConfig.java b/base/android/java/src/org/chromium/base/BuildConfig.java new file mode 100644 index 0000000..b8fee95 --- /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 de4ad08..111bca8 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. + *

+ * @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 448eff9..c648e01 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. @@ -61,16 +67,6 @@ public class ContextUtils { initJavaSideApplicationContext(appContext); } - /** - * Initialize the native Android application context to be the same as the java counter-part. - */ - public static void initApplicationContextForNative() { - if (sApplicationContext == null) { - throw new RuntimeException("Cannot have native global application context be null."); - } - nativeInitNativeSideApplicationContext(sApplicationContext); - } - /** * Only called by the static holder class and tests. * @@ -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 0000000..566df70 --- /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. + * + *

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. + * + *

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> mPool; + + public DiscardableReferencePool() { + WeakHashMap, 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 The type of the object. + */ + public static class DiscardableReference { + @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 The type of the object. + * @param payload The payload to add to the pool. + * @return A new reference to the {@code payload}. + */ + public DiscardableReference put(T payload) { + assert payload != null; + DiscardableReference 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 0000000..f192f78 --- /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 ab554cd..a8e487b 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 0000000..beaaac0 --- /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: + *

+ *     try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
+ *         return Example.doThingThatRequiresDiskWrites();
+ *     }
+ * 
+ * + */ +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 0000000..350da57 --- /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, but without a checked Exception. + * + * @param Return type. + */ +public interface Supplier { + /** + * 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 0000000..61872a0 --- /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 task = new FutureTask(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 runOnUiThreadBlockingNoException(Callable 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 runOnUiThreadBlocking(Callable c) throws ExecutionException { + FutureTask task = new FutureTask(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 FutureTask runOnUiThread(FutureTask 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 FutureTask runOnUiThread(Callable c) { + return runOnUiThread(new FutureTask(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 FutureTask postOnUiThread(FutureTask 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 0000000..cddd3d9 --- /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 94ef3fa..52f5b7e 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 0b35ade..56aab74 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 89068ac..0000000 --- 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 0000000..96eb38e --- /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& jcaller, + jboolean crash_after_report, + const JavaParamRef& 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& jcaller, + const JavaParamRef& 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 0000000..c0a7ea6 --- /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 + +#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 0000000..37e3b40 --- /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 0000000..2fd92be --- /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 { + @Override + protected Void doInBackground(Void... params) { + return null; + } + } + + private static class SpecialOsAsyncTask extends android.os.AsyncTask { + @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(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(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(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 2b5869f..3c17c39 100644 --- a/base/android/jni_android.cc +++ b/base/android/jni_android.cc @@ -5,14 +5,13 @@ #include "base/android/jni_android.h" #include +#include #include -#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>::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>::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(&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 bytearray_output_stream(env, env->NewObject(bytearray_output_stream_clazz.obj(), bytearray_output_stream_constructor)); + CheckException(env); // Create an instance of PrintStream. ScopedJavaLocalRef printstream_clazz = @@ -285,21 +293,24 @@ std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { ScopedJavaLocalRef 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 exception_string( env, static_cast( 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 de53c10..fba6113 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 0000000..52b1679 --- /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 +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(std::max(0, length)); +} + +} // namespace + +ScopedJavaLocalRef 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(bytes)); + CheckException(env); + + return ScopedJavaLocalRef(env, byte_array); +} + +ScopedJavaLocalRef ToJavaByteArray( + JNIEnv* env, + const std::vector& bytes) { + return ToJavaByteArray(env, bytes.data(), bytes.size()); +} + +ScopedJavaLocalRef 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(bools)); + CheckException(env); + + return ScopedJavaLocalRef(env, boolean_array); +} + +ScopedJavaLocalRef 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(ints)); + CheckException(env); + + return ScopedJavaLocalRef(env, int_array); +} + +ScopedJavaLocalRef ToJavaIntArray( + JNIEnv* env, const std::vector& ints) { + return ToJavaIntArray(env, ints.data(), ints.size()); +} + +ScopedJavaLocalRef 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(longs)); + CheckException(env); + + return ScopedJavaLocalRef(env, long_array); +} + +// Returns a new Java long array converted from the given int64_t array. +BASE_EXPORT ScopedJavaLocalRef ToJavaLongArray( + JNIEnv* env, + const std::vector& longs) { + return ToJavaLongArray(env, longs.data(), longs.size()); +} + +// Returns a new Java float array converted from the given C++ float array. +BASE_EXPORT ScopedJavaLocalRef 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(floats)); + CheckException(env); + + return ScopedJavaLocalRef(env, float_array); +} + +BASE_EXPORT ScopedJavaLocalRef ToJavaFloatArray( + JNIEnv* env, + const std::vector& floats) { + return ToJavaFloatArray(env, floats.data(), floats.size()); +} + +ScopedJavaLocalRef ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector& v) { + ScopedJavaLocalRef 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 byte_array = ToJavaByteArray( + env, reinterpret_cast(v[i].data()), v[i].length()); + env->SetObjectArrayElement(joa, i, byte_array.obj()); + } + return ScopedJavaLocalRef(env, joa); +} + +ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v) { + ScopedJavaLocalRef 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 item = ConvertUTF8ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef(env, joa); +} + +ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v) { + ScopedJavaLocalRef 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 item = ConvertUTF16ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef(env, joa); +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector* 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 str(env, + static_cast(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF16(env, str.obj(), out->data() + back + i); + } +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector* 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 str(env, + static_cast(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF8(env, str.obj(), out->data() + back + i); + } +} + +void AppendJavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector* 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(out->data() + back)); +} + +void JavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector* out) { + DCHECK(out); + DCHECK(byte_array); + out->clear(); + AppendJavaByteArrayToByteVector(env, byte_array, out); +} + +void JavaBooleanArrayToBoolVector(JNIEnv* env, + jbooleanArray boolean_array, + std::vector* 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. + jboolean* values = env->GetBooleanArrayElements(boolean_array, nullptr); + for (size_t i = 0; i < len; ++i) { + out->at(i) = static_cast(values[i]); + } +} + +void JavaIntArrayToIntVector(JNIEnv* env, + jintArray int_array, + std::vector* 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* out) { + DCHECK(out); + std::vector 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* 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* 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* out) { + DCHECK(out); + size_t len = SafeGetArrayLength(env, array); + out->resize(len); + for (size_t i = 0; i < len; ++i) { + ScopedJavaLocalRef bytes_array( + env, static_cast( + 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(bytes), bytes_len); + env->ReleaseByteArrayElements(bytes_array.obj(), bytes, JNI_ABORT); + } +} + +void JavaArrayOfIntArrayToIntVector( + JNIEnv* env, + jobjectArray array, + std::vector>* out) { + DCHECK(out); + size_t len = SafeGetArrayLength(env, array); + out->resize(len); + for (size_t i = 0; i < len; ++i) { + ScopedJavaLocalRef int_array( + env, static_cast(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 0000000..66c56ef --- /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 +#include +#include +#include +#include + +#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 ToJavaByteArray(JNIEnv* env, + const uint8_t* bytes, + size_t len); + +BASE_EXPORT ScopedJavaLocalRef ToJavaByteArray( + JNIEnv* env, + const std::vector& bytes); + +// Returns a new Java boolean array converted from the given bool array. +BASE_EXPORT ScopedJavaLocalRef +ToJavaBooleanArray(JNIEnv* env, const bool* bools, size_t len); + +// Returns a new Java int array converted from the given int array. +BASE_EXPORT ScopedJavaLocalRef ToJavaIntArray( + JNIEnv* env, const int* ints, size_t len); + +BASE_EXPORT ScopedJavaLocalRef ToJavaIntArray( + JNIEnv* env, const std::vector& ints); + +// Returns a new Java long array converted from the given int64_t array. +BASE_EXPORT ScopedJavaLocalRef ToJavaLongArray(JNIEnv* env, + const int64_t* longs, + size_t len); + +BASE_EXPORT ScopedJavaLocalRef ToJavaLongArray( + JNIEnv* env, + const std::vector& longs); + +// Returns a new Java float array converted from the given C++ float array. +BASE_EXPORT ScopedJavaLocalRef ToJavaFloatArray( + JNIEnv* env, const float* floats, size_t len); + +BASE_EXPORT ScopedJavaLocalRef ToJavaFloatArray( + JNIEnv* env, + const std::vector& floats); + +// Returns a array of Java byte array converted from |v|. +BASE_EXPORT ScopedJavaLocalRef ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector& v); + +BASE_EXPORT ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v); + +BASE_EXPORT ScopedJavaLocalRef ToJavaArrayOfStrings( + JNIEnv* env, const std::vector& v); + +// Converts a Java string array to a native array. +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector* out); + +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector* out); + +// Appends the Java bytes in |bytes_array| onto the end of |out|. +BASE_EXPORT void AppendJavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector* out); + +// Replaces the content of |out| with the Java bytes in |bytes_array|. +BASE_EXPORT void JavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector* out); + +// Replaces the content of |out| with the Java booleans in |boolean_array|. +BASE_EXPORT void JavaBooleanArrayToBoolVector(JNIEnv* env, + jbooleanArray boolean_array, + std::vector* out); + +// Replaces the content of |out| with the Java ints in |int_array|. +BASE_EXPORT void JavaIntArrayToIntVector( + JNIEnv* env, + jintArray int_array, + std::vector* out); + +// Replaces the content of |out| with the Java longs in |long_array|. +BASE_EXPORT void JavaLongArrayToInt64Vector(JNIEnv* env, + jlongArray long_array, + std::vector* out); + +// Replaces the content of |out| with the Java longs in |long_array|. +BASE_EXPORT void JavaLongArrayToLongVector( + JNIEnv* env, + jlongArray long_array, + std::vector* out); + +// Replaces the content of |out| with the Java floats in |float_array|. +BASE_EXPORT void JavaFloatArrayToFloatVector( + JNIEnv* env, + jfloatArray float_array, + std::vector* 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* 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>* 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 0000000..ec28ff5 --- /dev/null +++ b/base/android/jni_generator/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/base/android/jni_generator/PRESUBMIT.py b/base/android/jni_generator/PRESUBMIT.py new file mode 100644 index 0000000..bc76d5b --- /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 42d8e56..ba3abe7 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> foo, LinkedList 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 0000000..31d027c --- /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 99d8b42..ef676e2 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.*?)\"\)\s+)?' + r'(@NativeCall(\(\"(?P.*?)\"\))\s+)?' + r'(?P\w+\s\w+|\w+|\s+)\s*native ' + r'(?P\S*) ' + r'(?Pnative\w+)\((?P.*?)\);') + +_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\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\w+?)\W') + re_inner = re.compile(r'(class|interface|enum)\s+?(?P\w+?)\W') for match in re.finditer(re_inner, contents): inner = match.group('name') - if not JniParams._fully_qualified_class.endswith(inner): - JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + + 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.*?)}?\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.*?)\"\)\s+)?' - '(@NativeCall(\(\"(?P.*?)\"\))\s+)?' - '(?P\w+\s\w+|\w+|\s+)\s*native ' - '(?P\S*) ' - '(?Pnative\w+)\((?P.*?)\);') - for match in re.finditer(re_native, contents): + 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)*?)(?:\("(?P.*)"\))?' - '\s+(?P[\w ]*?)' - '(:?\s*@\w+)?' # Ignore annotations in return types. - '\s*(?P\S+?)' - '\s+(?P\w+)' - '\s*\((?P[^\)]*)\)') - + r'@CalledByNative(?P(?:Unchecked)?)(?:\("(?P.*)"\))?' + r'(?:\s+@\w+(?:\(.*\))?)*' # Ignore any other annotations. + r'\s+(?P(' + r'(private|protected|public|static|abstract|final|default|synchronized)' + r'\s*)*)' + r'(?:\s*@\w+)?' # Ignore annotations in return types. + r'\s*(?P\S*?)' + r'\s*(?P\w+)' + r'\s*\((?P[^\)]*)\)') # Removes empty lines that are indented (i.e. start with 2x spaces). def RemoveIndentedEmptyLines(string): return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE) -def ExtractCalledByNatives(contents): +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. 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.*?);') 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& obj') + ', const base::android::JavaRef& 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 = '' + 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(${STUB_NAME}) },') - values = {'NAME': native.name, - 'JNI_SIGNATURE': JniParams.Signature(native.params, - native.return_type, - True), - 'STUB_NAME': self.GetStubName(native)} - return template.substitute(values) - - def GetUniqueClasses(self, origin): - ret = {self.class_name: self.fully_qualified_class} - for entry in origin: - class_name = self.class_name - jni_class_path = self.fully_qualified_class - if entry.java_class_name: - class_name = entry.java_class_name - jni_class_path = self.fully_qualified_class + '$' + class_name - ret[class_name] = jni_class_path - return ret - - def GetClassPathDefinitions(self): - """Returns the ClassPath constants.""" - ret = [] - template = Template("""\ -const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""") - all_classes = self.GetUniqueClasses(self.called_by_natives) - if self.options.native_exports_optional: - all_classes.update(self.GetUniqueClasses(self.natives)) - - for clazz in all_classes: - values = { - 'JAVA_CLASS': clazz, - 'JNI_CLASS_PATH': all_classes[clazz], - } - ret += [template.substitute(values)] - ret += '' - - template = Template("""\ -// Leaking this jclass as we cannot use LazyInstance from some threads. -base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0; -#define ${JAVA_CLASS}_clazz(env) \ -base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \ -&g_${JAVA_CLASS}_clazz)""") - - for clazz in all_classes: - values = { - 'JAVA_CLASS': clazz, - } - ret += [template.substitute(values)] - - return '\n'.join(ret) - - def GetMethodIDImpl(self, called_by_native): - """Returns the implementation of GetMethodID.""" - template = Template("""\ - base::android::MethodID::LazyGet< - base::android::MethodID::TYPE_${STATIC}>( - env, ${JAVA_CLASS}_clazz(env), - "${JNI_NAME}", - ${JNI_SIGNATURE}, - &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); -""") - jni_name = called_by_native.name - jni_return_type = called_by_native.return_type - if called_by_native.is_constructor: - jni_name = '' - jni_return_type = 'void' - if called_by_native.signature: - signature = called_by_native.signature - else: - signature = JniParams.Signature(called_by_native.params, - jni_return_type, - True) - values = { - 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, - 'JNI_NAME': jni_name, - 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, - 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE', - 'JNI_SIGNATURE': signature, - } - return template.substitute(values) + def 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 3062806..3347fee 100644 --- a/base/android/jni_generator/jni_generator_helper.h +++ b/base/android/jni_generator/jni_generator_helper.h @@ -8,8 +8,10 @@ #include #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 c0c8238..12812a5 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 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 extends java.util.AbstractSet implements java.util.Set, 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 { + """, + '@MainDex public class Test implements Testable {', + '@a.B @MainDex @C public class Test extends Testable {', + """public class Test extends Testable { + @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 0000000..8c545f6 --- /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 + +#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(${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 0000000..86f7e48 --- /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 42b2143..890103e 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& caller) { delete this; } @@ -76,31 +72,36 @@ ScopedJavaLocalRef CPPClass::ReturnAString( } // Static free functions declared and called directly from java. -static jlong Init(JNIEnv* env, - const JavaParamRef& caller, - const JavaParamRef& param) { +static jlong JNI_SampleForTests_Init(JNIEnv* env, + const JavaParamRef& caller, + const JavaParamRef& param) { return 0; } -static jdouble GetDoubleFunction(JNIEnv*, const JavaParamRef&) { +static jdouble JNI_SampleForTests_GetDoubleFunction( + JNIEnv*, + const JavaParamRef&) { return 0; } -static jfloat GetFloatFunction(JNIEnv*, const JavaParamRef&) { +static jfloat JNI_SampleForTests_GetFloatFunction(JNIEnv*, + const JavaParamRef&) { return 0; } -static void SetNonPODDatatype(JNIEnv*, - const JavaParamRef&, - const JavaParamRef&) {} +static void JNI_SampleForTests_SetNonPODDatatype(JNIEnv*, + const JavaParamRef&, + const JavaParamRef&) { +} -static ScopedJavaLocalRef GetNonPODDatatype( +static ScopedJavaLocalRef JNI_SampleForTests_GetNonPODDatatype( JNIEnv*, const JavaParamRef&) { return ScopedJavaLocalRef(); } -static jint GetInnerIntFunction(JNIEnv*, const JavaParamRef&) { +static jint JNI_InnerClass_GetInnerIntFunction(JNIEnv*, + const JavaParamRef&) { 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 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 a9cf7b0..6414158 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 ac86b2e..f0673f6 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 - Java_TestJni_showConfirmInfoBar(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper nativeInfoBar, - const base::android::JavaRefOrBare& buttonOk, - const base::android::JavaRefOrBare& buttonCancel, - const base::android::JavaRefOrBare& title, - const base::android::JavaRefOrBare& icon) { +// Step 3: Method stubs. + +static base::subtle::AtomicWord g_org_chromium_TestJni_showConfirmInfoBar = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_showConfirmInfoBar(JNIEnv* env, const + base::android::JavaRef& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRef& buttonOk, + const base::android::JavaRef& buttonCancel, + const base::android::JavaRef& title, + const base::android::JavaRef& 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(env, ret); } -static base::subtle::AtomicWord g_TestJni_showAutoLoginInfoBar = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_showAutoLoginInfoBar(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper nativeInfoBar, - const base::android::JavaRefOrBare& realm, - const base::android::JavaRefOrBare& account, - const base::android::JavaRefOrBare& args) { +static base::subtle::AtomicWord g_org_chromium_TestJni_showAutoLoginInfoBar = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_showAutoLoginInfoBar(JNIEnv* env, + const base::android::JavaRef& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRef& realm, + const base::android::JavaRef& account, + const base::android::JavaRef& 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(env, ret); } -static base::subtle::AtomicWord g_InfoBar_dismiss = 0; -static void Java_InfoBar_dismiss(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_00024InfoBar_dismiss = 0; +static void Java_InfoBar_dismiss(JNIEnv* env, const base::android::JavaRef& 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& view, - const base::android::JavaRefOrBare& realm, - const base::android::JavaRefOrBare& account, - const base::android::JavaRefOrBare& args) { - CHECK_CLAZZ(env, TestJni_clazz(env), - TestJni_clazz(env), false); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_org_chromium_TestJni_shouldShowAutoLogin = 0; +static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, const base::android::JavaRef& + view, + const base::android::JavaRef& realm, + const base::android::JavaRef& account, + const base::android::JavaRef& 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 Java_TestJni_openUrl(JNIEnv* - env, const base::android::JavaRefOrBare& url) { - CHECK_CLAZZ(env, TestJni_clazz(env), - TestJni_clazz(env), NULL); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_org_chromium_TestJni_openUrl = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_openUrl(JNIEnv* env, const + base::android::JavaRef& 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(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& obj, jboolean activated, + base::android::JavaRef& 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& obj, JniIntWrapper iParam) { +static base::subtle::AtomicWord g_org_chromium_TestJni_uncheckedCall = 0; +static void Java_TestJni_uncheckedCall(JNIEnv* env, const base::android::JavaRef& 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 - Java_TestJni_returnByteArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnByteArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnByteArray(JNIEnv* env, const + base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -262,21 +226,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnBooleanArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnBooleanArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnBooleanArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnBooleanArray(JNIEnv* env, + const base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -285,21 +245,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnCharArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnCharArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnCharArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnCharArray(JNIEnv* env, const + base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -308,21 +264,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnShortArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnShortArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnShortArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnShortArray(JNIEnv* env, + const base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -331,21 +283,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnIntArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnIntArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnIntArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnIntArray(JNIEnv* env, const + base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -354,21 +302,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnLongArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnLongArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnLongArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnLongArray(JNIEnv* env, const + base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -377,21 +321,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnDoubleArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnDoubleArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnDoubleArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnDoubleArray(JNIEnv* env, + const base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -400,21 +340,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnObjectArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnObjectArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnObjectArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnObjectArray(JNIEnv* env, + const base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -423,21 +359,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_returnArrayOfByteArray = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_returnArrayOfByteArray(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_returnArrayOfByteArray = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_returnArrayOfByteArray(JNIEnv* + env, const base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -446,21 +378,17 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_getCompressFormat = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_getCompressFormat(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_getCompressFormat = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_getCompressFormat(JNIEnv* env, const + base::android::JavaRef& 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 return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_TestJni_getCompressFormatList = 0; -static base::android::ScopedJavaLocalRef - Java_TestJni_getCompressFormatList(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_org_chromium_TestJni_getCompressFormatList = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_getCompressFormatList(JNIEnv* env, + const base::android::JavaRef& 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 return base::android::ScopedJavaLocalRef(env, ret); } -// Step 3: RegisterNatives. - #endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testConstantsFromJavaP.golden b/base/android/jni_generator/testConstantsFromJavaP.golden index b16956f..9cb39b7 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& obj) __attribute__ ((unused)); -static void Java_MotionEvent_finalize(JNIEnv* env, const - base::android::JavaRefOrBare& 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& obj) + __attribute__ ((unused)); +static void Java_MotionEvent_finalize(JNIEnv* env, const base::android::JavaRef& 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 - 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& p4, - const base::android::JavaRefOrBare& p5, + const base::android::JavaRef& p4, + const base::android::JavaRef& p5, JniIntWrapper p6, JniIntWrapper p7, jfloat p8, @@ -151,13 +160,12 @@ static base::android::ScopedJavaLocalRef JniIntWrapper p12, JniIntWrapper p13) __attribute__ ((unused)); static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* - env, jlong p0, + Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* env, jlong p0, jlong p1, JniIntWrapper p2, JniIntWrapper p3, - const base::android::JavaRefOrBare& p4, - const base::android::JavaRefOrBare& p5, + const base::android::JavaRef& p4, + const base::android::JavaRef& p5, JniIntWrapper p6, JniIntWrapper p7, jfloat p8, @@ -166,35 +174,32 @@ static base::android::ScopedJavaLocalRef 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(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 - 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& p4, - const base::android::JavaRefOrBare& p5, + const base::android::JavaRef& p4, + const base::android::JavaRef& p5, JniIntWrapper p6, jfloat p7, jfloat p8, @@ -203,13 +208,12 @@ static base::android::ScopedJavaLocalRef JniIntWrapper p11, JniIntWrapper p12) __attribute__ ((unused)); static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, - jlong p0, + Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, jlong p0, jlong p1, JniIntWrapper p2, JniIntWrapper p3, - const base::android::JavaRefOrBare& p4, - const base::android::JavaRefOrBare& p5, + const base::android::JavaRef& p4, + const base::android::JavaRef& p5, JniIntWrapper p6, jfloat p7, jfloat p8, @@ -217,27 +221,24 @@ static base::android::ScopedJavaLocalRef 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(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 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 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(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 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 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(env, ret); } -static base::subtle::AtomicWord g_MotionEvent_obtainAVME_J_J_I_F_F_I = 0; -static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0, +static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_I = 0; +static base::android::ScopedJavaLocalRef Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* + env, jlong p0, jlong p1, JniIntWrapper p2, jfloat p3, jfloat p4, JniIntWrapper p5) __attribute__ ((unused)); -static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0, +static base::android::ScopedJavaLocalRef Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* + env, jlong p0, jlong p1, JniIntWrapper p2, jfloat p3, jfloat p4, JniIntWrapper p5) { - CHECK_CLAZZ(env, MotionEvent_clazz(env), - MotionEvent_clazz(env), NULL); - jmethodID method_id = - base::android::MethodID::LazyGet< + 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(env, ret); } -static base::subtle::AtomicWord g_MotionEvent_obtainAVME_AVME = 0; -static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const - base::android::JavaRefOrBare& p0) __attribute__ ((unused)); -static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const - base::android::JavaRefOrBare& p0) { - CHECK_CLAZZ(env, MotionEvent_clazz(env), - MotionEvent_clazz(env), NULL); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_AVME = 0; +static base::android::ScopedJavaLocalRef Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, + const base::android::JavaRef& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, + const base::android::JavaRef& 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(env, ret); } -static base::subtle::AtomicWord g_MotionEvent_obtainNoHistory = 0; -static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainNoHistory(JNIEnv* env, const - base::android::JavaRefOrBare& p0) __attribute__ ((unused)); -static base::android::ScopedJavaLocalRef - Java_MotionEvent_obtainNoHistory(JNIEnv* env, const - base::android::JavaRefOrBare& p0) { - CHECK_CLAZZ(env, MotionEvent_clazz(env), - MotionEvent_clazz(env), NULL); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_android_view_MotionEvent_obtainNoHistory = 0; +static base::android::ScopedJavaLocalRef Java_MotionEvent_obtainNoHistory(JNIEnv* env, + const base::android::JavaRef& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef Java_MotionEvent_obtainNoHistory(JNIEnv* env, + const base::android::JavaRef& 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(env, ret); } -static base::subtle::AtomicWord g_MotionEvent_recycle = 0; -static void Java_MotionEvent_recycle(JNIEnv* env, const - base::android::JavaRefOrBare& obj) __attribute__ ((unused)); -static void Java_MotionEvent_recycle(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_recycle = 0; +static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getDeviceId = 0; +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getSource(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getSource = 0; +static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static void Java_MotionEvent_setSource(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { +static base::subtle::AtomicWord g_android_view_MotionEvent_setSource = 0; +static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getAction(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getAction = 0; +static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getActionMasked = 0; +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getActionIndex = 0; +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getFlags(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getFlags = 0; +static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getDownTime = 0; +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getEventTime = 0; +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getXF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getXF = 0; +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getYF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getYF = 0; +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getPressureF = 0; +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getSizeF = 0; +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMajorF = 0; +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMinorF = 0; +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMajorF = 0; +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMinorF = 0; +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getOrientationF = 0; +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerCount = 0; +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jint Java_MotionEvent_getPointerId(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerId = 0; +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jint Java_MotionEvent_getToolType(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getToolType = 0; +static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { +static base::subtle::AtomicWord g_android_view_MotionEvent_findPointerIndex = 0; +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const base::android::JavaRef& + 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& 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& + obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, +static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0, - const base::android::JavaRefOrBare& p1) __attribute__ ((unused)); -static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, - const base::android::JavaRefOrBare& p1) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerCoords = 0; +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const base::android::JavaRef& + obj, JniIntWrapper p0, + const base::android::JavaRef& p1) __attribute__ ((unused)); +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const base::android::JavaRef& + obj, JniIntWrapper p0, + const base::android::JavaRef& 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& obj, JniIntWrapper p0, - const base::android::JavaRefOrBare& p1) __attribute__ ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0, + const base::android::JavaRef& p1) __attribute__ ((unused)); static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, - const base::android::JavaRefOrBare& p1) { + base::android::JavaRef& obj, JniIntWrapper p0, + const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getMetaState(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getMetaState = 0; +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getButtonState(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getButtonState = 0; +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getRawX = 0; +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getRawY = 0; +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getXPrecision = 0; +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getYPrecision = 0; +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getHistorySize = 0; +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const - base::android::JavaRefOrBare& 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& + obj, JniIntWrapper p0) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const base::android::JavaRef& + 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); + base::android::JavaRef& obj, JniIntWrapper p0) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1, JniIntWrapper p2) __attribute__ ((unused)); static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& 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& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1, - const base::android::JavaRefOrBare& p2) __attribute__ ((unused)); + const base::android::JavaRef& p2) __attribute__ ((unused)); static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0, + base::android::JavaRef& obj, JniIntWrapper p0, JniIntWrapper p1, - const base::android::JavaRefOrBare& p2) { + const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_getEdgeFlags = 0; +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { +static base::subtle::AtomicWord g_android_view_MotionEvent_setEdgeFlags = 0; +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static void Java_MotionEvent_setAction(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { +static base::subtle::AtomicWord g_android_view_MotionEvent_setAction = 0; +static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef& 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& obj, jfloat p0, +static base::subtle::AtomicWord g_android_view_MotionEvent_offsetLocation = 0; +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const base::android::JavaRef& obj, + jfloat p0, jfloat p1) __attribute__ ((unused)); -static void Java_MotionEvent_offsetLocation(JNIEnv* env, const - base::android::JavaRefOrBare& obj, jfloat p0, +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const base::android::JavaRef& 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& obj, jfloat p0, +static base::subtle::AtomicWord g_android_view_MotionEvent_setLocation = 0; +static void Java_MotionEvent_setLocation(JNIEnv* env, const base::android::JavaRef& obj, + jfloat p0, jfloat p1) __attribute__ ((unused)); -static void Java_MotionEvent_setLocation(JNIEnv* env, const - base::android::JavaRefOrBare& obj, jfloat p0, +static void Java_MotionEvent_setLocation(JNIEnv* env, const base::android::JavaRef& 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& obj, const - base::android::JavaRefOrBare& p0) __attribute__ ((unused)); -static void Java_MotionEvent_transform(JNIEnv* env, const - base::android::JavaRefOrBare& obj, const - base::android::JavaRefOrBare& p0) { +static base::subtle::AtomicWord g_android_view_MotionEvent_transform = 0; +static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef& obj, + const base::android::JavaRef& p0) __attribute__ ((unused)); +static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef& obj, + const base::android::JavaRef& 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& obj, jlong p0, + base::android::JavaRef& obj, jlong p0, jfloat p1, jfloat p2, jfloat p3, jfloat p4, JniIntWrapper p5) __attribute__ ((unused)); static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, jlong p0, + base::android::JavaRef& 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& obj, jlong p0, - const base::android::JavaRefOrBare& p1, + base::android::JavaRef& obj, jlong p0, + const base::android::JavaRef& p1, JniIntWrapper p2) __attribute__ ((unused)); static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, jlong p0, - const base::android::JavaRefOrBare& p1, + base::android::JavaRef& obj, jlong p0, + const base::android::JavaRef& 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 - Java_MotionEvent_toString(JNIEnv* env, const - base::android::JavaRefOrBare& obj) __attribute__ ((unused)); -static base::android::ScopedJavaLocalRef - Java_MotionEvent_toString(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_android_view_MotionEvent_toString = 0; +static base::android::ScopedJavaLocalRef Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRef& obj) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), @@ -2096,100 +1970,90 @@ static base::android::ScopedJavaLocalRef return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_MotionEvent_actionToString = 0; -static base::android::ScopedJavaLocalRef - Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) __attribute__ - ((unused)); -static base::android::ScopedJavaLocalRef - Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) { - CHECK_CLAZZ(env, MotionEvent_clazz(env), - MotionEvent_clazz(env), NULL); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_android_view_MotionEvent_actionToString = 0; +static base::android::ScopedJavaLocalRef Java_MotionEvent_actionToString(JNIEnv* env, + JniIntWrapper p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef Java_MotionEvent_actionToString(JNIEnv* env, + JniIntWrapper p0) { + CHECK_CLAZZ(env, 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(env->CallStaticObjectMethod(MotionEvent_clazz(env), + static_cast(env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), method_id, as_jint(p0))); jni_generator::CheckException(env); return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_MotionEvent_axisToString = 0; -static base::android::ScopedJavaLocalRef - Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) __attribute__ - ((unused)); -static base::android::ScopedJavaLocalRef - Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) { - CHECK_CLAZZ(env, MotionEvent_clazz(env), - MotionEvent_clazz(env), NULL); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_android_view_MotionEvent_axisToString = 0; +static base::android::ScopedJavaLocalRef Java_MotionEvent_axisToString(JNIEnv* env, + JniIntWrapper p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef Java_MotionEvent_axisToString(JNIEnv* env, + JniIntWrapper p0) { + CHECK_CLAZZ(env, 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(env->CallStaticObjectMethod(MotionEvent_clazz(env), + static_cast(env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env), method_id, as_jint(p0))); jni_generator::CheckException(env); return base::android::ScopedJavaLocalRef(env, ret); } -static base::subtle::AtomicWord g_MotionEvent_axisFromString = 0; -static jint Java_MotionEvent_axisFromString(JNIEnv* env, const - base::android::JavaRefOrBare& p0) __attribute__ ((unused)); -static jint Java_MotionEvent_axisFromString(JNIEnv* env, const - base::android::JavaRefOrBare& p0) { - CHECK_CLAZZ(env, MotionEvent_clazz(env), - MotionEvent_clazz(env), 0); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_android_view_MotionEvent_axisFromString = 0; +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef& p0) + __attribute__ ((unused)); +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef& 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& obj, const - base::android::JavaRefOrBare& p0, +static base::subtle::AtomicWord g_android_view_MotionEvent_writeToParcel = 0; +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const base::android::JavaRef& obj, + const base::android::JavaRef& p0, JniIntWrapper p1) __attribute__ ((unused)); -static void Java_MotionEvent_writeToParcel(JNIEnv* env, const - base::android::JavaRefOrBare& obj, const - base::android::JavaRefOrBare& p0, +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const base::android::JavaRef& obj, + const base::android::JavaRef& 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 18a9430..ca5b050 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& obj) __attribute__ ((unused)); -static jint Java_InputStream_available(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_java_io_InputStream_available = 0; +static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static void Java_InputStream_close(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_java_io_InputStream_close = 0; +static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef& 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& obj, JniIntWrapper p0) __attribute__ - ((unused)); -static void Java_InputStream_mark(JNIEnv* env, const - base::android::JavaRefOrBare& obj, JniIntWrapper p0) { +static base::subtle::AtomicWord g_java_io_InputStream_mark = 0; +static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef& obj, + JniIntWrapper p0) __attribute__ ((unused)); +static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static jboolean Java_InputStream_markSupported(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_java_io_InputStream_markSupported = 0; +static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef& + obj) __attribute__ ((unused)); +static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef& + 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& obj) __attribute__ ((unused)); -static jint Java_InputStream_readI(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_java_io_InputStream_readI = 0; +static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef& 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& obj, const - base::android::JavaRefOrBare& p0) __attribute__ ((unused)); -static jint Java_InputStream_readI_AB(JNIEnv* env, const - base::android::JavaRefOrBare& obj, const - base::android::JavaRefOrBare& p0) { +static base::subtle::AtomicWord g_java_io_InputStream_readI_AB = 0; +static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef& obj, const + base::android::JavaRef& p0) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef& obj, const + base::android::JavaRef& 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& obj, const - base::android::JavaRefOrBare& 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& obj, + const base::android::JavaRef& p0, JniIntWrapper p1, JniIntWrapper p2) __attribute__ ((unused)); -static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const - base::android::JavaRefOrBare& obj, const - base::android::JavaRefOrBare& p0, +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const base::android::JavaRef& obj, + const base::android::JavaRef& 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& obj) __attribute__ ((unused)); -static void Java_InputStream_reset(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_java_io_InputStream_reset = 0; +static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef& 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& obj, jlong p0) __attribute__ - ((unused)); -static jlong Java_InputStream_skip(JNIEnv* env, const - base::android::JavaRefOrBare& obj, jlong p0) { +static base::subtle::AtomicWord g_java_io_InputStream_skip = 0; +static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef& obj, jlong + p0) __attribute__ ((unused)); +static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef& 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 - Java_InputStream_Constructor(JNIEnv* env) __attribute__ ((unused)); -static base::android::ScopedJavaLocalRef - Java_InputStream_Constructor(JNIEnv* env) { - CHECK_CLAZZ(env, InputStream_clazz(env), - InputStream_clazz(env), NULL); - jmethodID method_id = - base::android::MethodID::LazyGet< +static base::subtle::AtomicWord g_java_io_InputStream_Constructor = 0; +static base::android::ScopedJavaLocalRef Java_InputStream_Constructor(JNIEnv* env) + __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef Java_InputStream_Constructor(JNIEnv* env) { + CHECK_CLAZZ(env, 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), - "", - "()V", - &g_InputStream_Constructor); + env, java_io_InputStream_clazz(env), + "", + "()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(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 c076c39..25c5e0e 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& obj) __attribute__ ((unused)); -static void Java_HashSet_dummy(JNIEnv* env, const - base::android::JavaRefOrBare& obj) { +static base::subtle::AtomicWord g_java_util_HashSet_dummy = 0; +static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef& obj) + __attribute__ ((unused)); +static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef& 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 Java_HashSet_getClass(JNIEnv* env, const + base::android::JavaRef& obj) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef Java_HashSet_getClass(JNIEnv* env, const + base::android::JavaRef& 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(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} } // namespace JNI_HashSet diff --git a/base/android/jni_generator/testInnerClassNatives.golden b/base/android/jni_generator/testInnerClassNatives.golden index 20b8830..2c89de9 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& - 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(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(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& 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(env, jcaller)); } + #endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden index 67352e7..0623b37 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& - 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& jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit( + JNIEnv* env, jobject jcaller) { - return Init(env, base::android::JavaParamRef(env, jcaller)); + return JNI_TestJni_Init(env, base::android::JavaParamRef(env, jcaller)); } -static jint Init(JNIEnv* env, const base::android::JavaParamRef& +static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef& 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(env, jcaller)); + return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef(env, jcaller)); } -// Step 3: RegisterNatives. - -static const JNINativeMethod kMethodsMyOtherInnerClass[] = { - { "nativeInit", -"(" -")" -"I", - reinterpret_cast(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) - }, -}; - -static const JNINativeMethod kMethodsTestJni[] = { - { "nativeInit", -"(" -")" -"I", reinterpret_cast(Java_org_chromium_TestJni_nativeInit) }, -}; - -static bool RegisterNativesImpl(JNIEnv* env) { - if (jni_generator::ShouldSkipJniRegistration(false)) - return true; - - const int kMethodsMyOtherInnerClassSize = - arraysize(kMethodsMyOtherInnerClass); - - if (env->RegisterNatives(MyOtherInnerClass_clazz(env), - kMethodsMyOtherInnerClass, - kMethodsMyOtherInnerClassSize) < 0) { - jni_generator::HandleRegistrationError( - env, MyOtherInnerClass_clazz(env), __FILE__); - return false; - } - - const int kMethodsTestJniSize = arraysize(kMethodsTestJni); - - if (env->RegisterNatives(TestJni_clazz(env), - kMethodsTestJni, - kMethodsTestJniSize) < 0) { - jni_generator::HandleRegistrationError( - env, TestJni_clazz(env), __FILE__); - return false; - } - - return true; -} #endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden new file mode 100644 index 0000000..3f91cf1 --- /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 + +#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(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) }, +}; + + +static const JNINativeMethod kMethods_org_chromium_TestJni[] = { + { "nativeInit", "()I", reinterpret_cast(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 7807efa..a7eff72 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& - jcaller); -JNI_GENERATOR_EXPORT jint - Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(JNIEnv* env, jobject - jcaller) { - return Init(env, base::android::JavaParamRef(env, jcaller)); +// Step 3: Method stubs. +static jint JNI_MyInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef& jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyInnerClass_nativeInit( + JNIEnv* env, + jobject jcaller) { + return JNI_MyInnerClass_Init(env, base::android::JavaParamRef(env, jcaller)); } -static jint Init(JNIEnv* env, const base::android::JavaParamRef& +static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef& 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(env, jcaller)); + return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef(env, jcaller)); } -// Step 3: RegisterNatives. - -static const JNINativeMethod kMethodsMyOtherInnerClass[] = { - { "nativeInit", -"(" -")" -"I", - reinterpret_cast(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) - }, -}; - -static const JNINativeMethod kMethodsMyInnerClass[] = { - { "nativeInit", -"(" -")" -"I", - reinterpret_cast(Java_org_chromium_TestJni_00024MyInnerClass_nativeInit) - }, -}; - -static bool RegisterNativesImpl(JNIEnv* env) { - if (jni_generator::ShouldSkipJniRegistration(false)) - return true; - - const int kMethodsMyOtherInnerClassSize = - arraysize(kMethodsMyOtherInnerClass); - - if (env->RegisterNatives(MyOtherInnerClass_clazz(env), - kMethodsMyOtherInnerClass, - kMethodsMyOtherInnerClassSize) < 0) { - jni_generator::HandleRegistrationError( - env, MyOtherInnerClass_clazz(env), __FILE__); - return false; - } - - const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); - - if (env->RegisterNatives(MyInnerClass_clazz(env), - kMethodsMyInnerClass, - kMethodsMyInnerClassSize) < 0) { - jni_generator::HandleRegistrationError( - env, MyInnerClass_clazz(env), __FILE__); - return false; - } - - return true; -} #endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testMultipleJNIAdditionalImport.golden b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden index 0eecb5a..40f3d6b 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& - jcaller, +// Step 3: Method stubs. +static void JNI_Foo_DoSomething(JNIEnv* env, const base::android::JavaParamRef& jcaller, const base::android::JavaParamRef& callback1, const base::android::JavaParamRef& callback2); -JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv* - env, jclass jcaller, +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething( + JNIEnv* env, + jclass jcaller, jobject callback1, jobject callback2) { - return DoSomething(env, base::android::JavaParamRef(env, jcaller), + return JNI_Foo_DoSomething(env, base::android::JavaParamRef(env, jcaller), base::android::JavaParamRef(env, callback1), base::android::JavaParamRef(env, callback2)); } -static base::subtle::AtomicWord g_Foo_calledByNative = 0; -static void Java_Foo_calledByNative(JNIEnv* env, const - base::android::JavaRefOrBare& callback1, - const base::android::JavaRefOrBare& callback2) { - CHECK_CLAZZ(env, Foo_clazz(env), - Foo_clazz(env)); - jmethodID method_id = - base::android::MethodID::LazyGet< + +static base::subtle::AtomicWord g_org_chromium_foo_Foo_calledByNative = 0; +static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef& callback1, + const base::android::JavaRef& 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(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 3362c92..1178f19 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& - jcaller); +// Step 3: Method stubs. +static jint JNI_TestJni_Init(JNIEnv* env, const base::android::JavaParamRef& 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(env, jcaller)); + return JNI_TestJni_Init(env, base::android::JavaParamRef(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(nativeChromeBrowserProvider); CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); - return native->Destroy(env, base::android::JavaParamRef(env, - jcaller)); + return native->Destroy(env, base::android::JavaParamRef(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(nativeChromeBrowserProvider); CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmark", 0); - return native->AddBookmark(env, base::android::JavaParamRef(env, - jcaller), base::android::JavaParamRef(env, url), - base::android::JavaParamRef(env, title), isFolder, parentId); + return native->AddBookmark(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, url), base::android::JavaParamRef(env, + title), isFolder, parentId); } -static base::android::ScopedJavaLocalRef GetDomainAndRegistry(JNIEnv* - env, const base::android::JavaParamRef& jcaller, +static base::android::ScopedJavaLocalRef JNI_TestJni_GetDomainAndRegistry(JNIEnv* env, + const base::android::JavaParamRef& jcaller, const base::android::JavaParamRef& url); -JNI_GENERATOR_EXPORT jstring - Java_org_chromium_TestJni_nativeGetDomainAndRegistry(JNIEnv* env, jclass - jcaller, +JNI_GENERATOR_EXPORT jstring Java_org_chromium_TestJni_nativeGetDomainAndRegistry( + JNIEnv* env, + jclass jcaller, jstring url) { - return GetDomainAndRegistry(env, base::android::JavaParamRef(env, - jcaller), base::android::JavaParamRef(env, url)).Release(); + return JNI_TestJni_GetDomainAndRegistry(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, url)).Release(); } -static void CreateHistoricalTabFromState(JNIEnv* env, const +static void JNI_TestJni_CreateHistoricalTabFromState(JNIEnv* env, const base::android::JavaParamRef& jcaller, const base::android::JavaParamRef& state, jint tab_index); -JNI_GENERATOR_EXPORT void - Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState(JNIEnv* env, +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState( + JNIEnv* env, jclass jcaller, jbyteArray state, jint tab_index) { - return CreateHistoricalTabFromState(env, - base::android::JavaParamRef(env, jcaller), - base::android::JavaParamRef(env, state), tab_index); + return JNI_TestJni_CreateHistoricalTabFromState(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, state), tab_index); } -static base::android::ScopedJavaLocalRef GetStateAsByteArray(JNIEnv* - env, const base::android::JavaParamRef& jcaller, +static base::android::ScopedJavaLocalRef JNI_TestJni_GetStateAsByteArray(JNIEnv* env, + const base::android::JavaParamRef& jcaller, const base::android::JavaParamRef& view); -JNI_GENERATOR_EXPORT jbyteArray - Java_org_chromium_TestJni_nativeGetStateAsByteArray(JNIEnv* env, jobject - jcaller, +JNI_GENERATOR_EXPORT jbyteArray Java_org_chromium_TestJni_nativeGetStateAsByteArray( + JNIEnv* env, + jobject jcaller, jobject view) { - return GetStateAsByteArray(env, base::android::JavaParamRef(env, - jcaller), base::android::JavaParamRef(env, view)).Release(); + return JNI_TestJni_GetStateAsByteArray(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, view)).Release(); } -static base::android::ScopedJavaLocalRef - GetAutofillProfileGUIDs(JNIEnv* env, const - base::android::JavaParamRef& jcaller); +static base::android::ScopedJavaLocalRef JNI_TestJni_GetAutofillProfileGUIDs(JNIEnv* + env, const base::android::JavaParamRef& jcaller); -JNI_GENERATOR_EXPORT jobjectArray - Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs(JNIEnv* env, jclass - jcaller) { - return GetAutofillProfileGUIDs(env, base::android::JavaParamRef(env, +JNI_GENERATOR_EXPORT jobjectArray Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs( + JNIEnv* env, + jclass jcaller) { + return JNI_TestJni_GetAutofillProfileGUIDs(env, base::android::JavaParamRef(env, jcaller)).Release(); } -static void SetRecognitionResults(JNIEnv* env, const +static void JNI_TestJni_SetRecognitionResults(JNIEnv* env, const base::android::JavaParamRef& jcaller, jint sessionId, const base::android::JavaParamRef& results); -JNI_GENERATOR_EXPORT void - Java_org_chromium_TestJni_nativeSetRecognitionResults(JNIEnv* env, jobject - jcaller, +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeSetRecognitionResults( + JNIEnv* env, + jobject jcaller, jint sessionId, jobjectArray results) { - return SetRecognitionResults(env, base::android::JavaParamRef(env, - jcaller), sessionId, base::android::JavaParamRef(env, - results)); + return JNI_TestJni_SetRecognitionResults(env, base::android::JavaParamRef(env, jcaller), + sessionId, base::android::JavaParamRef(env, results)); } -JNI_GENERATOR_EXPORT jlong - Java_org_chromium_TestJni_nativeAddBookmarkFromAPI(JNIEnv* env, jobject - jcaller, +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(nativeChromeBrowserProvider); CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmarkFromAPI", 0); - return native->AddBookmarkFromAPI(env, - base::android::JavaParamRef(env, jcaller), - base::android::JavaParamRef(env, url), - base::android::JavaParamRef(env, created), - base::android::JavaParamRef(env, isBookmark), - base::android::JavaParamRef(env, date), - base::android::JavaParamRef(env, favicon), - base::android::JavaParamRef(env, title), + return native->AddBookmarkFromAPI(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, url), base::android::JavaParamRef(env, + created), base::android::JavaParamRef(env, isBookmark), + base::android::JavaParamRef(env, date), base::android::JavaParamRef(env, + favicon), base::android::JavaParamRef(env, title), base::android::JavaParamRef(env, visits)); } -static jint FindAll(JNIEnv* env, const base::android::JavaParamRef& - jcaller, +static jint JNI_TestJni_FindAll(JNIEnv* env, const base::android::JavaParamRef& jcaller, const base::android::JavaParamRef& 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(env, jcaller), + return JNI_TestJni_FindAll(env, base::android::JavaParamRef(env, jcaller), base::android::JavaParamRef(env, find)); } -static base::android::ScopedJavaLocalRef GetInnerClass(JNIEnv* env, - const base::android::JavaParamRef& jcaller); +static base::android::ScopedJavaLocalRef JNI_TestJni_GetInnerClass(JNIEnv* env, const + base::android::JavaParamRef& jcaller); -JNI_GENERATOR_EXPORT jobject - Java_org_chromium_TestJni_nativeGetInnerClass(JNIEnv* env, jclass jcaller) { - return GetInnerClass(env, base::android::JavaParamRef(env, +JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeGetInnerClass( + JNIEnv* env, + jclass jcaller) { + return JNI_TestJni_GetInnerClass(env, base::android::JavaParamRef(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(nativeChromeBrowserProvider); CHECK_NATIVE_PTR(env, jcaller, native, "QueryBitmap", NULL); - return native->QueryBitmap(env, base::android::JavaParamRef(env, - jcaller), base::android::JavaParamRef(env, projection), + return native->QueryBitmap(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, projection), base::android::JavaParamRef(env, selection), base::android::JavaParamRef(env, selectionArgs), base::android::JavaParamRef(env, sortOrder)).Release(); } -JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation(JNIEnv* - env, jobject jcaller, +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(nativeDataFetcherImplAndroid); CHECK_NATIVE_PTR(env, jcaller, native, "GotOrientation"); - return native->GotOrientation(env, base::android::JavaParamRef(env, - jcaller), alpha, beta, gamma); + return native->GotOrientation(env, base::android::JavaParamRef(env, jcaller), alpha, + beta, gamma); } -static base::android::ScopedJavaLocalRef - MessWithJavaException(JNIEnv* env, const - base::android::JavaParamRef& jcaller, +static base::android::ScopedJavaLocalRef JNI_TestJni_MessWithJavaException(JNIEnv* env, + const base::android::JavaParamRef& jcaller, const base::android::JavaParamRef& e); -JNI_GENERATOR_EXPORT jthrowable - Java_org_chromium_TestJni_nativeMessWithJavaException(JNIEnv* env, jclass - jcaller, +JNI_GENERATOR_EXPORT jthrowable Java_org_chromium_TestJni_nativeMessWithJavaException( + JNIEnv* env, + jclass jcaller, jthrowable e) { - return MessWithJavaException(env, base::android::JavaParamRef(env, - jcaller), base::android::JavaParamRef(env, e)).Release(); + return JNI_TestJni_MessWithJavaException(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, e)).Release(); } -// Step 3: RegisterNatives. - -static const JNINativeMethod kMethodsTestJni[] = { - { "nativeInit", -"(" -")" -"I", reinterpret_cast(Java_org_chromium_TestJni_nativeInit) }, - { "nativeDestroy", -"(" -"I" -")" -"V", reinterpret_cast(Java_org_chromium_TestJni_nativeDestroy) }, - { "nativeAddBookmark", -"(" -"I" -"Ljava/lang/String;" -"Ljava/lang/String;" -"Z" -"J" -")" -"J", reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmark) }, - { "nativeGetDomainAndRegistry", -"(" -"Ljava/lang/String;" -")" -"Ljava/lang/String;", - reinterpret_cast(Java_org_chromium_TestJni_nativeGetDomainAndRegistry) - }, - { "nativeCreateHistoricalTabFromState", -"(" -"[B" -"I" -")" -"V", - reinterpret_cast(Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState) - }, - { "nativeGetStateAsByteArray", -"(" -"Landroid/view/View;" -")" -"[B", - reinterpret_cast(Java_org_chromium_TestJni_nativeGetStateAsByteArray) - }, - { "nativeGetAutofillProfileGUIDs", -"(" -")" -"[Ljava/lang/String;", - reinterpret_cast(Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs) - }, - { "nativeSetRecognitionResults", -"(" -"I" -"[Ljava/lang/String;" -")" -"V", - reinterpret_cast(Java_org_chromium_TestJni_nativeSetRecognitionResults) - }, - { "nativeAddBookmarkFromAPI", -"(" -"I" -"Ljava/lang/String;" -"Ljava/lang/Long;" -"Ljava/lang/Boolean;" -"Ljava/lang/Long;" -"[B" -"Ljava/lang/String;" -"Ljava/lang/Integer;" -")" -"J", reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmarkFromAPI) - }, - { "nativeFindAll", -"(" -"Ljava/lang/String;" -")" -"I", reinterpret_cast(Java_org_chromium_TestJni_nativeFindAll) }, - { "nativeGetInnerClass", -"(" -")" -"Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", - reinterpret_cast(Java_org_chromium_TestJni_nativeGetInnerClass) }, - { "nativeQueryBitmap", -"(" -"I" -"[Ljava/lang/String;" -"Ljava/lang/String;" -"[Ljava/lang/String;" -"Ljava/lang/String;" -")" -"Landroid/graphics/Bitmap;", - reinterpret_cast(Java_org_chromium_TestJni_nativeQueryBitmap) }, - { "nativeGotOrientation", -"(" -"I" -"D" -"D" -"D" -")" -"V", reinterpret_cast(Java_org_chromium_TestJni_nativeGotOrientation) }, - { "nativeMessWithJavaException", -"(" -"Ljava/lang/Throwable;" -")" -"Ljava/lang/Throwable;", - reinterpret_cast(Java_org_chromium_TestJni_nativeMessWithJavaException) - }, -}; - -static bool RegisterNativesImpl(JNIEnv* env) { - if (jni_generator::ShouldSkipJniRegistration(false)) - return true; - - const int kMethodsTestJniSize = arraysize(kMethodsTestJni); - - if (env->RegisterNatives(TestJni_clazz(env), - kMethodsTestJni, - kMethodsTestJniSize) < 0) { - jni_generator::HandleRegistrationError( - env, TestJni_clazz(env), __FILE__); - return false; - } - - return true; -} #endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testNativesLong.golden b/base/android/jni_generator/testNativesLong.golden index ec029ce..b3115ef 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(nativeChromeBrowserProvider); CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); - return native->Destroy(env, base::android::JavaParamRef(env, - jcaller)); + return native->Destroy(env, base::android::JavaParamRef(env, jcaller)); } -// Step 3: RegisterNatives. - -static const JNINativeMethod kMethodsTestJni[] = { - { "nativeDestroy", -"(" -"J" -")" -"V", reinterpret_cast(Java_org_chromium_TestJni_nativeDestroy) }, -}; - -static bool RegisterNativesImpl(JNIEnv* env) { - if (jni_generator::ShouldSkipJniRegistration(false)) - return true; - - const int kMethodsTestJniSize = arraysize(kMethodsTestJni); - - if (env->RegisterNatives(TestJni_clazz(env), - kMethodsTestJni, - kMethodsTestJniSize) < 0) { - jni_generator::HandleRegistrationError( - env, TestJni_clazz(env), __FILE__); - return false; - } - - return true; -} #endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testNativesRegistrations.golden b/base/android/jni_generator/testNativesRegistrations.golden new file mode 100644 index 0000000..1dae786 --- /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 + +#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(Java_org_chromium_TestJni_nativeInit) }, + { "nativeDestroy", "(I)V", reinterpret_cast(Java_org_chromium_TestJni_nativeDestroy) }, + { "nativeAddBookmark", "(ILjava/lang/String;Ljava/lang/String;ZJ)J", + reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmark) }, + { "nativeGetDomainAndRegistry", "(Ljava/lang/String;)Ljava/lang/String;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetDomainAndRegistry) }, + { "nativeCreateHistoricalTabFromState", "([BI)V", + reinterpret_cast(Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState) }, + { "nativeGetStateAsByteArray", "(Landroid/view/View;)[B", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetStateAsByteArray) }, + { "nativeGetAutofillProfileGUIDs", "()[Ljava/lang/String;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs) }, + { "nativeSetRecognitionResults", "(I[Ljava/lang/String;)V", + reinterpret_cast(Java_org_chromium_TestJni_nativeSetRecognitionResults) }, + { "nativeAddBookmarkFromAPI", + "(ILjava/lang/String;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Long;[BLjava/lang/String;Ljava/lang/Integer;)J", + reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmarkFromAPI) }, + { "nativeFindAll", "(Ljava/lang/String;)I", + reinterpret_cast(Java_org_chromium_TestJni_nativeFindAll) }, + { "nativeGetInnerClass", + "()Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetInnerClass) }, + { "nativeQueryBitmap", + "(I[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/graphics/Bitmap;", + reinterpret_cast(Java_org_chromium_TestJni_nativeQueryBitmap) }, + { "nativeGotOrientation", "(IDDD)V", + reinterpret_cast(Java_org_chromium_TestJni_nativeGotOrientation) }, + { "nativeMessWithJavaException", "(Ljava/lang/Throwable;)Ljava/lang/Throwable;", + reinterpret_cast(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 ef618da..4b3eccd 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& - jcaller, +// Step 3: Method stubs. +static void JNI_Foo_DoSomething(JNIEnv* env, const base::android::JavaParamRef& jcaller, const base::android::JavaParamRef& callback); -JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv* - env, jclass jcaller, +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething( + JNIEnv* env, + jclass jcaller, jobject callback) { - return DoSomething(env, base::android::JavaParamRef(env, jcaller), + return JNI_Foo_DoSomething(env, base::android::JavaParamRef(env, jcaller), base::android::JavaParamRef(env, callback)); } -static base::subtle::AtomicWord g_Foo_calledByNative = 0; -static void Java_Foo_calledByNative(JNIEnv* env, const - base::android::JavaRefOrBare& callback) { - CHECK_CLAZZ(env, Foo_clazz(env), - Foo_clazz(env)); - jmethodID method_id = - base::android::MethodID::LazyGet< + +static base::subtle::AtomicWord g_org_chromium_foo_Foo_calledByNative = 0; +static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef& 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(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 0000000..9701027 --- /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 + +#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(nativeInstance); + CHECK_NATIVE_PTR(env, jcaller, native, "InstanceMethod"); + return native->InstanceMethod(env, base::android::JavaParamRef(env, jcaller)); +} + +static void JNI_Foo_StaticMethod(JNIEnv* env, const base::android::JavaParamRef& 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(env, jcaller)); +} + + +static base::subtle::AtomicWord g_org_chromium_foo_Foo_Constructor = 0; +static base::android::ScopedJavaLocalRef 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), + "", + "()V", + &g_org_chromium_foo_Foo_Constructor); + + TRACE_EVENT0("jni", "org.chromium.foo.Foo."); jobject ret = + env->NewObject(org_chromium_foo_Foo_clazz(env), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_org_chromium_foo_Foo_callbackFromNative = 0; +static void Java_Foo_callbackFromNative(JNIEnv* env, const base::android::JavaRef& 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 0000000..5eff6c9 --- /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 weakReference = new WeakReference<>(object); + + DiscardableReference 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 weakReference = new WeakReference<>(object); + + DiscardableReference 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 0000000..7773b32 --- /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 0000000..0865d9d --- /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(linker_script_start_of_text); +const size_t kEndOfText = reinterpret_cast(linker_script_end_of_text); +const size_t kStartOfOrderedText = + reinterpret_cast(dummy_function_start_of_ordered_text); +const size_t kEndOfOrderedText = + reinterpret_cast(dummy_function_end_of_ordered_text); + +bool IsOrderingSane() { + size_t here = reinterpret_cast(&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 0000000..9894583 --- /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 +#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 0000000..cdeaaa2 --- /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 0000000..ff0bfff --- /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 0000000..f06cc20 --- /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 +#include + +#include +#include +#include +#include +#include +#include + +#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 + +#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 offsets[kBitfieldSize]; + std::atomic ordered_offsets[kMaxElements]; + std::atomic index; +}; + +LogData g_data[kPhases]; +std::atomic 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 +__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* 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(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(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(g_data[i].offsets), 0, + sizeof(uint32_t) * kBitfieldSize); + memset(reinterpret_cast(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(address); +} + +NO_INSTRUMENT_FUNCTION std::vector GetOrderedOffsetsForTesting() { + std::vector 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( + reinterpret_cast(__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 0000000..8db1943 --- /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 +#include + +#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 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 0000000..e1a69c9 --- /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 + +#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(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((tock - tick).InNanoseconds()); + auto ns_per_call = + nanos / (iterations * static_cast(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 0000000..deca250 --- /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 0000000..b410239 --- /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 0000000..11bc240 --- /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 0000000..55b0a70 --- /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 0000000..b8121ae --- /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 6d728e9..8bb18d4 100644 --- a/base/android/scoped_java_ref.h +++ b/base/android/scoped_java_ref.h @@ -45,12 +45,12 @@ template<> class BASE_EXPORT JavaRef { 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 { template class ScopedJavaLocalRef : public JavaRef { 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 { template class ScopedJavaGlobalRef : public JavaRef { public: - ScopedJavaGlobalRef() {} - ScopedJavaGlobalRef(std::nullptr_t) {} + constexpr ScopedJavaGlobalRef() {} + constexpr ScopedJavaGlobalRef(std::nullptr_t) {} ScopedJavaGlobalRef(const ScopedJavaGlobalRef& other) { this->Reset(other); @@ -279,22 +279,6 @@ class ScopedJavaGlobalRef : public JavaRef { } }; -// Temporary type for parameters to Java functions, to allow incremental -// migration from bare jobject to JavaRef. Don't use outside JNI generator. -template -class JavaRefOrBare { - public: - JavaRefOrBare(std::nullptr_t) : obj_(nullptr) {} - JavaRefOrBare(const JavaRef& ref) : obj_(ref.obj()) {} - JavaRefOrBare(T obj) : obj_(obj) {} - T obj() const { return obj_; } - - private: - T obj_; - - DISALLOW_COPY_AND_ASSIGN(JavaRefOrBare); -}; - } // namespace android } // namespace base diff --git a/base/android/timezone_utils.cc b/base/android/timezone_utils.cc new file mode 100644 index 0000000..5243cdc --- /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 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 0000000..f78ef85 --- /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 + +#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 e0025ea..52c2151 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 tasks; + base::stack 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 6bf3f50..e74de8d 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 - #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 stack_; + base::stack 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 cda7340..3de061f 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(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 93c1f0d..3ffa017 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 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 59b0d25..717e37a 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 + #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( - 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 3428fe8..4d8510e 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 9f05b7e..ee9043e 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 +#include "base/win/windows_types.h" #include @@ -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(ptr), - reinterpret_cast(new_value), reinterpret_cast(old_value)); + PVOID result = _InterlockedCompareExchangePointer( + reinterpret_cast(ptr), + reinterpret_cast(new_value), reinterpret_cast(old_value)); return reinterpret_cast(result); } inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, Atomic64 new_value) { - PVOID result = InterlockedExchangePointer( - reinterpret_cast(ptr), - reinterpret_cast(new_value)); + PVOID result = + _InterlockedExchangePointer(reinterpret_cast(ptr), + reinterpret_cast(new_value)); return reinterpret_cast(result); } inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment) { - return InterlockedExchangeAdd64( - reinterpret_cast(ptr), - static_cast(increment)) + increment; + return _InterlockedExchangeAdd64(reinterpret_cast(ptr), + static_cast(increment)) + + increment; } inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, diff --git a/base/auto_reset.h b/base/auto_reset.h index 9116537..8515fe9 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 + #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 0000000..4426bb9 --- /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 + +#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 0000000..282aa39 --- /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 0000000..3716f72 --- /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 + +#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(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 0000000..c324be0 --- /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 + +#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(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 31bc554..e3f322e 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 ef6aa82..2a163f4 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\" 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 37d646c..5c4dd57 100644 --- a/base/base_paths_posix.cc +++ b/base/base_paths_posix.cc @@ -28,14 +28,13 @@ #if defined(OS_FREEBSD) #include #include -#elif defined(OS_SOLARIS) +#elif defined(OS_SOLARIS) || defined(OS_AIX) #include #endif namespace base { bool PathProviderPosix(int key, FilePath* result) { - FilePath path; switch (key) { case FILE_EXE: case FILE_MODULE: { // TODO(evanm): is this correct? @@ -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 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 e8aa5cb..13c710b 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 04b0773..4ef070d 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 0000000..514581f --- /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 +bool BigEndianReader::Read(T* value) { + if (ptr_ + sizeof(T) > end_) + return false; + ReadBigEndian(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 +bool BigEndianWriter::Write(T value) { + if (ptr_ + sizeof(T) > end_) + return false; + WriteBigEndian(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 0000000..5684c67 --- /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 +#include + +#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 +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(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 +inline void WriteBigEndian(char buf[], T val) { + for (size_t i = 0; i < sizeof(T); ++i) { + buf[sizeof(T)-i-1] = static_cast(val & 0xFF); + val >>= 8; + } +} + +// Specializations to make clang happy about the (dead code) shifts above. +template <> +inline void ReadBigEndian(const char buf[], uint8_t* out) { + *out = buf[0]; +} + +template <> +inline void WriteBigEndian(char buf[], uint8_t val) { + buf[0] = static_cast(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 + 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 + 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 0000000..4e1e7ce --- /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 + +#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(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 ce71797..66d5d82 100644 --- a/base/bind.h +++ b/base/bind.h @@ -5,14 +5,46 @@ #ifndef BASE_BIND_H_ #define BASE_BIND_H_ +#include + #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 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 { void F(); }; +// auto instance = base::MakeRefCounted(); +// 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 is a std::true_type if |T| is a OnceCallback. +template +struct IsOnceCallback : std::false_type {}; + +template +struct IsOnceCallback> : 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 +struct AssertConstructible { + private: + static constexpr bool param_is_forwardable = + std::is_constructible::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&&>::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::value; + static_assert(arg_is_storable || + !std::is_constructible&&>::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 +struct AssertBindArgsValidity; + +template +struct AssertBindArgsValidity, + TypeList, + TypeList, + TypeList> + : AssertConstructible, Unwrapped, Params>... { + static constexpr bool ok = true; +}; + +// The implementation of TransformToUnwrappedType below. +template +struct TransformToUnwrappedTypeImpl; + +template +struct TransformToUnwrappedTypeImpl { + using StoredType = std::decay_t; + using ForwardType = StoredType&&; + using Unwrapped = decltype(Unwrap(std::declval())); +}; + +template +struct TransformToUnwrappedTypeImpl { + using StoredType = std::decay_t; + using ForwardType = const StoredType&; + using Unwrapped = decltype(Unwrap(std::declval())); +}; + +// 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*&&`. +// In is_once == false case, +// `int&&` -> `const int&`, +// `const int&` -> `const int&`, +// `OwnedWrapper&` -> `int* const &`. +template +using TransformToUnwrappedType = + typename TransformToUnwrappedTypeImpl::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 +struct MakeUnwrappedTypeListImpl { + using Type = TypeList...>; +}; + +// Performs special handling for this pointers. +// Example: +// int* -> int*, +// std::unique_ptr -> int*. +template +struct MakeUnwrappedTypeListImpl { + using UnwrappedReceiver = TransformToUnwrappedType; + using Type = TypeList()), + TransformToUnwrappedType...>; +}; + +template +using MakeUnwrappedTypeList = + typename MakeUnwrappedTypeListImpl::Type; + +} // namespace internal + // Bind as OnceCallback. template inline OnceCallback> BindOnce(Functor&& functor, Args&&... args) { + static_assert(!internal::IsOnceCallback>() || + (std::is_rvalue_reference() && + !std::is_const>()), + "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; + using FunctorTraits = typename Helper::FunctorTraits; + using BoundArgsList = typename Helper::BoundArgsList; + using UnwrappedArgsList = + internal::MakeUnwrappedTypeList; + using BoundParamsList = typename Helper::BoundParamsList; + static_assert(internal::AssertBindArgsValidity< + std::make_index_sequence, BoundArgsList, + UnwrappedArgsList, BoundParamsList>::ok, + "The bound args need to be convertible to the target params."); + using BindState = internal::MakeBindStateType; using UnboundRunType = MakeUnboundRunType; using Invoker = internal::Invoker; @@ -50,6 +226,25 @@ BindOnce(Functor&& functor, Args&&... args) { template inline RepeatingCallback> BindRepeating(Functor&& functor, Args&&... args) { + static_assert( + !internal::IsOnceCallback>(), + "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; + using FunctorTraits = typename Helper::FunctorTraits; + using BoundArgsList = typename Helper::BoundArgsList; + using UnwrappedArgsList = + internal::MakeUnwrappedTypeList; + using BoundParamsList = typename Helper::BoundParamsList; + static_assert(internal::AssertBindArgsValidity< + std::make_index_sequence, BoundArgsList, + UnwrappedArgsList, BoundParamsList>::ok, + "The bound args need to be convertible to the target params."); + using BindState = internal::MakeBindStateType; using UnboundRunType = MakeUnboundRunType; using Invoker = internal::Invoker; @@ -74,10 +269,214 @@ BindRepeating(Functor&& functor, Args&&... args) { template inline Callback> Bind(Functor&& functor, Args&&... args) { - return BindRepeating(std::forward(functor), - std::forward(args)...); + return base::BindRepeating(std::forward(functor), + std::forward(args)...); +} + +// Special cases for binding to a base::Callback without extra bound arguments. +template +OnceCallback BindOnce(OnceCallback closure) { + return closure; +} + +template +RepeatingCallback BindRepeating( + RepeatingCallback closure) { + return closure; +} + +template +Callback Bind(Callback 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 +static inline internal::UnretainedWrapper Unretained(T* o) { + return internal::UnretainedWrapper(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 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 +static inline internal::RetainedRefWrapper RetainedRef(T* o) { + return internal::RetainedRefWrapper(o); +} +template +static inline internal::RetainedRefWrapper RetainedRef(scoped_refptr o) { + return internal::RetainedRefWrapper(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 +static inline internal::ConstRefWrapper ConstRef(const T& o) { + return internal::ConstRefWrapper(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 +static inline internal::OwnedWrapper Owned(T* o) { + return internal::OwnedWrapper(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 arg) { } +// std::unique_ptr CreateFoo() { return std::make_unique(); +// } +// +// auto f = std::make_unique(); +// +// // |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 ::value>* = nullptr> +static inline internal::PassedWrapper Passed(T&& scoper) { + return internal::PassedWrapper(std::move(scoper)); +} +template +static inline internal::PassedWrapper Passed(T* scoper) { + return internal::PassedWrapper(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 cb = Bind(IgnoreResult(&DoSomething)); +// cb->Run(1); // Prints "1". +// +// // Prints "1" on |ml|. +// ml->PostTask(FROM_HERE, Bind(IgnoreResult(&DoSomething), 1); +template +static inline internal::IgnoreResultHelper IgnoreResult(T data) { + return internal::IgnoreResultHelper(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 cb = Bind(RetainBlock(^(int n) { NSLog(@"%d", n); })); +// cb.Run(1); // Logs "1". +template +base::mac::ScopedBlock RetainBlock(R (^block)(Args...)) { + return base::mac::ScopedBlock(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 f1fe46d..0000000 --- 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 7b3d7d3..15961e6 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 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 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 arg) { } -// std::unique_ptr CreateFoo() { return std::unique_ptr(new Foo()); -// } -// -// std::unique_ptr 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() - 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 #include +#include "base/bind.h" #include "base/callback.h" #include "base/memory/weak_ptr.h" #include "build/build_config.h" -namespace base { - -template -struct IsWeakReceiver; - -template -struct BindUnwrapTraits; - -namespace internal { - -template -struct FunctorTraits; - -template -class UnretainedWrapper { - public: - explicit UnretainedWrapper(T* o) : ptr_(o) {} - T* get() const { return ptr_; } - private: - T* ptr_; -}; - -template -class ConstRefWrapper { - public: - explicit ConstRefWrapper(const T& o) : ptr_(&o) {} - const T& get() const { return *ptr_; } - private: - const T* ptr_; -}; - -template -class RetainedRefWrapper { - public: - explicit RetainedRefWrapper(T* o) : ptr_(o) {} - explicit RetainedRefWrapper(scoped_refptr o) : ptr_(std::move(o)) {} - T* get() const { return ptr_.get(); } - private: - scoped_refptr ptr_; -}; - -template -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 -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 + operator RepeatingCallback() const { + return RepeatingCallback(); } - - 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 -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 + operator OnceCallback() const { + return OnceCallback(); } - - private: - mutable bool is_valid_; - mutable T scoper_; -}; - -template -using Unwrapper = BindUnwrapTraits::type>; - -template -auto Unwrap(T&& o) -> decltype(Unwrapper::Unwrap(std::forward(o))) { - return Unwrapper::Unwrap(std::forward(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 -struct IsWeakMethod : std::false_type {}; - -template -struct IsWeakMethod : IsWeakReceiver {}; - -// Packs a list of types to hold them in a single type. -template -struct TypeList {}; - -// Used for DropTypeListItem implementation. -template -struct DropTypeListItemImpl; - -// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure. -template -struct DropTypeListItemImpl> - : DropTypeListItemImpl> {}; - -template -struct DropTypeListItemImpl<0, TypeList> { - using Type = TypeList; -}; - -template <> -struct DropTypeListItemImpl<0, TypeList<>> { - using Type = TypeList<>; -}; - -// A type-level function that drops |n| list item from given TypeList. -template -using DropTypeListItem = typename DropTypeListItemImpl::Type; - -// Used for TakeTypeListItem implementation. -template -struct TakeTypeListItemImpl; - -// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure. -template -struct TakeTypeListItemImpl, Accum...> - : TakeTypeListItemImpl, Accum..., T> {}; - -template -struct TakeTypeListItemImpl<0, TypeList, Accum...> { - using Type = TypeList; -}; - -template -struct TakeTypeListItemImpl<0, TypeList<>, Accum...> { - using Type = TypeList; -}; - -// A type-level function that takes first |n| list item from given TypeList. -// E.g. TakeTypeListItem<3, TypeList> is evaluated to -// TypeList. -template -using TakeTypeListItem = typename TakeTypeListItemImpl::Type; - -// Used for ConcatTypeLists implementation. -template -struct ConcatTypeListsImpl; - -template -struct ConcatTypeListsImpl, TypeList> { - using Type = TypeList; -}; - -// A type-level function that concats two TypeLists. -template -using ConcatTypeLists = typename ConcatTypeListsImpl::Type; - -// Used for MakeFunctionType implementation. -template -struct MakeFunctionTypeImpl; - -template -struct MakeFunctionTypeImpl> { - // 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 -using MakeFunctionType = typename MakeFunctionTypeImpl::Type; - -// Used for ExtractArgs and ExtractReturnType. -template -struct ExtractArgsImpl; - -template -struct ExtractArgsImpl { - using ReturnType = R; - using ArgsList = TypeList; }; -// A type-level function that extracts function arguments into a TypeList. -// E.g. ExtractArgs is evaluated to TypeList. -template -using ExtractArgs = typename ExtractArgsImpl::ArgsList; - -// A type-level function that extracts the return type of a function. -// E.g. ExtractReturnType is evaluated to R. -template -using ExtractReturnType = typename ExtractArgsImpl::ReturnType; - -} // namespace internal - -template -static inline internal::UnretainedWrapper Unretained(T* o) { - return internal::UnretainedWrapper(o); -} - -template -static inline internal::RetainedRefWrapper RetainedRef(T* o) { - return internal::RetainedRefWrapper(o); -} - -template -static inline internal::RetainedRefWrapper RetainedRef(scoped_refptr o) { - return internal::RetainedRefWrapper(std::move(o)); -} - -template -static inline internal::ConstRefWrapper ConstRef(const T& o) { - return internal::ConstRefWrapper(o); -} - -template -static inline internal::OwnedWrapper Owned(T* o) { - return internal::OwnedWrapper(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 ::value>::type* = - nullptr> -static inline internal::PassedWrapper Passed(T&& scoper) { - return internal::PassedWrapper(std::move(scoper)); -} -template -static inline internal::PassedWrapper Passed(T* scoper) { - return internal::PassedWrapper(std::move(*scoper)); -} - -template -static inline internal::IgnoreResultHelper IgnoreResult(T data) { - return internal::IgnoreResultHelper(std::move(data)); -} - -BASE_EXPORT void DoNothing(); - -template -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 { -// void bar() {} -// }; -// -// WeakPtr oo = nullptr; -// base::Bind(&Foo::bar, oo).Run(); -template -struct IsWeakReceiver : std::false_type {}; - -template -struct IsWeakReceiver> : IsWeakReceiver {}; - -template -struct IsWeakReceiver> : 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 -struct BindUnwrapTraits { - template - static T&& Unwrap(T&& o) { return std::forward(o); } -}; - -template -struct BindUnwrapTraits> { - static T* Unwrap(const internal::UnretainedWrapper& o) { - return o.get(); +// Creates a callback that does nothing when called. +class BASE_EXPORT DoNothing { + public: + template + operator RepeatingCallback() const { + return Repeatedly(); } -}; - -template -struct BindUnwrapTraits> { - static const T& Unwrap(const internal::ConstRefWrapper& o) { - return o.get(); + template + operator OnceCallback() const { + return Once(); } -}; - -template -struct BindUnwrapTraits> { - static T* Unwrap(const internal::RetainedRefWrapper& o) { - return o.get(); + // Explicit way of specifying a specific callback type when the compiler can't + // deduce it. + template + static RepeatingCallback Repeatedly() { + return BindRepeating([](Args... args) {}); } -}; - -template -struct BindUnwrapTraits> { - static T* Unwrap(const internal::OwnedWrapper& o) { - return o.get(); + template + static OnceCallback 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 -struct BindUnwrapTraits> { - static T Unwrap(const internal::PassedWrapper& 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 -struct CallbackCancellationTraits { - static constexpr bool is_cancellable = false; -}; - -// Specialization for method bound to weak pointer receiver. -template -struct CallbackCancellationTraits< - Functor, - std::tuple, - typename std::enable_if< - internal::IsWeakMethod::is_method, - BoundArgs...>::value>::type> { - static constexpr bool is_cancellable = true; - - template - static bool IsCancelled(const Functor&, - const Receiver& receiver, - const Args&...) { - return !receiver; - } -}; - -// Specialization for a nested bind. -template -struct CallbackCancellationTraits, - std::tuple> { - static constexpr bool is_cancellable = true; - - template - 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 8988bdc..4ebecfa 100644 --- a/base/bind_internal.h +++ b/base/bind_internal.h @@ -7,19 +7,19 @@ #include -#include #include +#include -#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 -struct make_void { - using type = void; +namespace base { + +template +struct IsWeakReceiver; + +template +struct BindUnwrapTraits; + +template +struct CallbackCancellationTraits; + +namespace internal { + +template +struct FunctorTraits; + +template +class UnretainedWrapper { + public: + explicit UnretainedWrapper(T* o) : ptr_(o) {} + T* get() const { return ptr_; } + + private: + T* ptr_; +}; + +template +class ConstRefWrapper { + public: + explicit ConstRefWrapper(const T& o) : ptr_(&o) {} + const T& get() const { return *ptr_; } + + private: + const T* ptr_; +}; + +template +class RetainedRefWrapper { + public: + explicit RetainedRefWrapper(T* o) : ptr_(o) {} + explicit RetainedRefWrapper(scoped_refptr o) : ptr_(std::move(o)) {} + T* get() const { return ptr_.get(); } + + private: + scoped_refptr ptr_; +}; + +template +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 +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 +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 +using Unwrapper = BindUnwrapTraits>; + +template +decltype(auto) Unwrap(T&& o) { + return Unwrapper::Unwrap(std::forward(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 +struct IsWeakMethod : std::false_type {}; + +template +struct IsWeakMethod : IsWeakReceiver {}; + +// Packs a list of types to hold them in a single type. +template +struct TypeList {}; + +// Used for DropTypeListItem implementation. +template +struct DropTypeListItemImpl; + +// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure. +template +struct DropTypeListItemImpl> + : DropTypeListItemImpl> {}; + +template +struct DropTypeListItemImpl<0, TypeList> { + using Type = TypeList; +}; + +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 -using void_t = typename make_void::type; +// A type-level function that drops |n| list item from given TypeList. +template +using DropTypeListItem = typename DropTypeListItemImpl::Type; + +// Used for TakeTypeListItem implementation. +template +struct TakeTypeListItemImpl; + +// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure. +template +struct TakeTypeListItemImpl, Accum...> + : TakeTypeListItemImpl, Accum..., T> {}; + +template +struct TakeTypeListItemImpl<0, TypeList, Accum...> { + using Type = TypeList; +}; + +template +struct TakeTypeListItemImpl<0, TypeList<>, Accum...> { + using Type = TypeList; +}; + +// A type-level function that takes first |n| list item from given TypeList. +// E.g. TakeTypeListItem<3, TypeList> is evaluated to +// TypeList. +template +using TakeTypeListItem = typename TakeTypeListItemImpl::Type; + +// Used for ConcatTypeLists implementation. +template +struct ConcatTypeListsImpl; + +template +struct ConcatTypeListsImpl, TypeList> { + using Type = TypeList; +}; + +// A type-level function that concats two TypeLists. +template +using ConcatTypeLists = typename ConcatTypeListsImpl::Type; + +// Used for MakeFunctionType implementation. +template +struct MakeFunctionTypeImpl; + +template +struct MakeFunctionTypeImpl> { + // 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 +using MakeFunctionType = typename MakeFunctionTypeImpl::Type; + +// Used for ExtractArgs and ExtractReturnType. +template +struct ExtractArgsImpl; + +template +struct ExtractArgsImpl { + using ReturnType = R; + using ArgsList = TypeList; +}; + +// A type-level function that extracts function arguments into a TypeList. +// E.g. ExtractArgs is evaluated to TypeList. +template +using ExtractArgs = typename ExtractArgsImpl::ArgsList; + +// A type-level function that extracts the return type of a function. +// E.g. ExtractReturnType is evaluated to R. +template +using ExtractReturnType = typename ExtractArgsImpl::ReturnType; template struct ExtractCallableRunTypeImpl; template -struct ExtractCallableRunTypeImpl { +struct ExtractCallableRunTypeImpl { + using Type = R(Args...); +}; + +template +struct ExtractCallableRunTypeImpl { using Type = R(Args...); }; @@ -78,27 +310,23 @@ template using ExtractCallableRunType = typename ExtractCallableRunTypeImpl::Type; -// IsConvertibleToRunType is std::true_type if |Functor| has operator() -// and convertible to the corresponding function pointer. Otherwise, it's -// std::false_type. +// IsCallableObject is std::true_type if |Functor| has operator(). +// Otherwise, it's std::false_type. // Example: -// IsConvertibleToRunType::value is false. +// IsCallableObject::value is false. // // struct Foo {}; -// IsConvertibleToRunType::value is false. -// -// auto f = []() {}; -// IsConvertibleToRunType::value is true. +// IsCallableObject::value is false. // // int i = 0; -// auto g = [i]() {}; -// IsConvertibleToRunType::value is false. +// auto f = [i]() {}; +// IsCallableObject::value is false. template -struct IsConvertibleToRunType : std::false_type {}; +struct IsCallableObject : std::false_type {}; template -struct IsConvertibleToRunType> - : std::is_convertible*> {}; +struct IsCallableObject> + : 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 struct HasRefCountedTypeAsRawPtr - : std::conditional::value, - std::true_type, - HasRefCountedTypeAsRawPtr>::type {}; + : std::conditional_t::value, + std::true_type, + HasRefCountedTypeAsRawPtr> {}; // ForceVoidReturn<> // @@ -133,22 +361,37 @@ struct ForceVoidReturn { template 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 -struct FunctorTraits< - Functor, - typename std::enable_if::value>::type> { +struct FunctorTraits::value && + std::is_empty::value>> { using RunType = ExtractCallableRunType; static constexpr bool is_method = false; static constexpr bool is_nullable = false; - template - static ExtractReturnType - Invoke(const Functor& functor, RunArgs&&... args) { - return functor(std::forward(args)...); + template + static ExtractReturnType Invoke(RunFunctor&& functor, + RunArgs&&... args) { + return std::forward(functor)(std::forward(args)...); } }; @@ -159,9 +402,9 @@ struct FunctorTraits { static constexpr bool is_method = false; static constexpr bool is_nullable = true; - template - static R Invoke(R (*function)(Args...), RunArgs&&... args) { - return function(std::forward(args)...); + template + static R Invoke(Function&& function, RunArgs&&... args) { + return std::forward(function)(std::forward(args)...); } }; @@ -195,6 +438,61 @@ struct FunctorTraits { #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 +struct FunctorTraits { + using RunType = R(Args...); + static constexpr bool is_method = false; + static constexpr bool is_nullable = true; + + template + 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(args)...); + } +}; + +#else // HAS_FEATURE(objc_arc) + +template +struct FunctorTraits> { + using RunType = R(Args...); + static constexpr bool is_method = false; + static constexpr bool is_nullable = true; + + template + 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 scoped_block(block); + return scoped_block.get()(std::forward(args)...); + } +}; + +#endif // HAS_FEATURE(objc_arc) +#endif // defined(OS_MACOSX) + // For methods. template struct FunctorTraits { @@ -202,16 +500,11 @@ struct FunctorTraits { static constexpr bool is_method = true; static constexpr bool is_nullable = true; - template - static R Invoke(R (Receiver::*method)(Args...), + template + 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(args)...); + return ((*receiver_ptr).*method)(std::forward(args)...); } }; @@ -222,19 +515,31 @@ struct FunctorTraits { static constexpr bool is_method = true; static constexpr bool is_nullable = true; - template - static R Invoke(R (Receiver::*method)(Args...) const, + template + 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(args)...); + return ((*receiver_ptr).*method)(std::forward(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 +struct FunctorTraits : FunctorTraits { +}; + +template +struct FunctorTraits + : FunctorTraits {}; + +template +struct FunctorTraits + : FunctorTraits {}; +#endif + // For IgnoreResults. template struct FunctorTraits> : FunctorTraits { @@ -250,10 +555,9 @@ struct FunctorTraits> : FunctorTraits { } }; -// For Callbacks. -template -struct FunctorTraits> { +// For OnceCallbacks. +template +struct FunctorTraits> { using RunType = R(Args...); static constexpr bool is_method = false; static constexpr bool is_nullable = true; @@ -266,6 +570,24 @@ struct FunctorTraits> { } }; +// For RepeatingCallbacks. +template +struct FunctorTraits> { + using RunType = R(Args...); + static constexpr bool is_method = false; + static constexpr bool is_nullable = true; + + template + static R Invoke(CallbackType&& callback, RunArgs&&... args) { + DCHECK(!callback.is_null()); + return std::forward(callback).Run( + std::forward(args)...); + } +}; + +template +using MakeFunctorTraits = FunctorTraits>; + // InvokeHelper<> // // There are 2 logical InvokeHelper<> specializations: normal, WeakCalls. @@ -281,7 +603,7 @@ template struct InvokeHelper { template static inline ReturnType MakeItSo(Functor&& functor, RunArgs&&... args) { - using Traits = FunctorTraits::type>; + using Traits = MakeFunctorTraits; return Traits::Invoke(std::forward(functor), std::forward(args)...); } @@ -301,7 +623,7 @@ struct InvokeHelper { RunArgs&&... args) { if (!weak_ptr) return; - using Traits = FunctorTraits::type>; + using Traits = MakeFunctorTraits; Traits::Invoke(std::forward(functor), std::forward(weak_ptr), std::forward(args)...); @@ -316,7 +638,8 @@ struct Invoker; template struct Invoker { - static R RunOnce(BindStateBase* base, UnboundArgs&&... unbound_args) { + static R RunOnce(BindStateBase* base, + PassingType... 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 { std::tuple_sizebound_args_)>::value; return RunImpl(std::move(storage->functor_), std::move(storage->bound_args_), - MakeIndexSequence(), + std::make_index_sequence(), std::forward(unbound_args)...); } - static R Run(BindStateBase* base, UnboundArgs&&... unbound_args) { + static R Run(BindStateBase* base, PassingType... 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(base); static constexpr size_t num_bound_args = std::tuple_sizebound_args_)>::value; - return RunImpl(storage->functor_, - storage->bound_args_, - MakeIndexSequence(), + return RunImpl(storage->functor_, storage->bound_args_, + std::make_index_sequence(), std::forward(unbound_args)...); } @@ -346,44 +668,60 @@ struct Invoker { template static inline R RunImpl(Functor&& functor, BoundArgsTuple&& bound, - IndexSequence, + std::index_sequence, UnboundArgs&&... unbound_args) { - static constexpr bool is_method = - FunctorTraits::type>::is_method; + static constexpr bool is_method = MakeFunctorTraits::is_method; - using DecayedArgsTuple = typename std::decay::type; + using DecayedArgsTuple = std::decay_t; static constexpr bool is_weak_call = IsWeakMethod::type...>::value; + std::tuple_element_t...>(); return InvokeHelper::MakeItSo( std::forward(functor), - Unwrap(base::get(std::forward(bound)))..., + Unwrap(std::get(std::forward(bound)))..., std::forward(unbound_args)...); } }; -// Used to implement MakeUnboundRunType. +// Extracts necessary type info from Functor and BoundArgs. +// Used to implement MakeUnboundRunType, BindOnce and BindRepeating. template -struct MakeUnboundRunTypeImpl { - using RunType = - typename FunctorTraits::type>::RunType; +struct BindTypeHelper { + static constexpr size_t num_bounds = sizeof...(BoundArgs); + using FunctorTraits = MakeFunctorTraits; + + // 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`, + // - BoundParamsList is `TypeList`, + // - UnboundParamsList is `TypeList`, + // - BoundArgsList is `TypeList`, + // - UnboundRunType is `double(const std::string&)`. + using RunType = typename FunctorTraits::RunType; using ReturnType = ExtractReturnType; - using Args = ExtractArgs; - using UnboundArgs = DropTypeListItem; - using Type = MakeFunctionType; + + using RunParamsList = ExtractArgs; + using BoundParamsList = TakeTypeListItem; + using UnboundParamsList = DropTypeListItem; + + using BoundArgsList = TypeList; + + using UnboundRunType = MakeFunctionType; }; + template -typename std::enable_if::is_nullable, bool>::type -IsNull(const Functor& functor) { +std::enable_if_t::is_nullable, bool> IsNull( + const Functor& functor) { return !functor; } template -typename std::enable_if::is_nullable, bool>::type -IsNull(const Functor&) { +std::enable_if_t::is_nullable, bool> IsNull( + const Functor&) { return false; } @@ -391,9 +729,9 @@ IsNull(const Functor&) { template bool ApplyCancellationTraitsImpl(const Functor& functor, const BoundArgsTuple& bound_args, - IndexSequence) { + std::index_sequence) { return CallbackCancellationTraits::IsCancelled( - functor, base::get(bound_args)...); + functor, std::get(bound_args)...); } // Relays |base| to corresponding CallbackCancellationTraits<>::Run(). Returns @@ -403,25 +741,9 @@ bool ApplyCancellationTraits(const BindStateBase* base) { const BindStateType* storage = static_cast(base); static constexpr size_t num_bound_args = std::tuple_sizebound_args_)>::value; - return ApplyCancellationTraitsImpl(storage->functor_, storage->bound_args_, - MakeIndexSequence()); -}; - -// 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 -struct BindingCallbackWithNoArgs { - static constexpr bool value = false; -}; - -template -struct BindingCallbackWithNoArgs, - BoundArgs...> { - static constexpr bool value = sizeof...(BoundArgs) == 0; + return ApplyCancellationTraitsImpl( + storage->functor_, storage->bound_args_, + std::make_index_sequence()); }; // BindState<> @@ -444,12 +766,7 @@ struct BindState final : BindStateBase { : BindState(IsCancellable{}, invoke_func, std::forward(functor), - std::forward(bound_args)...) { - static_assert(!BindingCallbackWithNoArgs::value, - "Attempting to bind a base::Callback with no additional " - "arguments: save a heap allocation and use the original " - "base::Callback object"); - } + std::forward(bound_args)...) {} Functor functor_; std::tuple 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(self); @@ -492,51 +809,162 @@ struct MakeBindStateTypeImpl; template struct MakeBindStateTypeImpl { - static_assert(!HasRefCountedTypeAsRawPtr::value, + static_assert(!HasRefCountedTypeAsRawPtr...>::value, "A parameter is a refcounted type and needs scoped_refptr."); - using Type = BindState::type, - typename std::decay::type...>; + using Type = BindState, std::decay_t...>; }; template struct MakeBindStateTypeImpl { - using Type = BindState::type>; + using Type = BindState>; }; template struct MakeBindStateTypeImpl { + private: + using DecayedReceiver = std::decay_t; + + static_assert(!std::is_array>::value, + "First bound argument to a method cannot be an array."); static_assert( - !std::is_array::type>::value, - "First bound argument to a method cannot be an array."); - static_assert(!HasRefCountedTypeAsRawPtr::value, + !std::is_pointer::value || + IsRefCountedType>::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...>::value, "A parameter is a refcounted type and needs scoped_refptr."); - private: - using DecayedReceiver = typename std::decay::type; - public: using Type = BindState< - typename std::decay::type, - typename std::conditional< - std::is_pointer::value, - scoped_refptr::type>, - DecayedReceiver>::type, - typename std::decay::type...>; + std::decay_t, + std::conditional_t::value, + scoped_refptr>, + DecayedReceiver>, + std::decay_t...>; }; template -using MakeBindStateType = typename MakeBindStateTypeImpl< - FunctorTraits::type>::is_method, - Functor, - BoundArgs...>::Type; +using MakeBindStateType = + typename MakeBindStateTypeImpl::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 { +// void bar() {} +// }; +// +// WeakPtr oo = nullptr; +// base::Bind(&Foo::bar, oo).Run(); +template +struct IsWeakReceiver : std::false_type {}; + +template +struct IsWeakReceiver> : IsWeakReceiver {}; + +template +struct IsWeakReceiver> : 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 +struct BindUnwrapTraits { + template + static T&& Unwrap(T&& o) { + return std::forward(o); + } +}; + +template +struct BindUnwrapTraits> { + static T* Unwrap(const internal::UnretainedWrapper& o) { return o.get(); } +}; + +template +struct BindUnwrapTraits> { + static const T& Unwrap(const internal::ConstRefWrapper& o) { + return o.get(); + } +}; + +template +struct BindUnwrapTraits> { + static T* Unwrap(const internal::RetainedRefWrapper& o) { return o.get(); } +}; + +template +struct BindUnwrapTraits> { + static T* Unwrap(const internal::OwnedWrapper& o) { return o.get(); } +}; + +template +struct BindUnwrapTraits> { + static T Unwrap(const internal::PassedWrapper& 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 +struct CallbackCancellationTraits { + static constexpr bool is_cancellable = false; +}; + +// Specialization for method bound to weak pointer receiver. +template +struct CallbackCancellationTraits< + Functor, + std::tuple, + std::enable_if_t< + internal::IsWeakMethod::is_method, + BoundArgs...>::value>> { + static constexpr bool is_cancellable = true; + + template + static bool IsCancelled(const Functor&, + const Receiver& receiver, + const Args&...) { + return !receiver; + } +}; + +// Specialization for a nested bind. +template +struct CallbackCancellationTraits, + std::tuple> { + static constexpr bool is_cancellable = true; + + template + static bool IsCancelled(const Functor& functor, const BoundArgs&...) { + return functor.IsCancelled(); + } +}; + +template +struct CallbackCancellationTraits, + std::tuple> { + static constexpr bool is_cancellable = true; + + template + static bool IsCancelled(const Functor& functor, const BoundArgs&...) { + return functor.IsCancelled(); + } +}; + // Returns a RunType of bound functor. // E.g. MakeUnboundRunType is evaluated to R(C). template using MakeUnboundRunType = - typename internal::MakeUnboundRunTypeImpl::Type; + typename internal::BindTypeHelper::UnboundRunType; } // namespace base diff --git a/base/bind_unittest.cc b/base/bind_unittest.cc index 0de9294..870132b 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 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(42)))); + .WillOnce(Return(ByMove(std::make_unique(42)))); CallbackType normal_cb = TypeParam::Bind(&IntFunc0); CallbackType method_cb = @@ -1238,17 +1241,17 @@ TEST_F(BindTest, ArgumentCopiesAndMoves) { } TEST_F(BindTest, CapturelessLambda) { - EXPECT_FALSE(internal::IsConvertibleToRunType::value); - EXPECT_FALSE(internal::IsConvertibleToRunType::value); - EXPECT_FALSE(internal::IsConvertibleToRunType::value); - EXPECT_FALSE(internal::IsConvertibleToRunType::value); + EXPECT_FALSE(internal::IsCallableObject::value); + EXPECT_FALSE(internal::IsCallableObject::value); + EXPECT_FALSE(internal::IsCallableObject::value); + EXPECT_FALSE(internal::IsCallableObject::value); auto f = []() {}; - EXPECT_TRUE(internal::IsConvertibleToRunType::value); + EXPECT_TRUE(internal::IsCallableObject::value); int i = 0; auto g = [i]() { (void)i; }; - EXPECT_FALSE(internal::IsConvertibleToRunType::value); + EXPECT_TRUE(internal::IsCallableObject::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::value); + EXPECT_TRUE(internal::IsCallableObject::value); + EXPECT_TRUE(internal::IsCallableObject::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 y) { return x * *y; }; + EXPECT_EQ(42, BindLambdaForTesting(f).Run(std::make_unique(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 cb4 = BindOnce( - &VoidPolymorphic, int>::Run, MakeUnique(0)); + OnceCallback cb4 = + BindOnce(&VoidPolymorphic, int>::Run, + std::make_unique(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>(); + 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 null_cb; diff --git a/base/bits.h b/base/bits.h index d101cb7..a1c8b5d 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 @@ -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 ::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 +ALWAYS_INLINE + typename std::enable_if::value && sizeof(T) <= 4, + unsigned>::type + CountLeadingZeroBits(T x) { + static_assert(bits > 0, "invalid instantiation"); + unsigned long index; + return LIKELY(_BitScanReverse(&index, static_cast(x))) + ? (31 - index - (32 - bits)) + : bits; +} + +template +ALWAYS_INLINE + typename std::enable_if::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(x))) + ? (63 - index) + : 64; +} + +template +ALWAYS_INLINE + typename std::enable_if::value && sizeof(T) <= 4, + unsigned>::type + CountTrailingZeroBits(T x) { + static_assert(bits > 0, "invalid instantiation"); + unsigned long index; + return LIKELY(_BitScanForward(&index, static_cast(x))) ? index + : bits; +} + +template +ALWAYS_INLINE + typename std::enable_if::value && sizeof(T) == 8, + unsigned>::type + CountTrailingZeroBits(T x) { + static_assert(bits > 0, "invalid instantiation"); + unsigned long index; + return LIKELY(_BitScanForward64(&index, static_cast(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 +ALWAYS_INLINE + typename std::enable_if::value && sizeof(T) <= 8, + unsigned>::type + CountLeadingZeroBits(T value) { + static_assert(bits > 0, "invalid instantiation"); + return LIKELY(value) + ? bits == 64 + ? __builtin_clzll(static_cast(value)) + : __builtin_clz(static_cast(value)) - (32 - bits) + : bits; +} + +template +ALWAYS_INLINE + typename std::enable_if::value && sizeof(T) <= 8, + unsigned>::type + CountTrailingZeroBits(T value) { + return LIKELY(value) ? bits == 64 + ? __builtin_ctzll(static_cast(value)) + : __builtin_ctz(static_cast(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 270b8ef..98b9c08 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 @@ -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(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(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(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(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 c91e1a8..bcda5af 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 + #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 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. 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 -struct IsCallbackConvertible : std::false_type {}; - -template -struct IsCallbackConvertible, - OnceCallback> : std::true_type {}; +template +class OnceCallback : public internal::CallbackBase { + public: + using RunType = R(Args...); + using PolymorphicInvoke = R (*)(internal::BindStateBase*, + internal::PassingType...); -} // namespace internal + constexpr OnceCallback() = default; + OnceCallback(std::nullptr_t) = delete; -template -class Callback - : public internal::CallbackBase { - 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(nullptr) {} + OnceCallback(OnceCallback&&) noexcept = default; + OnceCallback& operator=(OnceCallback&&) noexcept = default; - explicit Callback(internal::BindStateBase* bind_state) - : internal::CallbackBase(bind_state) { - } + OnceCallback(RepeatingCallback other) + : internal::CallbackBase(std::move(other)) {} - template ::value - >::type> - Callback(OtherCallback other) - : internal::CallbackBase(std::move(other)) {} - - template ::value - >::type> - Callback& operator=(OtherCallback other) { - static_cast&>(*this) = std::move(other); + OnceCallback& operator=(RepeatingCallback other) { + static_cast(*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(cb.polymorphic_invoke()); + return f(cb.bind_state_.get(), std::forward(args)...); + } +}; + +template +class RepeatingCallback : public internal::CallbackBaseCopyable { + public: + using RunType = R(Args...); + using PolymorphicInvoke = R (*)(internal::BindStateBase*, + internal::PassingType...); + + 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(this->polymorphic_invoke()); return f(this->bind_state_.get(), std::forward(args)...); @@ -85,7 +134,7 @@ class Callback // 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(cb.polymorphic_invoke()); return f(cb.bind_state_.get(), std::forward(args)...); diff --git a/base/callback_forward.h b/base/callback_forward.h index 13eed0e..f1851c4 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 +class OnceCallback; -} // namespace internal +template +class RepeatingCallback; -template -class Callback; +template +using Callback = RepeatingCallback; // Syntactic sugar to make Callback easier to declare since it // will be used in a lot of APIs with delayed execution. -using Closure = Callback; - -template -using OnceCallback = Callback; -template -using RepeatingCallback = Callback; using OnceClosure = OnceCallback; using RepeatingClosure = RepeatingCallback; +using Closure = Callback; } // namespace base diff --git a/base/callback_helpers.cc b/base/callback_helpers.cc index 838e6c8..9086731 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 6e0aee8..0cdda6d 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 + +#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 -base::Callback ResetAndReturn( - base::Callback* cb) { - base::Callback ret(std::move(*cb)); +// Prefer std::move() over ResetAndReturn(). +template +CallbackType ResetAndReturn(CallbackType* cb) { + CallbackType ret(std::move(*cb)); DCHECK(!*cb); return ret; } +namespace internal { + +template +class AdaptCallbackForRepeatingHelper final { + public: + explicit AdaptCallbackForRepeatingHelper(OnceCallback 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)...); + } + + private: + volatile subtle::Atomic32 has_run_ = 0; + base::OnceCallback 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 +RepeatingCallback AdaptCallbackForRepeating( + OnceCallback callback) { + using Helper = internal::AdaptCallbackForRepeatingHelper; + return base::BindRepeating(&Helper::Run, + std::make_unique(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 6c48d7c..1c1102d 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 cb = + base::BindOnce([](int* count) { ++*count; }); + + base::RepeatingCallback 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 a760f06..dd000ca 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::CallbackBase(CallbackBase&& c) = default; - -CallbackBase& -CallbackBase::operator=(CallbackBase&& c) = default; - -CallbackBase::CallbackBase( - const CallbackBase& c) +CallbackBase& CallbackBase::operator=(CallbackBase&& c) noexcept = default; +CallbackBase::CallbackBase(const CallbackBaseCopyable& c) : bind_state_(c.bind_state_) {} -CallbackBase& CallbackBase::operator=( - const CallbackBase& c) { +CallbackBase& CallbackBase::operator=(const CallbackBaseCopyable& c) { bind_state_ = c.bind_state_; return *this; } -CallbackBase::CallbackBase( - CallbackBase&& c) +CallbackBase::CallbackBase(CallbackBaseCopyable&& c) noexcept : bind_state_(std::move(c.bind_state_)) {} -CallbackBase& CallbackBase::operator=( - CallbackBase&& c) { +CallbackBase& CallbackBase::operator=(CallbackBaseCopyable&& c) noexcept { bind_state_ = std::move(c.bind_state_); return *this; } -void CallbackBase::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::IsCancelled() const { +bool CallbackBase::IsCancelled() const { DCHECK(bind_state_); return bind_state_->IsCancelled(); } -bool CallbackBase::EqualsInternal( - const CallbackBase& other) const { +bool CallbackBase::EqualsInternal(const CallbackBase& other) const { return bind_state_ == other.bind_state_; } -CallbackBase::CallbackBase(BindStateBase* bind_state) - : bind_state_(bind_state ? AdoptRef(bind_state) : nullptr) { - DCHECK(!bind_state_.get() || bind_state_->HasOneRef()); -} - -CallbackBase::~CallbackBase() {} +CallbackBase::~CallbackBase() = default; -CallbackBase::CallbackBase( - const CallbackBase& c) - : CallbackBase(nullptr) { +CallbackBaseCopyable::CallbackBaseCopyable(const CallbackBaseCopyable& c) { bind_state_ = c.bind_state_; } -CallbackBase::CallbackBase(CallbackBase&& c) = default; - -CallbackBase& -CallbackBase::operator=(const CallbackBase& c) { +CallbackBaseCopyable& CallbackBaseCopyable::operator=( + const CallbackBaseCopyable& c) { bind_state_ = c.bind_state_; return *this; } -CallbackBase& -CallbackBase::operator=(CallbackBase&& c) = default; - -template class CallbackBase; -template class CallbackBase; +CallbackBaseCopyable& CallbackBaseCopyable::operator=( + CallbackBaseCopyable&& c) noexcept = default; } // namespace internal } // namespace base diff --git a/base/callback_internal.h b/base/callback_internal.h index 29b07c2..1215e3e 100644 --- a/base/callback_internal.h +++ b/base/callback_internal.h @@ -19,8 +19,8 @@ struct FakeBindState; namespace internal { -template class CallbackBase; +class CallbackBaseCopyable; class BindStateBase; @@ -31,6 +31,9 @@ struct BindStateBaseRefCountTraits { static void Destruct(const BindStateBase*); }; +template +using PassingType = std::conditional_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; - template friend class CallbackBase; + friend class CallbackBaseCopyable; // Whitelist subclasses that access the destructor of BindStateBase. template @@ -90,17 +93,16 @@ class BASE_EXPORT BindStateBase // template bloat. // CallbackBase is a direct base class of MoveOnly callbacks, and // CallbackBase uses CallbackBase for its implementation. -template <> -class BASE_EXPORT CallbackBase { +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& c); - CallbackBase& operator=(const CallbackBase& c); + explicit CallbackBase(const CallbackBaseCopyable& c); + CallbackBase& operator=(const CallbackBaseCopyable& c); - explicit CallbackBase(CallbackBase&& c); - CallbackBase& operator=(CallbackBase&& 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 { // 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 { scoped_refptr bind_state_; }; +constexpr CallbackBase::CallbackBase() = default; +CallbackBase::CallbackBase(CallbackBase&&) noexcept = default; +CallbackBase::CallbackBase(BindStateBase* bind_state) + : bind_state_(AdoptRef(bind_state)) {} + // CallbackBase is a direct base class of Copyable Callbacks. -template <> -class BASE_EXPORT CallbackBase - : public CallbackBase { +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(bind_state) {} - ~CallbackBase() {} + constexpr CallbackBaseCopyable() = default; + explicit CallbackBaseCopyable(BindStateBase* bind_state) + : CallbackBase(bind_state) {} + ~CallbackBaseCopyable() = default; }; -extern template class CallbackBase; -extern template class CallbackBase; - } // namespace internal } // namespace base diff --git a/base/callback_list.h b/base/callback_list.h index 7ab79dd..f455c65 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 OnFooCallback; -// // std::unique_ptr::Subscription> -// RegisterCallback(const OnFooCallback& cb) { +// RegisterCallback(const base::RepeatingCallback& 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 Add(const CallbackType& cb) WARN_UNUSED_RESULT { DCHECK(!cb.is_null()); - return std::unique_ptr( - new Subscription(this, callbacks_.insert(callbacks_.end(), cb))); + return std::make_unique( + 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::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 callbacks_; int active_iterator_count_; - Closure removal_callback_; + RepeatingClosure removal_callback_; DISALLOW_COPY_AND_ASSIGN(CallbackListBase); }; @@ -204,18 +202,17 @@ template class CallbackList; template class CallbackList - : public internal::CallbackListBase > { + : public internal::CallbackListBase> { public: - typedef Callback CallbackType; + using CallbackType = RepeatingCallback; - CallbackList() {} + CallbackList() = default; template void Notify(RunArgs&&... args) { - typename internal::CallbackListBase::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 62081e9..6eb5ff7 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::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 f76adbc..c07d3ee 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(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 callback_a_; diff --git a/base/cancelable_callback.h b/base/cancelable_callback.h index 13cbd0c..a98101a 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 -class CancelableCallback; - -template -class CancelableCallback { +template +class CancelableCallbackImpl { public: - CancelableCallback() : weak_factory_(this) {} + CancelableCallbackImpl() : weak_ptr_factory_(this) {} // |callback| must not be null. - explicit CancelableCallback(const base::Callback& 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 { // 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& 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& 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(args)...); + template + void MakeForwarder(RepeatingCallback* 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::Forward, - weak_factory_.GetWeakPtr()); + template + void MakeForwarder(OnceCallback* out) const { + using ForwarderType = void (CancelableCallbackImpl::*)(Args...); + ForwarderType forwarder = &CancelableCallbackImpl::ForwardOnce; + *out = BindOnce(forwarder, weak_ptr_factory_.GetWeakPtr()); } - // The wrapper closure. - base::Callback forwarder_; + template + void ForwardRepeating(Args... args) { + callback_.Run(std::forward(args)...); + } - // The stored closure that may be cancelled. - base::Callback callback_; + template + void ForwardOnce(Args... args) { + weak_ptr_factory_.InvalidateWeakPtrs(); + std::move(callback_).Run(std::forward(args)...); + } - // Used to ensure Forward() is not run when this object is destroyed. - base::WeakPtrFactory> weak_factory_; + // The stored closure that may be cancelled. + CallbackType callback_; + mutable base::WeakPtrFactory weak_ptr_factory_; - DISALLOW_COPY_AND_ASSIGN(CancelableCallback); + DISALLOW_COPY_AND_ASSIGN(CancelableCallbackImpl); }; -typedef CancelableCallback CancelableClosure; +} // namespace internal + +// Consider using base::WeakPtr directly instead of base::CancelableCallback for +// the task cancellation. +template +using CancelableOnceCallback = + internal::CancelableCallbackImpl>; +using CancelableOnceClosure = CancelableOnceCallback; + +template +using CancelableRepeatingCallback = + internal::CancelableCallbackImpl>; +using CancelableRepeatingClosure = CancelableOnceCallback; + +template +using CancelableCallback = CancelableRepeatingCallback; +using CancelableClosure = CancelableCallback; } // namespace base diff --git a/base/cancelable_callback_unittest.cc b/base/cancelable_callback_unittest.cc index 23b6c1c..373498c 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 { private: friend class RefCountedThreadSafe; - ~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 0000000..be8270e --- /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 873da81..aec89f5 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 3d29f8f..25fd7d9 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; - using SwitchMap = std::map; - using StringPieceSwitchMap = std::map; + using SwitchMap = std::map>; // 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 79c9aec..3718cd9 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 327e3fa..a930f5d 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 0000000..b5cb364 --- /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 0000000..e994353 --- /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 0000000..092a264 --- /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 +using UniquePtrSet = base::flat_set, + base::UniquePtrComparator>; + +// ... +// Collect data. +std::vector> ptr_vec; +ptr_vec.reserve(5); +std::generate_n(std::back_inserter(ptr_vec), 5, []{ + return std::make_unique(0); +}); + +// Construct a set. +UniquePtrSet 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: + +```cpp +base::flat_map 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 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> 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 0000000..bf42a95 --- /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 +#include +#include +#include +#include + +#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); +// +// Assignment functions: +// circular_deque& operator=(const circular_deque&); +// circular_deque& operator=(circular_deque&&); +// circular_deque& operator=(std::initializer_list); +// void assign(size_t count, const T& value); +// void assign(InputIterator first, InputIterator last); +// void assign(std::initializer_list 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 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 +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; + + circular_deque_const_iterator(const circular_deque* 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(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* 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 +class circular_deque_iterator : public circular_deque_const_iterator { + using base = circular_deque_const_iterator; + + public: + friend class circular_deque; + + 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() {} + + // Dereferencing. + T& operator*() const { return const_cast(base::operator*()); } + T* operator->() const { return const_cast(base::operator->()); } + T& operator[](difference_type i) { + return const_cast(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* parent, size_t index) + : circular_deque_const_iterator(parent, index) {} +}; + +} // namespace internal + +template +class circular_deque { + private: + using VectorBuffer = internal::VectorBuffer; + + 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; + using const_iterator = internal::circular_deque_const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_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 + 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 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 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 std::enable_if<::base::internal::is_iterator::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) { + 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( + const_cast(this)->at(i)); + } + + value_type& operator[](size_type i) { return at(i); } + const value_type& operator[](size_type i) const { + return const_cast(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 + typename std::enable_if<::base::internal::is_iterator::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 + 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)...); + 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)...); + + 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 + reference emplace_front(Args&&... args) { + ExpandCapacityIfNecessary(1); + if (begin_ == 0) + begin_ = buffer_.capacity() - 1; + else + begin_--; + IncrementGeneration(); + new (&buffer_[begin_]) T(std::forward(args)...); + return front(); + } + + template + reference emplace_back(Args&&... args) { + ExpandCapacityIfNecessary(1); + new (&buffer_[end_]) T(std::forward(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; + friend internal::circular_deque_const_iterator; + + // 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 +void Erase(circular_deque& container, const Value& value) { + container.erase(std::remove(container.begin(), container.end(), value), + container.end()); +} + +template +void EraseIf(circular_deque& 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 0000000..0c168e0 --- /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 MakeSequence(size_t max) { + circular_deque 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 +void CycleTest(circular_deque& 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 foo(15); + EXPECT_EQ(15u, foo.size()); + + // Fill with default constructor. + { + circular_deque buf(num_elts); + + EXPECT_EQ(num_elts, buf.size()); + EXPECT_EQ(num_elts, static_cast(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 buf(num_elts, value); + + EXPECT_EQ(num_elts, buf.size()); + EXPECT_EQ(num_elts, static_cast(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 first(std::begin(values), std::end(values)); + + circular_deque 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 first(std::begin(values), std::end(values)); + + circular_deque 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 empty({}); + ASSERT_TRUE(empty.empty()); + + circular_deque 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 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 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 first = {1, 2, 3, 4, 5, 6}; + circular_deque 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], ©[i]); + } +} + +TEST(CircularDeque, EqualsMove) { + circular_deque first = {1, 2, 3, 4, 5, 6}; + circular_deque 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 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 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 empty; + empty.assign(0, 52); + EXPECT_EQ(0u, empty.size()); + + circular_deque 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 empty; + empty.assign(std::begin(range), std::begin(range)); + EXPECT_TRUE(empty.empty()); + + circular_deque 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 empty; + empty.assign({}); + EXPECT_TRUE(empty.empty()); + + circular_deque 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 q = MakeSequence(10); + CycleTest(q, [](const circular_deque& 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(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 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 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 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 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 a = MakeSequence(10); + circular_deque 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 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 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 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 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::iterator it = q.begin(); + circular_deque::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::const_iterator it = q.begin(); + circular_deque::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::iterator it = q.begin(); + circular_deque::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::const_iterator it = q.begin(); + circular_deque::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 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(q.end() - q.begin())); + + // += + circular_deque::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 q = MakeSequence(10); + + circular_deque::iterator begin = q.begin(); + EXPECT_EQ(0, begin[0]); + EXPECT_EQ(9, begin[9]); + + circular_deque::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 q; + q.push_back(4); + q.push_back(3); + q.push_back(2); + q.push_back(1); + + circular_deque::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 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 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 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 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 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 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 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 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 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 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 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 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 0000000..b4fe519 --- /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 +#include +#include + +#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 +struct GetKeyFromValuePairFirst { + const Key& operator()(const std::pair& 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, +// FlatContainerDupes = KEEP_FIRST_OF_DUPES, +// const Compare& compare = Compare()); // Re-use storage. +// flat_map(std::initializer_list 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); +// +// 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 insert(const value_type&); +// pair 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 insert_or_assign(K&&, M&&); +// iterator insert_or_assign(const_iterator hint, K&&, M&&); +// pair emplace(Args&&...); +// iterator emplace_hint(const_iterator, Args&&...); +// pair 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 size_t erase(const K& key); +// +// Comparators (see std::map documentation). +// key_compare key_comp() const; +// value_compare value_comp() const; +// +// Search functions: +// template size_t count(const K&) const; +// template iterator find(const K&); +// template const_iterator find(const K&) const; +// template pair equal_range(const K&); +// template iterator lower_bound(const K&); +// template const_iterator lower_bound(const K&) const; +// template iterator upper_bound(const K&); +// template 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 flat_map : public ::base::internal::flat_tree< + Key, + std::pair, + ::base::internal::GetKeyFromValuePairFirst, + Compare> { + private: + using tree = typename ::base::internal::flat_tree< + Key, + std::pair, + ::base::internal::GetKeyFromValuePairFirst, + 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 + 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 items, + FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES, + const Compare& comp = Compare()); + + flat_map(std::initializer_list 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 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 + std::pair insert_or_assign(K&& key, M&& obj); + template + iterator insert_or_assign(const_iterator hint, K&& key, M&& obj); + + template + std::enable_if_t::value, + std::pair> + try_emplace(K&& key, Args&&... args); + + template + std::enable_if_t::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 +flat_map::flat_map(const Compare& comp) : tree(comp) {} + +template +template +flat_map::flat_map(InputIterator first, + InputIterator last, + FlatContainerDupes dupe_handling, + const Compare& comp) + : tree(first, last, dupe_handling, comp) {} + +template +flat_map::flat_map(std::vector items, + FlatContainerDupes dupe_handling, + const Compare& comp) + : tree(std::move(items), dupe_handling, comp) {} + +template +flat_map::flat_map( + std::initializer_list ilist, + FlatContainerDupes dupe_handling, + const Compare& comp) + : flat_map(std::begin(ilist), std::end(ilist), dupe_handling, comp) {} + +// ---------------------------------------------------------------------------- +// Assignments. + +template +auto flat_map::operator=( + std::initializer_list 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 +auto flat_map::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 +auto flat_map::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 +template +auto flat_map::insert_or_assign(K&& key, M&& obj) + -> std::pair { + auto result = + tree::emplace_key_args(key, std::forward(key), std::forward(obj)); + if (!result.second) + result.first->second = std::forward(obj); + return result; +} + +template +template +auto flat_map::insert_or_assign(const_iterator hint, + K&& key, + M&& obj) -> iterator { + auto result = tree::emplace_hint_key_args(hint, key, std::forward(key), + std::forward(obj)); + if (!result.second) + result.first->second = std::forward(obj); + return result.first; +} + +template +template +auto flat_map::try_emplace(K&& key, Args&&... args) + -> std::enable_if_t::value, + std::pair> { + return tree::emplace_key_args( + key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); +} + +template +template +auto flat_map::try_emplace(const_iterator hint, + K&& key, + Args&&... args) + -> std::enable_if_t::value, iterator> { + return tree::emplace_hint_key_args( + hint, key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)) + .first; +} + +// ---------------------------------------------------------------------------- +// General operations. + +template +void flat_map::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 0000000..bf14c36 --- /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 + +#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, +// FlatContainerDupes = KEEP_FIRST_OF_DUPES, +// const Compare& compare = Compare()); // Re-use storage. +// flat_set(std::initializer_list 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); +// +// 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 insert(const key_type&); +// pair 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 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 size_t erase(const K& key); +// +// Comparators (see std::set documentation). +// key_compare key_comp() const; +// value_compare value_comp() const; +// +// Search functions: +// template size_t count(const K&) const; +// template iterator find(const K&); +// template const_iterator find(const K&) const; +// template pair equal_range(K&); +// template iterator lower_bound(const K&); +// template const_iterator lower_bound(const K&) const; +// template iterator upper_bound(const K&); +// template 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 > +using flat_set = typename ::base::internal::flat_tree< + Key, + Key, + ::base::internal::GetKeyFromValueIdentity, + 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 0000000..7856e24 --- /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 +#include +#include +#include + +#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 +constexpr bool is_multipass() { + return std::is_base_of< + std::forward_iterator_tag, + typename std::iterator_traits::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 +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 +struct IsTransparentCompare : std::false_type {}; +template +struct IsTransparentCompare> + : 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 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 flat_tree { + private: + using underlying_type = std::vector; + + 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 + explicit value_compare(Cmp&& compare_arg) + : KeyCompare(std::forward(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 + 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 items, + FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES, + const key_compare& comp = key_compare()); + + flat_tree(std::initializer_list 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 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 insert(const value_type& val); + std::pair 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 + void insert(InputIterator first, + InputIterator last, + FlatContainerDupes dupes = KEEP_FIRST_OF_DUPES); + + template + std::pair emplace(Args&&... args); + + template + 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 + 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 + size_type count(const K& key) const; + + template + iterator find(const K& key); + + template + const_iterator find(const K& key) const; + + template + std::pair equal_range(const K& key); + + template + std::pair equal_range(const K& key) const; + + template + iterator lower_bound(const K& key); + + template + const_iterator lower_bound(const K& key) const; + + template + iterator upper_bound(const K& key); + + template + 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 + 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 + std::pair emplace_key_args(const K& key, Args&&... args); + + // Similar to |emplace_key_args|, but checks |hint| first as a possible + // insertion position. + template + std::pair 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 + 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 + 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 + std::pair 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(val)), true}; + + *position = std::forward(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 + std::pair 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(val)); + return {std::next(begin(), distance), true}; + } + + *position = std::forward(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 + std::pair 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(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 + explicit Impl(Cmp&& compare_arg, Body&&... underlying_type_args) + : value_compare(std::forward(compare_arg)), + body_(std::forward(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 + using KeyTypeOrK = typename std:: + conditional::value, K, key_type>::type; +}; + +// ---------------------------------------------------------------------------- +// Lifetime. + +template +flat_tree::flat_tree() = default; + +template +flat_tree::flat_tree( + const KeyCompare& comp) + : impl_(comp) {} + +template +template +flat_tree::flat_tree( + InputIterator first, + InputIterator last, + FlatContainerDupes dupe_handling, + const KeyCompare& comp) + : impl_(comp, first, last) { + sort_and_unique(begin(), end(), dupe_handling); +} + +template +flat_tree::flat_tree( + const flat_tree&) = default; + +template +flat_tree::flat_tree( + std::vector items, + FlatContainerDupes dupe_handling, + const KeyCompare& comp) + : impl_(comp, std::move(items)) { + sort_and_unique(begin(), end(), dupe_handling); +} + +template +flat_tree::flat_tree( + std::initializer_list ilist, + FlatContainerDupes dupe_handling, + const KeyCompare& comp) + : flat_tree(std::begin(ilist), std::end(ilist), dupe_handling, comp) {} + +template +flat_tree::~flat_tree() = default; + +// ---------------------------------------------------------------------------- +// Assignments. + +template +auto flat_tree::operator=( + const flat_tree&) -> flat_tree& = default; + +template +auto flat_tree::operator=(flat_tree &&) + -> flat_tree& = default; + +template +auto flat_tree::operator=( + std::initializer_list ilist) -> flat_tree& { + impl_.body_ = ilist; + sort_and_unique(begin(), end(), KEEP_FIRST_OF_DUPES); + return *this; +} + +// ---------------------------------------------------------------------------- +// Memory management. + +template +void flat_tree::reserve( + size_type new_capacity) { + impl_.body_.reserve(new_capacity); +} + +template +auto flat_tree::capacity() const + -> size_type { + return impl_.body_.capacity(); +} + +template +void flat_tree::shrink_to_fit() { + impl_.body_.shrink_to_fit(); +} + +// ---------------------------------------------------------------------------- +// Size management. + +template +void flat_tree::clear() { + impl_.body_.clear(); +} + +template +auto flat_tree::size() const + -> size_type { + return impl_.body_.size(); +} + +template +auto flat_tree::max_size() const + -> size_type { + return impl_.body_.max_size(); +} + +template +bool flat_tree::empty() const { + return impl_.body_.empty(); +} + +// ---------------------------------------------------------------------------- +// Iterators. + +template +auto flat_tree::begin() -> iterator { + return impl_.body_.begin(); +} + +template +auto flat_tree::begin() const + -> const_iterator { + return impl_.body_.begin(); +} + +template +auto flat_tree::cbegin() const + -> const_iterator { + return impl_.body_.cbegin(); +} + +template +auto flat_tree::end() -> iterator { + return impl_.body_.end(); +} + +template +auto flat_tree::end() const + -> const_iterator { + return impl_.body_.end(); +} + +template +auto flat_tree::cend() const + -> const_iterator { + return impl_.body_.cend(); +} + +template +auto flat_tree::rbegin() + -> reverse_iterator { + return impl_.body_.rbegin(); +} + +template +auto flat_tree::rbegin() const + -> const_reverse_iterator { + return impl_.body_.rbegin(); +} + +template +auto flat_tree::crbegin() const + -> const_reverse_iterator { + return impl_.body_.crbegin(); +} + +template +auto flat_tree::rend() + -> reverse_iterator { + return impl_.body_.rend(); +} + +template +auto flat_tree::rend() const + -> const_reverse_iterator { + return impl_.body_.rend(); +} + +template +auto flat_tree::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 +auto flat_tree::insert( + const value_type& val) -> std::pair { + return emplace_key_args(GetKeyFromValue()(val), val); +} + +template +auto flat_tree::insert( + value_type&& val) -> std::pair { + return emplace_key_args(GetKeyFromValue()(val), std::move(val)); +} + +template +auto flat_tree::insert( + const_iterator position_hint, + const value_type& val) -> iterator { + return emplace_hint_key_args(position_hint, GetKeyFromValue()(val), val) + .first; +} + +template +auto flat_tree::insert( + const_iterator position_hint, + value_type&& val) -> iterator { + return emplace_hint_key_args(position_hint, GetKeyFromValue()(val), + std::move(val)) + .first; +} + +template +template +void flat_tree::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() && 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 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 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 +template +auto flat_tree::emplace(Args&&... args) + -> std::pair { + return insert(value_type(std::forward(args)...)); +} + +template +template +auto flat_tree::emplace_hint( + const_iterator position_hint, + Args&&... args) -> iterator { + return insert(position_hint, value_type(std::forward(args)...)); +} + +// ---------------------------------------------------------------------------- +// Erase operations. + +template +auto flat_tree::erase( + iterator position) -> iterator { + return impl_.body_.erase(position); +} + +template +auto flat_tree::erase( + const_iterator position) -> iterator { + return impl_.body_.erase(position); +} + +template +template +auto flat_tree::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 +auto flat_tree::erase( + const_iterator first, + const_iterator last) -> iterator { + return impl_.body_.erase(first, last); +} + +// ---------------------------------------------------------------------------- +// Comparators. + +template +auto flat_tree::key_comp() const + -> key_compare { + return impl_.get_key_comp(); +} + +template +auto flat_tree::value_comp() const + -> value_compare { + return impl_.get_value_comp(); +} + +// ---------------------------------------------------------------------------- +// Search operations. + +template +template +auto flat_tree::count( + const K& key) const -> size_type { + auto eq_range = equal_range(key); + return std::distance(eq_range.first, eq_range.second); +} + +template +template +auto flat_tree::find(const K& key) + -> iterator { + return const_cast_it(as_const().find(key)); +} + +template +template +auto flat_tree::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 +template +auto flat_tree::equal_range( + const K& key) -> std::pair { + auto res = as_const().equal_range(key); + return {const_cast_it(res.first), const_cast_it(res.second)}; +} + +template +template +auto flat_tree::equal_range( + const K& key) const -> std::pair { + 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 +template +auto flat_tree::lower_bound( + const K& key) -> iterator { + return const_cast_it(as_const().lower_bound(key)); +} + +template +template +auto flat_tree::lower_bound( + const K& key) const -> const_iterator { + static_assert(std::is_convertible&, const K&>::value, + "Requested type cannot be bound to the container's key_type " + "which is required for a non-transparent compare."); + + const KeyTypeOrK& key_ref = key; + + KeyValueCompare key_value(impl_.get_key_comp()); + return std::lower_bound(begin(), end(), key_ref, key_value); +} + +template +template +auto flat_tree::upper_bound( + const K& key) -> iterator { + return const_cast_it(as_const().upper_bound(key)); +} + +template +template +auto flat_tree::upper_bound( + const K& key) const -> const_iterator { + static_assert(std::is_convertible&, const K&>::value, + "Requested type cannot be bound to the container's key_type " + "which is required for a non-transparent compare."); + + const KeyTypeOrK& key_ref = key; + + KeyValueCompare key_value(impl_.get_key_comp()); + return std::upper_bound(begin(), end(), key_ref, key_value); +} + +// ---------------------------------------------------------------------------- +// General operations. + +template +void flat_tree::swap( + flat_tree& other) noexcept { + std::swap(impl_, other.impl_); +} + +template +template +auto flat_tree::unsafe_emplace( + const_iterator position, + Args&&... args) -> iterator { + return impl_.body_.emplace(position, std::forward(args)...); +} + +template +template +auto flat_tree::emplace_key_args( + const K& key, + Args&&... args) -> std::pair { + auto lower = lower_bound(key); + if (lower == end() || key_comp()(key, GetKeyFromValue()(*lower))) + return {unsafe_emplace(lower, std::forward(args)...), true}; + return {lower, false}; +} + +template +template +auto flat_tree::emplace_hint_key_args( + const_iterator hint, + const K& key, + Args&&... args) -> std::pair { + 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)...), 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)...); +} + +// 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 +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 +void EraseIf(base::internal::flat_tree& + 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 41461ff..a913bad 100644 --- a/base/containers/linked_list.h +++ b/base/containers/linked_list.h @@ -84,10 +84,24 @@ namespace base { template class LinkNode { public: - LinkNode() : previous_(NULL), next_(NULL) {} + LinkNode() : previous_(nullptr), next_(nullptr) {} LinkNode(LinkNode* previous, LinkNode* next) : previous_(previous), next_(next) {} + LinkNode(LinkNode&& 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* 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* previous() const { diff --git a/base/containers/mru_cache.h b/base/containers/mru_cache.h index 7c684a9..4a9f44e 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 +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))); - index_.insert(std::make_pair(key, ordering_.begin())); + ordering_.emplace_front(key, std::forward(payload)); + index_.emplace(key, ordering_.begin()); return ordering_.begin(); } @@ -195,6 +203,10 @@ class MRUCacheBase { bool empty() const { return ordering_.empty(); } private: + template + friend size_t trace_event::internal::DoEstimateMemoryUsageForMruCache( + const MruCacheType&); + PayloadList ordering_; KeyIndex index_; @@ -218,7 +230,7 @@ class MRUCache : public MRUCacheBase { // 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 0000000..b5bc5c3 --- /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 + +#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 > +using queue = std::queue; + +} // 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 0000000..ca4a48d --- /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 + +#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 +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& buffer, size_t index) + : buffer_(buffer), index_(index), out_of_range_(false) {} + + const RingBuffer& buffer_; + size_t index_; + bool out_of_range_; + + friend class RingBuffer; + }; + + // Returns an Iterator pointing to the oldest value in the buffer. + // Example usage (iterate from oldest to newest value): + // for (RingBuffer::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::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 2945d58..495332f 100644 --- a/base/containers/small_map.h +++ b/base/containers/small_map.h @@ -8,68 +8,57 @@ #include #include +#include #include #include #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, 4, std::equal_to > +// base::small_map, 4, std::equal_to> // // // 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* 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 > days; +// base::small_map> 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 -class SmallMapDefaultInit { +class small_map_default_init { public: - void operator()(ManualConstructor* map) const { - map->Init(); - } + void operator()(NormalMap* map) const { new (map) NormalMap(); } }; // has_key_equal::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 -struct select_equal_key< std::map, false> { +struct select_equal_key, 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, false> { }; }; template -struct select_equal_key< base::hash_map, false> { +struct select_equal_key, false> { struct equal_key { bool operator()(const KeyType& left, const KeyType& right) { return left == right; @@ -178,12 +161,11 @@ struct select_equal_key { template ::value>::equal_key, - typename MapInit = internal::SmallMapDefaultInit > -class SmallMap { + typename EqualKey = typename internal::select_equal_key< + NormalMap, + internal::has_key_equal::value>::equal_key, + typename MapInit = internal::small_map_default_init> +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* 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* 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* 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* 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 ret = map_->insert(x); + std::pair 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 ret = map_->insert(x); + std::pair ret = map_.insert(x); return std::make_pair(iterator(ret.first), ret.second); } } @@ -469,18 +447,46 @@ class SmallMap { } } + // Invalidates iterators. + template + std::pair emplace(Args&&... args) { + key_equal compare; + + if (size_ >= 0) { + value_type x(std::forward(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 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 ret = + map_.emplace(std::forward(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_); } 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 array_[kArraySize]; - ManualConstructor map_; + value_type array_[kArraySize]; + NormalMap map_; }; void ConvertToRealMap() { - // Move the current elements into a temporary array. - ManualConstructor 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 -inline bool SmallMap::iterator::operator==( - const const_iterator& other) const { +inline bool small_map::iterator:: +operator==(const const_iterator& other) const { return other == *this; } -template -inline bool SmallMap::iterator::operator!=( - const const_iterator& other) const { +inline bool small_map::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 0000000..f1e0000 --- /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 + +#include +#include +#include +#include +#include + +#include "base/logging.h" +#include "base/stl_util.h" + +namespace base { + +// [views.constants] +constexpr size_t dynamic_extent = static_cast(-1); + +template +class span; + +namespace internal { + +template +struct IsSpanImpl : std::false_type {}; + +template +struct IsSpanImpl> : std::true_type {}; + +template +using IsSpan = IsSpanImpl>; + +template +struct IsStdArrayImpl : std::false_type {}; + +template +struct IsStdArrayImpl> : std::true_type {}; + +template +using IsStdArray = IsStdArrayImpl>; + +template +using IsCArray = std::is_array>; + +template +using IsLegalDataConversion = std::is_convertible; + +template +using ContainerHasConvertibleData = IsLegalDataConversion< + std::remove_pointer_t()))>, + T>; + +template +using ContainerHasIntegralSize = + std::is_integral()))>; + +template +using EnableIfLegalSpanConversion = + std::enable_if_t<(ToExtent == dynamic_extent || ToExtent == FromExtent) && + IsLegalDataConversion::value>; + +// SFINAE check if Array can be converted to a span. +template +using EnableIfSpanCompatibleArray = + std::enable_if_t<(Extent == dynamic_extent || Extent == N) && + ContainerHasConvertibleData::value>; + +// SFINAE check if Container can be converted to a span. +template +using EnableIfSpanCompatibleContainer = + std::enable_if_t::value && + !internal::IsStdArray::value && + !internal::IsCArray::value && + ContainerHasConvertibleData::value && + ContainerHasIntegralSize::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). A mutable span can also be implicitly converted to an +// immutable span. +// +// 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: the caller can supply either +// a span or a span, while the callee will have a read-only view. +// For read-write access a mutable span is required. +// +// Without span: +// Read-Only: +// // std::string HexEncode(const uint8_t* data, size_t size); +// std::vector 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 data); +// std::vector data_buffer = GenerateData(); +// std::string r = HexEncode(data_buffer); +// +// Mutable: +// // ssize_t SafeSNPrintf(base::span, 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 => base::span +// std::vector => base::span +// const std::vector => base::span +// +// 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 +class span { + public: + using element_type = T; + using value_type = std::remove_cv_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; + using const_reverse_iterator = std::reverse_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 + 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> + constexpr span(T (&array)[N]) noexcept : span(base::data(array), N) {} + + template < + size_t N, + typename = internal:: + EnableIfSpanCompatibleArray&, N, T, Extent>> + constexpr span(std::array& array) noexcept + : span(base::data(array), N) {} + + template &, + N, + T, + Extent>> + constexpr span(const std::array& array) noexcept + : span(base::data(array), N) {} + + // Conversion from a container that has compatible base::data() and integral + // base::size(). + template > + constexpr span(Container& container) noexcept + : span(base::data(container), base::size(container)) {} + + template < + typename Container, + typename = internal::EnableIfSpanCompatibleContainer> + 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 to be seamlessly used as a span, 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> + constexpr span(const span& other) + : span(other.data(), other.size()) {} + + constexpr span& operator=(const span& other) noexcept = default; + ~span() noexcept = default; + + // [span.sub], span subviews + template + constexpr span 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 + constexpr span 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 + constexpr span + 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 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 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 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::extent can not be declared inline prior to C++17, hence this +// definition is required. +template +constexpr size_t span::extent; + +// [span.comparison], span comparison operators +// Relational operators. Equality is a element-wise comparison. +template +constexpr bool operator==(span lhs, span rhs) noexcept { + return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend()); +} + +template +constexpr bool operator!=(span lhs, span rhs) noexcept { + return !(lhs == rhs); +} + +template +constexpr bool operator<(span lhs, span rhs) noexcept { + return std::lexicographical_compare(lhs.cbegin(), lhs.cend(), rhs.cbegin(), + rhs.cend()); +} + +template +constexpr bool operator<=(span lhs, span rhs) noexcept { + return !(rhs < lhs); +} + +template +constexpr bool operator>(span lhs, span rhs) noexcept { + return rhs < lhs; +} + +template +constexpr bool operator>=(span lhs, span rhs) noexcept { + return !(lhs < rhs); +} + +// [span.objectrep], views of object representation +template +span +as_bytes(span s) noexcept { + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +template ::value>> +span +as_writable_bytes(span s) noexcept { + return {reinterpret_cast(s.data()), s.size_bytes()}; +} + +// Type-deducing helpers for constructing a span. +template +constexpr span make_span(T* data, size_t size) noexcept { + return {data, size}; +} + +template +constexpr span make_span(T* begin, T* end) noexcept { + return {begin, end}; +} + +template +constexpr span make_span(T (&array)[N]) noexcept { + return array; +} + +template +constexpr span make_span(std::array& array) noexcept { + return array; +} + +template +constexpr span make_span(const std::array& array) noexcept { + return array; +} + +template > +constexpr span make_span(Container& container) noexcept { + return container; +} + +template < + typename Container, + typename T = const typename Container::value_type, + typename = internal::EnableIfSpanCompatibleContainer> +constexpr span make_span(const Container& container) noexcept { + return container; +} + +template +constexpr span make_span(const span& 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 0000000..de5e401 --- /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 + +#include +#include +#include +#include + +#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 dynamic_span; + EXPECT_EQ(nullptr, dynamic_span.data()); + EXPECT_EQ(0u, dynamic_span.size()); + + constexpr span static_span; + static_assert(nullptr == static_span.data(), ""); + static_assert(0u == static_span.size(), ""); +} + +TEST(SpanTest, ConstructFromDataAndSize) { + constexpr span empty_span(nullptr, 0); + EXPECT_TRUE(empty_span.empty()); + EXPECT_EQ(nullptr, empty_span.data()); + + std::vector vector = {1, 1, 2, 3, 5, 8}; + + span 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 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 empty_span(nullptr, nullptr); + EXPECT_TRUE(empty_span.empty()); + EXPECT_EQ(nullptr, empty_span.data()); + + std::vector vector = {1, 1, 2, 3, 5, 8}; + + span 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 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 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 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_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 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 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 array = {{5, 4, 3, 2, 1}}; + + span 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 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 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 il = {1, 1, 2, 3, 5, 8}; + + span 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 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_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 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 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 vector = {1, 1, 2, 3, 5, 8}; + + span 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 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 vector = {1, 1, 2, 3, 5, 8}; + + span 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 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 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 vector = {1, 1, 2, 3, 5, 8}; + + span int_span(vector.data(), vector.size()); + span const_span(int_span); + EXPECT_THAT(const_span, Pointwise(Eq(), int_span)); + + span static_int_span(vector.data(), vector.size()); + span static_const_span(static_int_span); + EXPECT_THAT(static_const_span, Pointwise(Eq(), static_int_span)); +} + +TEST(SpanTest, ConvertNonConstPointerToConst) { + auto a = std::make_unique(11); + auto b = std::make_unique(22); + auto c = std::make_unique(33); + std::vector vector = {a.get(), b.get(), c.get()}; + + span non_const_pointer_span(vector); + EXPECT_THAT(non_const_pointer_span, Pointwise(Eq(), vector)); + span 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 to span, since that + // would imply a conversion from int** to const int**, which is unsafe. + // + // Note: no test for conversion from span to span, + // due to CWG Defect 330: + // http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#330 + + span static_non_const_pointer_span(vector); + EXPECT_THAT(static_non_const_pointer_span, Pointwise(Eq(), vector)); + span 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 vector = {2, 4, 8, 16, 32}; + + span int32_t_span(vector); + span converted_span(int32_t_span); + EXPECT_EQ(int32_t_span, converted_span); + + span static_int32_t_span(vector); + span 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 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 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 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 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 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 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 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 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 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 span; + EXPECT_EQ(0u, span.size()); + } + + { + int array[] = {1, 2, 3}; + span span(array); + EXPECT_EQ(3u, span.size()); + } +} + +TEST(SpanTest, SizeBytes) { + { + span span; + EXPECT_EQ(0u, span.size_bytes()); + } + + { + int array[] = {1, 2, 3}; + span span(array); + EXPECT_EQ(3u * sizeof(int), span.size_bytes()); + } +} + +TEST(SpanTest, Empty) { + { + span span; + EXPECT_TRUE(span.empty()); + } + + { + int array[] = {1, 2, 3}; + span span(array); + EXPECT_FALSE(span.empty()); + } +} + +TEST(SpanTest, OperatorAt) { + static constexpr int kArray[] = {1, 6, 1, 8, 0}; + constexpr span 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 span(kArray); + + std::vector 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 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 span1(kArray1); + constexpr span span2(kArray2); + + EXPECT_EQ(span1, span2); + + static constexpr int kArray3[] = {2, 7, 1, 8, 3}; + constexpr span span3(kArray3); + + EXPECT_FALSE(span1 == span3); + + static double kArray4[] = {2.0, 7.0, 1.0, 8.0, 3.0}; + span 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 span1(kArray1); + constexpr span span2(kArray2); + + EXPECT_NE(span1, span2); + + static constexpr int kArray3[] = {2, 3, 5, 7, 11}; + constexpr span span3(kArray3); + + EXPECT_FALSE(span1 != span3); + + static double kArray4[] = {1.0, 4.0, 6.0, 8.0, 9.0}; + span 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 span1(kArray1); + constexpr span span2(kArray2); + + EXPECT_LT(span1, span2); + + static constexpr int kArray3[] = {2, 3, 5, 7, 11}; + constexpr span span3(kArray3); + + EXPECT_FALSE(span1 < span3); + + static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0, 13.0}; + span 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 span1(kArray1); + constexpr span span2(kArray2); + + EXPECT_LE(span1, span1); + EXPECT_LE(span1, span2); + + static constexpr int kArray3[] = {2, 3, 5, 7, 10}; + constexpr span span3(kArray3); + + EXPECT_FALSE(span1 <= span3); + + static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0, 13.0}; + span 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 span1(kArray1); + constexpr span span2(kArray2); + + EXPECT_GT(span1, span2); + + static constexpr int kArray3[] = {2, 3, 5, 7, 11, 13}; + constexpr span span3(kArray3); + + EXPECT_FALSE(span1 > span3); + + static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0}; + span 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 span1(kArray1); + constexpr span span2(kArray2); + + EXPECT_GE(span1, span1); + EXPECT_GE(span1, span2); + + static constexpr int kArray3[] = {2, 3, 5, 7, 12}; + constexpr span span3(kArray3); + + EXPECT_FALSE(span1 >= span3); + + static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0}; + span span4(kArray4); + + EXPECT_GE(span3, span4); +} + +TEST(SpanTest, AsBytes) { + { + constexpr int kArray[] = {2, 3, 5, 7, 11, 13}; + span bytes_span = + as_bytes(make_span(kArray)); + EXPECT_EQ(reinterpret_cast(kArray), bytes_span.data()); + EXPECT_EQ(sizeof(kArray), bytes_span.size()); + EXPECT_EQ(bytes_span.size(), bytes_span.size_bytes()); + } + + { + std::vector vec = {1, 1, 2, 3, 5, 8}; + span mutable_span(vec); + span bytes_span = as_bytes(mutable_span); + EXPECT_EQ(reinterpret_cast(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 vec = {1, 1, 2, 3, 5, 8}; + span mutable_span(vec); + span writable_bytes_span = as_writable_bytes(mutable_span); + EXPECT_EQ(reinterpret_cast(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 vector = {1, 1, 2, 3, 5, 8}; + span 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 vector = {1, 1, 2, 3, 5, 8}; + span 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 span(kArray); + EXPECT_EQ(span, make_span(kArray)); + static_assert(decltype(make_span(kArray))::extent == 5, ""); +} + +TEST(SpanTest, MakeSpanFromStdArray) { + const std::array kArray = {{1, 2, 3, 4, 5}}; + span span(kArray); + EXPECT_EQ(span, make_span(kArray)); + static_assert(decltype(make_span(kArray))::extent == 5, ""); +} + +TEST(SpanTest, MakeSpanFromConstContainer) { + const std::vector vector = {-1, -2, -3, -4, -5}; + span span(vector); + EXPECT_EQ(span, make_span(vector)); + static_assert(decltype(make_span(vector))::extent == dynamic_extent, ""); +} + +TEST(SpanTest, MakeSpanFromContainer) { + std::vector vector = {-1, -2, -3, -4, -5}; + span 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 span(kArray); + static_assert(std::is_same::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 span(kArray); + static_assert(std::is_same::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 constexpr_span(kArray); + const size_t size = 2; + + const size_t start = 1; + constexpr span 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 firsts = constexpr_span.first(size); + for (size_t i = 0; i < firsts.size(); ++i) + EXPECT_EQ(kArray[i], firsts[i]); + + constexpr span 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 0000000..0d2af89 --- /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 +#include +#include + +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 span; +} + +#elif defined(NCTEST_SPAN_FROM_ARRAY_WITH_NON_MATCHING_STATIC_EXTENT_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span'"] + +// A span with static extent constructed from an array must match the size of +// the array. +void WontCompile() { + int array[] = {1, 2, 3}; + span 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'"] + +// A span with static extent constructed from std::array must match the size of +// the array. +void WontCompile() { + std::array array = {1, 2, 3}; + span 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'"] + +// A span with static extent constructed from std::array must match the size of +// the array. +void WontCompile() { + const std::array array = {1, 2, 3}; + span span(array); +} + +#elif defined(NCTEST_SPAN_FROM_OTHER_SPAN_WITH_MISMATCHING_EXTENT_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span'"] + +// A span with static extent constructed from another span must match the +// extent. +void WontCompile() { + std::array array = {1, 2, 3}; + span span3(array); + span span4(span3); +} + +#elif defined(NCTEST_DYNAMIC_SPAN_TO_STATIC_SPAN_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span'"] + +// Converting a dynamic span to a static span should not be allowed. +void WontCompile() { + span dynamic_span; + span static_span(dynamic_span); +} + +#elif defined(NCTEST_DERIVED_TO_BASE_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span'"] + +// 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_span; + span base_span(derived_span); +} + +#elif defined(NCTEST_PTR_TO_CONSTPTR_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span'"] + +// Similarly, converting a span to span 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 non_const_span; + span const_span(non_const_span); +} + +#elif defined(NCTEST_CONST_CONTAINER_TO_MUTABLE_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span'"] + +// A const container should not be convertible to a mutable span. +void WontCompile() { + const std::vector v = {1, 2, 3}; + span span(v); +} + +#elif defined(NCTEST_STD_SET_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span'"] + +// A std::set() should not satisfy the requirements for conversion to a span. +void WontCompile() { + std::set set; + span 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 array = {1, 2, 3}; + span 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 array = {1, 2, 3}; + span 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 array = {1, 2, 3}; + span 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 array = {1, 2, 3}; + span 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 v = {1, 2, 3}; + span 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 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 0000000..5cf06f8 --- /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 + +#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 > +using stack = std::stack; + +} // namespace base + +#endif // BASE_CONTAINERS_STACK_H_ diff --git a/base/containers/stack_container.h b/base/containers/stack_container.h index 9e0efc1..c775744 100644 --- a/base/containers/stack_container.h +++ b/base/containers/stack_container.h @@ -7,12 +7,9 @@ #include -#include #include #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 { } // Casts the buffer in its right type. - T* stack_buffer() { return stack_buffer_.template data_as(); } + T* stack_buffer() { return reinterpret_cast(stack_buffer_); } const T* stack_buffer() const { - return stack_buffer_.template data_as(); + return reinterpret_cast(&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 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 { // 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 -class StackString : public StackContainer< - std::basic_string, - StackAllocator >, - stack_capacity> { - public: - StackString() : StackContainer< - std::basic_string, - StackAllocator >, - stack_capacity>() { - } - - private: - DISALLOW_COPY_AND_ASSIGN(StackString); -}; - -// StackStrin16 ---------------------------------------------------------------- - -template -class StackString16 : public StackContainer< - std::basic_string >, - stack_capacity> { - public: - StackString16() : StackContainer< - std::basic_string >, - 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 0000000..a72c1ed --- /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 +#include + +#include +#include + +#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 +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(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 ::value, + int>::type = 0> + void DestructRange(T* begin, T* end) {} + + // Non-trivially destructible objects must have their destructors called + // individually. + template ::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 ::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 ::value && + !base::is_trivially_copyable::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 ::value && + !base::is_trivially_copyable::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 0000000..6d49505 --- /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 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 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 dest(size); + + VectorBuffer 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 dest(size); + + VectorBuffer 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 dest(size); + + VectorBuffer 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 848208f..cd9066f 100644 --- a/base/cpu.cc +++ b/base/cpu.cc @@ -10,6 +10,7 @@ #include #include +#include #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 #include // 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(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 0e24df6..2c6caea 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 9cabfd6..8a68ea0 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 5081c1c..24cfa5a 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(std::numeric_limits::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 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 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(base)), stack_(reinterpret_cast(reinterpret_cast(base) + sizeof(Header))), +#if DCHECK_IS_ON() + thread_id_(PlatformThreadRef()), +#endif stack_slots_( static_cast((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 ThreadActivityTracker::GetUserData( @@ -854,12 +853,12 @@ std::unique_ptr 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(); + return std::make_unique(); } // User-data is only stored for activities actually held in the stack. if (id >= stack_slots_) - return MakeUnique(); + return std::make_unique(); // 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(stack_depth) * sizeof(Activity) + sizeof(Header); } +bool ThreadActivityTracker::CalledOnValidThread() { +#if DCHECK_IS_ON() + return thread_id_ == PlatformThreadRef(); +#else + return true; +#endif +} + std::unique_ptr ThreadActivityTracker::CreateUserDataForActivity( Activity* activity, @@ -1053,14 +1065,14 @@ ThreadActivityTracker::CreateUserDataForActivity( void* memory = allocator->GetAsArray(ref, kUserDataSize); if (memory) { std::unique_ptr user_data = - MakeUnique(memory, kUserDataSize); + std::make_unique(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(); + return std::make_unique(); } // 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(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(); + user_data_ = std::make_unique(); } } 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 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(size)}, - MemoryMappedFile::READ_WRITE_EXTEND); - DCHECK(success); - CreateWithAllocator(MakeUnique( + 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( 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(size, id, name), stack_depth, - process_id); + std::make_unique(size, id, name), + stack_depth, process_id); + return true; +} + +// static +bool GlobalActivityTracker::CreateWithSharedMemory( + std::unique_ptr shm, + uint64_t id, + StringPiece name, + int stack_depth) { + if (shm->mapped_size() == 0 || + !SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(*shm)) { + return false; + } + CreateWithAllocator(std::make_unique( + 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 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(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( 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( 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(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( - 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 c8cf1e9..5647d86 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 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 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( @@ -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 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 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 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 c7efa58..e2b61a9 100644 --- a/base/debug/activity_tracker_unittest.cc +++ b/base/debug/activity_tracker_unittest.cc @@ -7,6 +7,7 @@ #include #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 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 CreateActivityTracker() { std::unique_ptr memory(new char[kStackSize]); - return MakeUnique(std::move(memory), kStackSize); + return std::make_unique(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 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 ready_; + std::atomic 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 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 6b0caaa..ebc8b5a 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 3b2ab64..128fdaa 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 0000000..66682f1 --- /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 +#include + +#include "base/debug/alias.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(DebugAlias, Test) { + std::unique_ptr input = + std::make_unique("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 0000000..1dabb6b --- /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 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 0000000..9c6cd75 --- /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 + +#include + +#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 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 3255552..b62bf01 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_flags.h b/base/debug/debugging_buildflags.h similarity index 58% rename from base/debug/debugging_flags.h rename to base/debug/debugging_buildflags.h index e6ae1ee..33e3dd0 100644 --- a/base/debug/debugging_flags.h +++ b/base/debug/debugging_buildflags.h @@ -5,4 +5,8 @@ #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/dump_without_crashing.cc b/base/debug/dump_without_crashing.cc index 4b338ca..1ab8c9c 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 a5c85d5..913f6c4 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 0000000..cdf8193 --- /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 +#include + +#include + +#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; + +Optional ElfSegmentBuildIDNoteAsString(const ElfSegment& segment) { + const void* section_end = segment.data() + segment.size_bytes(); + const Nhdr* note_header = reinterpret_cast(segment.data()); + while (note_header < section_end) { + if (note_header->n_type == NT_GNU_BUILD_ID) + break; + note_header = reinterpret_cast( + reinterpret_cast(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(note_header) + + sizeof(Nhdr) + bits::Align(note_header->n_namesz, 4); + + uint32_t dword = htonl(*reinterpret_cast(guid)); + uint16_t word1 = htons(*reinterpret_cast(guid + 4)); + uint16_t word2 = htons(*reinterpret_cast(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 FindElfSegments(const void* elf_mapped_base, + uint32_t segment_type) { + const char* elf_base = reinterpret_cast(elf_mapped_base); + if (strncmp(elf_base, ELFMAG, SELFMAG) != 0) + return std::vector(); + + const Ehdr* elf_header = reinterpret_cast(elf_base); + const Phdr* phdrs = + reinterpret_cast(elf_base + elf_header->e_phoff); + std::vector 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 ReadElfBuildId(const void* elf_base) { + // Elf program headers can have multiple PT_NOTE arrays. + std::vector segs = FindElfSegments(elf_base, PT_NOTE); + if (segs.empty()) + return nullopt; + Optional id; + for (const ElfSegment& seg : segs) { + id = ElfSegmentBuildIDNoteAsString(seg); + if (id) + return id; + } + + return nullopt; +} + +Optional ReadElfLibraryName(const void* elf_base) { + std::vector 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(dynamic_seg.data()); + const Dyn* dynamic_end = reinterpret_cast( + dynamic_seg.data() + dynamic_seg.size_bytes()); + Optional 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(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 0000000..4086dfb --- /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 + +#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 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 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 0000000..5510418 --- /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 + +#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 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 9dd1622..7ddd5b6 100644 --- a/base/debug/leak_tracker.h +++ b/base/debug/leak_tracker.h @@ -56,6 +56,8 @@ namespace debug { template 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 38e9231..f5f8a59 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 b19e7ec..ef9afb6 100644 --- a/base/debug/profiler.cc +++ b/base/debug/profiler.cc @@ -6,7 +6,7 @@ #include -#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 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 ea81b13..f706a1a 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 43a23d9..7715121 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 @@ -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 ab1d2eb..81e6672 100644 --- a/base/debug/stack_trace.h +++ b/base/debug/stack_trace.h @@ -11,6 +11,7 @@ #include #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 4a55f64..f3f05da 100644 --- a/base/debug/stack_trace_posix.cc +++ b/base/debug/stack_trace_posix.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,7 @@ #if !defined(USE_SYMBOLIZE) #include #endif -#if !defined(__UCLIBC__) +#if !defined(__UCLIBC__) && !defined(_AIX) #include #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 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 using a const_iterator does not allocate // dynamic memory, hence it is async-signal-safe. - std::vector::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 46969f2..18083c1 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* GetTLSForCurrentPendingTask() { + static NoDestructor> + 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_sizetask_backtrace)>::value + 1; + std::tuple_sizetask_backtrace)>::value + 3; std::array 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(0xefefefefefefefef); + task_backtrace.back() = reinterpret_cast(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* 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 34115d8..9ff5c7b 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 8a1c8bd..2f07bbd 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 +#include + #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; + + 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& 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& 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 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 508a0a3..eb03b3f 100644 --- a/base/debug/thread_heap_usage_tracker.h +++ b/base/debug/thread_heap_usage_tracker.h @@ -7,7 +7,7 @@ #include -#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 8b1d8fc..cdea53c 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 -#elif defined(OS_WIN) +#if defined(OS_WIN) #include +#elif defined(OS_POSIX) || defined(OS_FUCHSIA) +#include #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::Create() { - return MakeUnique(); + return std::make_unique(); } 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 AlterEnvironment(const char* const* const env, const EnvironmentMap& changes) { @@ -235,6 +233,6 @@ std::unique_ptr 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 3a4ed04..e842ab0 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 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 diff --git a/base/environment_unittest.cc b/base/environment_unittest.cc index ef264cf..7cb8c9c 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 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 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 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 0000000..aac8b7c --- /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; +// +// In a source file, write: +// +// template class EXPORT_TEMPLATE_DEFINE(FOO_EXPORT) foo; + +// 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 61043ce..1610eec 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 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::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::ClearInstanceForTesting() { // static void FeatureList::RestoreInstanceForTesting( std::unique_ptr 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 c9f4a7b..2237507 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 5fbd294..164997a 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,OffByDefaultGetCommandLineFeatureOverrides(&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 4ce0c34..259bcfe 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 6a32d9f..15fc744 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 5d7fd8b..1954cb2 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 +#include #include #include #include @@ -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 1b2224e..e8934b1 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 +#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 0155c7c..30f4053 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 -#include "base/win/scoped_handle.h" -#endif - -#if defined(OS_POSIX) +#if defined(OS_POSIX) || defined(OS_FUCHSIA) #include #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 9746e35..b26bf6c 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, MessageLoopForIO::Mode mode, int fd); + Watcher(WeakPtr 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, - 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(weak_factory_.GetWeakPtr(), mode, fd); + watcher_ = std::make_unique(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::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::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 6cc011b..aa44579 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 WatchReadable(int fd, const Closure& callback); static std::unique_ptr WatchWritable(int fd, diff --git a/base/files/file_descriptor_watcher_posix_unittest.cc b/base/files/file_descriptor_watcher_posix_unittest.cc index 7ff40c5..4ed044b 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( + file_descriptor_watcher_ = std::make_unique( static_cast(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 WatchWritable() { std::unique_ptr 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 9749980..9dfb2ba 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 7cac8dd..0fa99a6 100644 --- a/base/files/file_enumerator.h +++ b/base/files/file_enumerator.h @@ -8,10 +8,10 @@ #include #include -#include #include #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 -#elif defined(OS_POSIX) +#elif defined(OS_POSIX) || defined(OS_FUCHSIA) #include #include #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* 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 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 pending_paths_; + base::stack pending_paths_; DISALLOW_COPY_AND_ASSIGN(FileEnumerator); }; diff --git a/base/files/file_enumerator_posix.cc b/base/files/file_enumerator_posix.cc index fb4010a..4b429c6 100644 --- a/base/files/file_enumerator_posix.cc +++ b/base/files/file_enumerator_posix.cc @@ -8,12 +8,29 @@ #include #include #include +#include #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 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::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* 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 0000000..11df075 --- /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 +#include + +#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 kFolderSearchPolicies{ + FileEnumerator::FolderSearchPolicy::MATCH_ONLY, + FileEnumerator::FolderSearchPolicy::ALL}; + +circular_deque RunEnumerator( + const FilePath& root_path, + bool recursive, + int file_type, + const FilePath::StringType& pattern, + FileEnumerator::FolderSearchPolicy folder_search_policy) { + circular_deque 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 5b1eb29..14f9251 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* 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 0be0ad0..2dc15f9 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 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 { - 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()(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 9f33952..e722c68 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 245bd8e..af40346 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 1dc833d..c58d686 100644 --- a/base/files/file_path_watcher_linux.cc +++ b/base/files/file_path_watcher_linux.cc @@ -16,11 +16,11 @@ #include #include #include +#include #include #include #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 watchers_; + std::unordered_map 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 recursive_paths_by_watch_; + std::unordered_map recursive_paths_by_watch_; std::map 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 weak_ptr_; + WeakPtrFactory 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::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 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(&buffer[i]); size_t event_size = sizeof(inotify_event) + event->len; DCHECK(i + event_size <= static_cast(bytes_read)); - reader->OnInotifyEvent(event); + g_inotify_reader.Get().OnInotifyEvent(event); i += event_size; } } } -static LazyInstance::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::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(); + impl_ = std::make_unique(); } } // namespace base diff --git a/base/files/file_path_watcher_unittest.cc b/base/files/file_path_watcher_unittest.cc index d2ec37b..2530b27 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() = 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 task_runner_; + + // Closure to run when all delegates have signaled. + base::OnceClosure signal_closure_; }; class TestDelegateBase : public SupportsWeakPtr { 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()), + 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 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(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 2738d6c..f9192c3 100644 --- a/base/files/file_posix.cc +++ b/base/files/file_posix.cc @@ -11,7 +11,7 @@ #include #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 66c312b..8a57322 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 @@ -494,6 +540,33 @@ TEST(FileTest, DuplicateDeleteOnClose) { ASSERT_FALSE(base::PathExists(file_path)); } +#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; @@ -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 80fa44f..109cb22 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 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(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 5ada35f..456962a 100644 --- a/base/files/file_util.h +++ b/base/files/file_util.h @@ -16,6 +16,11 @@ #include #include +#if defined(OS_POSIX) || defined(OS_FUCHSIA) +#include +#include +#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 -#elif defined(OS_POSIX) -#include -#include -#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& 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 91f1203..066a1d1 100644 --- a/base/files/file_util_posix.cc +++ b/base/files/file_util_posix.cc @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -22,6 +21,9 @@ #include #include +#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 #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(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 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(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, ¤t, &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 directories; + stack 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(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 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 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 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(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 b468462..235bb8d 100644 --- a/base/files/important_file_writer.cc +++ b/base/files/important_file_writer.cc @@ -11,6 +11,7 @@ #include #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 +void UmaHistogramExactLinearWithSuffix(const char* histogram_name, + StringPiece histogram_suffix, + SampleType add_sample, + SampleType max_sample) { + static_assert(std::is_convertible::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(add_sample), + static_cast(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 data, Closure before_write_callback, - Callback after_write_callback) { + Callback 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(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 task_runner) - : ImportantFileWriter( - path, - std::move(task_runner), - TimeDelta::FromMilliseconds(kDefaultCommitIntervalMs)) {} + scoped_refptr task_runner, + const char* histogram_suffix) + : ImportantFileWriter(path, + std::move(task_runner), + kDefaultCommitInterval, + histogram_suffix) {} ImportantFileWriter::ImportantFileWriter( const FilePath& path, scoped_refptr 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 data) { - DCHECK(CalledOnValidThread()); + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!IsValueInRangeForNumericType(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 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 f154b04..08a7ee3 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 task_runner); + scoped_refptr task_runner, + const char* histogram_suffix = nullptr); // Same as above, but with a custom commit interval. ImportantFileWriter(const FilePath& path, scoped_refptr 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(*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 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 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 9b8dcfd..5dddc71 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("foo")); + writer.WriteNow(std::make_unique("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("foo")); + writer.WriteNow(std::make_unique("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("bar")); + writer.WriteNow(std::make_unique("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("baz")); + writer.WriteNow(std::make_unique("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("foo")); + writer.WriteNow(std::make_unique("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("foo")); + writer.WriteNow(std::make_unique("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("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 67890d6..ccd9e23 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::max() - region.offset) { - DLOG(ERROR) << "Region bounds exceed maximum for base::File."; - return false; + DCHECK(Region::kWholeFile != region); + { + CheckedNumeric 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(SysInfo::VMAllocationGranularity()) - 1; - DCHECK_LT(mask, std::numeric_limits::max()); + auto mask = SysInfo::VMAllocationGranularity() - 1; + DCHECK(IsValueInRangeForNumericType(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 cad99f6..04f4336 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 90ba6f4..45a0aea 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 #include #include #include @@ -11,19 +12,23 @@ #include #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 +#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(file_len)) + return false; map_size = static_cast(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::max() || - static_cast(aligned_size) > - std::numeric_limits::max() || - static_cast(region.size) > - std::numeric_limits::max()) { + if (aligned_start < 0 || + !IsValueInRangeForNumericType(aligned_start)) { DLOG(ERROR) << "Region bounds are not valid for mmap"; return false; } map_start = static_cast(aligned_start); - map_size = static_cast(aligned_size); - length_ = static_cast(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, ¶ms) != 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(mmap(NULL, map_size, flags, MAP_SHARED, + + data_ = static_cast(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 0000000..3929a0d --- /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 78d4ca5..1b9227d 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 #include -#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 68c0415..e32a603 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 2681521..01ec0f0 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 a5aaf84..872f6f8 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 0697c6d..1279ff7 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 -#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 855380a..5c16728 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 3289e63..2ca267e 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 5714073..2a23658 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((bytes[0] >> 16) & 0x0000ffff), static_cast(bytes[0] & 0x0000ffff), static_cast(bytes[1] >> 48), - bytes[1] & 0x0000ffffffffffffULL); + bytes[1] & 0x0000ffff'ffffffffULL); } } // namespace base diff --git a/base/guid.h b/base/guid.h index 29c24ea..c6937a1 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 4dfd0d0..d9fe07e 100644 --- a/base/hash.cc +++ b/base/hash.cc @@ -6,11 +6,101 @@ #include -namespace base { - uint32_t SuperFastHash(const char* data, size_t len) { std::hash 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(std::numeric_limits::max())) { + NOTREACHED(); + return 0; + } + return ::SuperFastHash(reinterpret_cast(data), + static_cast(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(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(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(value1 & 0xffffffff); + uint32_t value1b = static_cast((value1 >> 32) & 0xffffffff); + uint32_t value2a = static_cast(value2 & 0xffffffff); + uint32_t value2b = static_cast((value2 >> 32) & 0xffffffff); + + uint64_t product1 = static_cast(value1a) * short_random1; + uint64_t product2 = static_cast(value1b) * short_random2; + uint64_t product3 = static_cast(value2a) * short_random3; + uint64_t product4 = static_cast(value2b) * short_random4; + + uint64_t hash64 = product1 + product2 + product3 + product4; + + if (sizeof(size_t) >= sizeof(uint64_t)) + return static_cast(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(hash64 >> (8 * (sizeof(uint64_t) - sizeof(size_t)))); + return high_bits; +} + } // namespace base diff --git a/base/hash.h b/base/hash.h index 7c0fba6..165899e 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(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(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(value1 & 0xffffffff); - uint32_t value1b = static_cast((value1 >> 32) & 0xffffffff); - uint32_t value2a = static_cast(value2 & 0xffffffff); - uint32_t value2b = static_cast((value2 >> 32) & 0xffffffff); - - uint64_t product1 = static_cast(value1a) * short_random1; - uint64_t product2 = static_cast(value1b) * short_random2; - uint64_t product3 = static_cast(value2a) * short_random3; - uint64_t product4 = static_cast(value2b) * short_random4; - - uint64_t hash64 = product1 + product2 + product3 + product4; - - if (sizeof(size_t) >= sizeof(uint64_t)) - return static_cast(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(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 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; +// std::unordered_set> set; template struct IntPairHash; diff --git a/base/i18n/rtl.h b/base/i18n/rtl.h index df15cd0..e54f8ea 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 0000000..1f32d8c --- /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 +#include + +#include + +#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 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 661d25d..a7c68c5 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 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 c6f6409..dfe246c 100644 --- a/base/json/json_parser.cc +++ b/base/json/json_parser.cc @@ -6,10 +6,11 @@ #include #include +#include #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 JSONParser::Parse(StringPiece input) { - start_pos_ = input.data(); - pos_ = start_pos_; - end_pos_ = start_pos_ + input.length(); +Optional JSONParser::Parse(StringPiece input) { + input_ = input; index_ = 0; line_number_ = 1; index_last_line_ = 0; @@ -85,27 +80,27 @@ std::unique_ptr 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(*pos_) == 0xEF && - static_cast(*(pos_ + 1)) == 0xBB && - static_cast(*(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(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 root(ParseNextToken()); + Optional 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(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(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 JSONParser::PeekChars(int count) { + if (static_cast(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 JSONParser::PeekChar() { + Optional chars = PeekChars(1); + if (chars) + return (*chars)[0]; + return nullopt; } -void JSONParser::NextNChars(int n) { - DCHECK(CanConsume(n)); - index_ += n; - pos_ += n; +Optional JSONParser::ConsumeChars(int count) { + Optional chars = PeekChars(count); + if (chars) + index_ += count; + return chars; +} + +Optional JSONParser::ConsumeChar() { + Optional chars = ConsumeChars(1); + if (chars) + return (*chars)[0]; + return nullopt; +} + +const char* JSONParser::pos() { + CHECK_LE(static_cast(index_), input_.length()); + return input_.data() + index_; } JSONParser::Token JSONParser::GetNextToken() { EatWhitespaceAndComments(); - if (!CanConsume(1)) + + Optional 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 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 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 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 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 JSONParser::ParseNextToken() { +Optional JSONParser::ParseNextToken() { return ParseToken(GetNextToken()); } -std::unique_ptr JSONParser::ParseToken(Token token) { +Optional JSONParser::ParseToken(Token token) { switch (token) { case T_OBJECT_BEGIN: return ConsumeDictionary(); @@ -328,127 +321,127 @@ std::unique_ptr JSONParser::ParseToken(Token token) { return ConsumeLiteral(); default: ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1); - return nullptr; + return nullopt; } } -std::unique_ptr JSONParser::ConsumeDictionary() { - if (*pos_ != '{') { +Optional 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 dict(new DictionaryValue); + std::vector 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 = ParseNextToken(); + ConsumeChar(); + Optional 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(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 JSONParser::ConsumeList() { - if (*pos_ != '[') { +Optional 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 list(new ListValue); + Value::ListStorage list_storage; - NextChar(); Token token = GetNextToken(); while (token != T_ARRAY_END) { - std::unique_ptr item = ParseToken(token); + Optional 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 JSONParser::ConsumeString() { +Optional JSONParser::ConsumeString() { StringBuilder string; if (!ConsumeStringRaw(&string)) - return nullptr; + return nullopt; - return base::MakeUnique(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(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(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 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(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 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(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 JSONParser::ConsumeNumber() { - const char* num_start = pos_; +Optional 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 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 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 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(num_int); + return Value(num_int); double num_double; if (StringToDouble(num_string.as_string(), &num_double) && std::isfinite(num_double)) { - return base::MakeUnique(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 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 JSONParser::ConsumeLiteral() { - switch (*pos_) { - case 't': { - const char kTrueLiteral[] = "true"; - const int kTrueLen = static_cast(strlen(kTrueLiteral)); - if (!CanConsume(kTrueLen - 1) || - !StringsAreEqual(pos_, kTrueLiteral, kTrueLen)) { - ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); - return nullptr; - } - NextNChars(kTrueLen - 1); - return base::MakeUnique(true); - } - case 'f': { - const char kFalseLiteral[] = "false"; - const int kFalseLen = static_cast(strlen(kFalseLiteral)); - if (!CanConsume(kFalseLen - 1) || - !StringsAreEqual(pos_, kFalseLiteral, kFalseLen)) { - ReportError(JSONReader::JSON_SYNTAX_ERROR, 1); - return nullptr; - } - NextNChars(kFalseLen - 1); - return base::MakeUnique(false); - } - case 'n': { - const char kNullLiteral[] = "null"; - const int kNullLen = static_cast(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 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 4f26458..a4dd2ba 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 Parse(StringPiece input); + Optional 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 string_; + // The copied string representation. Will be unset until Convert() is + // called. + base::Optional 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 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 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 ConsumeChars(int count); + + // Calls ConsumeChars() with a |count| of 1. + Optional 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 ParseNextToken(); + Optional 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 ParseToken(Token token); + Optional ParseToken(Token token); // Assuming that the parser is currently wound to '{', this parses a JSON - // object into a DictionaryValue. - std::unique_ptr ConsumeDictionary(); + // object into a Value. + Optional ConsumeDictionary(); // Assuming that the parser is wound to '[', this parses a JSON list into a - // std::unique_ptr. - std::unique_ptr ConsumeList(); + // Value. + Optional ConsumeList(); // Calls through ConsumeStringRaw and wraps it in a value. - std::unique_ptr ConsumeString(); + Optional 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 ConsumeNumber(); + Optional 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 ConsumeLiteral(); + Optional 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 e3f635b..0da3db8 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* 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(parser->index_), parser->input_.length()); } }; @@ -40,22 +53,25 @@ TEST_F(JSONParserTest, NextChar) { std::string input("Hello world"); std::unique_ptr 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(parser->index_), parser->input_.length()); } TEST_F(JSONParserTest, ConsumeString) { std::string input("\"test\",|"); std::unique_ptr parser(NewTestParser(input)); - std::unique_ptr value(parser->ConsumeString()); - EXPECT_EQ('"', *parser->pos_); + Optional 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 parser(NewTestParser(input)); - std::unique_ptr value(parser->ConsumeList()); - EXPECT_EQ(']', *parser->pos_); + Optional 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 parser(NewTestParser(input)); - std::unique_ptr value(parser->ConsumeDictionary()); - EXPECT_EQ('}', *parser->pos_); + Optional 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 parser(NewTestParser(input)); - std::unique_ptr value(parser->ConsumeLiteral()); - EXPECT_EQ('e', *parser->pos_); + Optional 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 parser(NewTestParser(input)); - std::unique_ptr value(parser->ConsumeNumber()); - EXPECT_EQ('4', *parser->pos_); + Optional 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 parser( NewTestParser(quoted_bogus_char, JSON_REPLACE_INVALID_CHARACTERS)); - std::unique_ptr value(parser->ConsumeString()); - ASSERT_TRUE(value.get()); + Optional 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 parser( + NewTestParser(invalid, JSON_REPLACE_INVALID_CHARACTERS)); + Optional 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(str_len); - memcpy(non_nul_termianted.get(), test_case.input, str_len); + std::unique_ptr input_owner; + StringPiece input = + MakeNotNullTerminatedInput(test_case.input, &input_owner); - StringPiece string_piece(non_nul_termianted.get(), str_len); - std::unique_ptr result = JSONReader::Read(string_piece); + std::unique_ptr 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 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 4ff7496..bf2a18a 100644 --- a/base/json/json_reader.cc +++ b/base/json/json_reader.cc @@ -4,12 +4,20 @@ #include "base/json/json_reader.h" +#include +#include + #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 JSONReader::Read(StringPiece json) { - internal::JSONParser parser(JSON_PARSE_RFC); - return parser.Parse(json); -} - -// static -std::unique_ptr JSONReader::Read(StringPiece json, int options) { - internal::JSONParser parser(options); - return parser.Parse(json); +std::unique_ptr JSONReader::Read(StringPiece json, + int options, + int max_depth) { + internal::JSONParser parser(options, max_depth); + Optional root = parser.Parse(json); + return root ? std::make_unique(std::move(*root)) : nullptr; } // static std::unique_ptr 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 root(parser.Parse(json)); + Optional root = parser.Parse(json); if (!root) { if (error_code_out) *error_code_out = parser.error_code(); @@ -76,7 +77,7 @@ std::unique_ptr JSONReader::ReadAndReturnError( *error_column_out = parser.error_column(); } - return root; + return root ? std::make_unique(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 JSONReader::ReadToValue(StringPiece json) { - return parser_->Parse(json); + Optional value = parser_->Parse(json); + return value ? std::make_unique(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 a39b37a..2c6bd3e 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 Read(StringPiece json); - - // Same as Read() above, but the parser respects the given |options|. - static std::unique_ptr Read(StringPiece json, int options); + static std::unique_ptr 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 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 0000000..5e69940 --- /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 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 f645b42..2b8417c 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 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 root = JSONReader().ReadToValue("true "); - ASSERT_TRUE(root); - EXPECT_TRUE(root->IsType(Value::Type::BOOLEAN)); - } - - { - // Embedded comment - std::unique_ptr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 list = + ListValue::From(JSONReader::Read("[true, false, null]")); + ASSERT_TRUE(list); + EXPECT_EQ(3U, list->GetSize()); - { - // Basic array - std::unique_ptr 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 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 root2 = + JSONReader::Read("[true, false, null, ]", JSON_ALLOW_TRAILING_COMMAS); + EXPECT_TRUE(list->Equals(root2.get())); +} - { - // Empty array - std::unique_ptr list = ListValue::From(JSONReader::Read("[]")); - ASSERT_TRUE(list); - EXPECT_EQ(0U, list->GetSize()); - } +TEST(JSONReaderTest, EmptyArray) { + std::unique_ptr list = ListValue::From(JSONReader::Read("[]")); + ASSERT_TRUE(list); + EXPECT_EQ(0U, list->GetSize()); +} - { - // Nested arrays - std::unique_ptr list = ListValue::From( - JSONReader::Read("[[true], [], [false, [], [null]], null]")); - ASSERT_TRUE(list); - EXPECT_EQ(4U, list->GetSize()); - - // Lots of trailing commas. - std::unique_ptr root2 = - JSONReader::Read("[[true], [], [false, [], [null, ] , ], null,]", - JSON_ALLOW_TRAILING_COMMAS); - EXPECT_TRUE(list->Equals(root2.get())); - } +TEST(JSONReaderTest, NestedArrays) { + std::unique_ptr list = ListValue::From( + JSONReader::Read("[[true], [], [false, [], [null]], null]")); + ASSERT_TRUE(list); + EXPECT_EQ(4U, list->GetSize()); + + // Lots of trailing commas. + std::unique_ptr 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 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 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 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 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 dict_val = + DictionaryValue::From(JSONReader::Read("{}")); + ASSERT_TRUE(dict_val); +} - { - // Test nesting - std::unique_ptr 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 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 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 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 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 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 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 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 list = ListValue::From(JSONReader::Read(not_evil)); + ASSERT_TRUE(list); + EXPECT_EQ(5001U, list->GetSize()); +} - { - // Test utf8 encoded input - std::unique_ptr 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 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 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 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 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 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 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 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 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 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 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 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 2e46ab3..f9c45a4 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 JSONStringValueDeserializer::Deserialize( int* error_code, diff --git a/base/json/json_value_converter.h b/base/json/json_value_converter.h index 68ebfa2..ef08115 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 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 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 : public ValueConverter { public: - BasicValueConverter() {} + BasicValueConverter() = default; bool Convert(const base::Value& value, int* field) const override; @@ -152,7 +152,7 @@ template <> class BASE_EXPORT BasicValueConverter : public ValueConverter { public: - BasicValueConverter() {} + BasicValueConverter() = default; bool Convert(const base::Value& value, std::string* field) const override; @@ -164,7 +164,7 @@ template <> class BASE_EXPORT BasicValueConverter : public ValueConverter { public: - BasicValueConverter() {} + BasicValueConverter() = default; bool Convert(const base::Value& value, string16* field) const override; @@ -175,7 +175,7 @@ class BASE_EXPORT BasicValueConverter template <> class BASE_EXPORT BasicValueConverter : public ValueConverter { public: - BasicValueConverter() {} + BasicValueConverter() = default; bool Convert(const base::Value& value, double* field) const override; @@ -186,7 +186,7 @@ class BASE_EXPORT BasicValueConverter : public ValueConverter { template <> class BASE_EXPORT BasicValueConverter : public ValueConverter { public: - BasicValueConverter() {} + BasicValueConverter() = default; bool Convert(const base::Value& value, bool* field) const override; @@ -215,7 +215,7 @@ class ValueFieldConverter : public ValueConverter { template class CustomFieldConverter : public ValueConverter { 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 { template class NestedValueConverter : public ValueConverter { public: - NestedValueConverter() {} + NestedValueConverter() = default; bool Convert(const base::Value& value, NestedType* field) const override { return converter_.Convert(value, field); @@ -250,7 +250,7 @@ template class RepeatedValueConverter : public ValueConverter>> { public: - RepeatedValueConverter() {} + RepeatedValueConverter() = default; bool Convert(const base::Value& value, std::vector>* field) const override { @@ -286,7 +286,7 @@ template class RepeatedMessageConverter : public ValueConverter>> { public: - RepeatedMessageConverter() {} + RepeatedMessageConverter() = default; bool Convert(const base::Value& value, std::vector>* field) const override { @@ -365,51 +365,53 @@ class JSONValueConverter { void RegisterIntField(const std::string& field_name, int StructType::* field) { - fields_.push_back(MakeUnique>( - field_name, field, new internal::BasicValueConverter)); + fields_.push_back( + std::make_unique>( + field_name, field, new internal::BasicValueConverter)); } void RegisterStringField(const std::string& field_name, std::string StructType::* field) { fields_.push_back( - MakeUnique>( + std::make_unique>( field_name, field, new internal::BasicValueConverter)); } void RegisterStringField(const std::string& field_name, string16 StructType::* field) { fields_.push_back( - MakeUnique>( + std::make_unique>( field_name, field, new internal::BasicValueConverter)); } void RegisterBoolField(const std::string& field_name, bool StructType::* field) { - fields_.push_back(MakeUnique>( - field_name, field, new internal::BasicValueConverter)); + fields_.push_back( + std::make_unique>( + field_name, field, new internal::BasicValueConverter)); } void RegisterDoubleField(const std::string& field_name, double StructType::* field) { - fields_.push_back(MakeUnique>( - field_name, field, new internal::BasicValueConverter)); + fields_.push_back( + std::make_unique>( + field_name, field, new internal::BasicValueConverter)); } template void RegisterNestedField( const std::string& field_name, NestedType StructType::* field) { fields_.push_back( - MakeUnique>( + std::make_unique>( field_name, field, new internal::NestedValueConverter)); } template - 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>( + std::make_unique>( field_name, field, new internal::CustomFieldConverter(convert_func))); } @@ -420,7 +422,7 @@ class JSONValueConverter { FieldType StructType::* field, bool (*convert_func)(const base::Value*, FieldType*)) { fields_.push_back( - MakeUnique>( + std::make_unique>( field_name, field, new internal::ValueFieldConverter(convert_func))); } @@ -428,17 +430,16 @@ class JSONValueConverter { void RegisterRepeatedInt( const std::string& field_name, std::vector> StructType::*field) { - fields_.push_back( - MakeUnique>>>( - field_name, field, new internal::RepeatedValueConverter)); + fields_.push_back(std::make_unique>>>( + field_name, field, new internal::RepeatedValueConverter)); } void RegisterRepeatedString( const std::string& field_name, std::vector> StructType::*field) { fields_.push_back( - MakeUnique>>>( field_name, field, new internal::RepeatedValueConverter)); @@ -447,7 +448,7 @@ class JSONValueConverter { void RegisterRepeatedString( const std::string& field_name, std::vector> StructType::*field) { - fields_.push_back(MakeUnique>>>( field_name, field, new internal::RepeatedValueConverter)); } @@ -455,7 +456,7 @@ class JSONValueConverter { void RegisterRepeatedDouble( const std::string& field_name, std::vector> StructType::*field) { - fields_.push_back(MakeUnique>>>( field_name, field, new internal::RepeatedValueConverter)); } @@ -463,7 +464,7 @@ class JSONValueConverter { void RegisterRepeatedBool( const std::string& field_name, std::vector> StructType::*field) { - fields_.push_back(MakeUnique>>>( field_name, field, new internal::RepeatedValueConverter)); } @@ -474,7 +475,7 @@ class JSONValueConverter { std::vector> StructType::*field, bool (*convert_func)(const base::Value*, NestedType*)) { fields_.push_back( - MakeUnique>>>( field_name, field, new internal::RepeatedCustomValueConverter( @@ -486,7 +487,7 @@ class JSONValueConverter { const std::string& field_name, std::vector> StructType::*field) { fields_.push_back( - MakeUnique>>>( field_name, field, new internal::RepeatedMessageConverter)); diff --git a/base/json/json_value_converter_unittest.cc b/base/json/json_value_converter_unittest.cc index 6a603d3..322f5f0 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> 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 e5cb126..69a5661 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 07b9d50..e4f1e3c 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(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 6cb236f..2d81af3 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 inner_dict(new DictionaryValue()); inner_dict->SetInteger("inner int", 10); list->Append(std::move(inner_dict)); - list->Append(MakeUnique()); + list->Append(std::make_unique()); 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 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 root(BinaryValue::CreateWithCopiedBuffer("asdf", 4)); + std::unique_ptr 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(5)); - binary_list.Append(BinaryValue::CreateWithCopiedBuffer("asdf", 4)); - binary_list.Append(MakeUnique(2)); - binary_list.Append(BinaryValue::CreateWithCopiedBuffer("asdf", 4)); + binary_list.Append(Value::CreateWithCopiedBuffer("asdf", 4)); + binary_list.Append(std::make_unique(5)); + binary_list.Append(Value::CreateWithCopiedBuffer("asdf", 4)); + binary_list.Append(std::make_unique(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 f67fa93..471a9d3 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(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 b66b7e5..f75f475 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 0000000..f430411 --- /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 + +// 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 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(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 ae3d82a..1e962c6 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.h b/base/lazy_instance.h index b0d72bc..4449373 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. Use a function-local +// static of type base::NoDestructor instead: +// +// Factory& Factory::GetInstance() { +// static base::NoDestructor instance; +// return *instance; +// } +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// // The LazyInstance 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 // 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 struct LazyInstanceTraitsBase { static Type* New(void* instance) { - DCHECK_EQ(reinterpret_cast(instance) & (ALIGNOF(Type) - 1), 0u); + DCHECK_EQ(reinterpret_cast(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 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( - 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(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 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.cc b/base/lazy_instance_helpers.cc similarity index 50% rename from base/lazy_instance.cc rename to base/lazy_instance_helpers.cc index 5468065..7b9e0de 100644 --- a/base/lazy_instance.cc +++ b/base/lazy_instance_helpers.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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.h" +#include "base/lazy_instance_helpers.h" #include "base/at_exit.h" #include "base/atomicops.h" @@ -11,26 +11,36 @@ 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) + 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(); + 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; @@ -38,16 +48,16 @@ bool NeedsLazyInstance(subtle::AtomicWord* state) { 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(). + 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 (dtor) - AtExitManager::RegisterCallback(dtor, lazy_instance); + if (new_instance && destructor) + AtExitManager::RegisterCallback(destructor, destructor_arg); } } // namespace internal diff --git a/base/lazy_instance_helpers.h b/base/lazy_instance_helpers.h new file mode 100644 index 0000000..5a43d8b --- /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 +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((*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(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 0aa4659..a5f024c 100644 --- a/base/lazy_instance_unittest.cc +++ b/base/lazy_instance_unittest.cc @@ -4,17 +4,26 @@ #include +#include +#include + #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::DestructorAtExit* lazy_; + + DISALLOW_COPY_AND_ASSIGN(SlowDelegate); }; } // namespace -static base::LazyInstance::DestructorAtExit - lazy_logger = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance::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::DestructorAtExit lazy_slow = +base::LazyInstance::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 class AlignedData { public: - AlignedData() {} - ~AlignedData() {} - base::AlignedMemory data_; + AlignedData() = default; + ~AlignedData() = default; + alignas(alignment) char data_[alignment]; }; -} // anonymous namespace +} // namespace #define EXPECT_ALIGNED(ptr, align) \ EXPECT_EQ(0u, reinterpret_cast(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::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::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::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> 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( + 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 1333e6e..8bbf6ed 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 #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 "<" 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("<"); - break; - - case '>': - output->append(">"); - 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 dd78515..14fe2fa 100644 --- a/base/location.h +++ b/base/location.h @@ -11,14 +11,23 @@ #include #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:". 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(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 01e311b..112afb8 100644 --- a/base/logging.cc +++ b/base/logging.cc @@ -7,32 +7,62 @@ #include #include -#include "base/debug/activity_tracker.h" #include "base/macros.h" #include "build/build_config.h" #if defined(OS_WIN) #include +#include typedef HANDLE FileHandle; typedef HANDLE MutexHandle; // Windows warns on using write(). It prefers _write(). #define write(fd, buf, count) _write(fd, buf, static_cast(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 +#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 +#else +#include +#endif + #include #include #include #include -#elif defined(OS_POSIX) + +#elif defined(OS_POSIX) || defined(OS_FUCHSIA) #if defined(OS_NACL) #include // timespec doesn't seem to be in #endif #include #endif -#if defined(OS_POSIX) +#if defined(OS_FUCHSIA) +#include +#include +#endif + +#if defined(OS_ANDROID) || defined(__ANDROID__) +#include +#endif + +#if defined(OS_POSIX) || defined(OS_FUCHSIA) #include #include #include @@ -52,12 +82,17 @@ typedef pthread_mutex_t* MutexHandle; #include #include #include +#include #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 +#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>::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(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(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 7ca018e..08c1f0f 100644 --- a/base/logging.h +++ b/base/logging.h @@ -15,9 +15,11 @@ #include #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 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; + +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(::logging::AnalyzerNoReturn()) +#define ANALYZER_ALLOW_UNUSED(var) static_cast(var); + +#else // !defined(__clang_analyzer__) + +#define ANALYZER_ASSUME_TRUE(arg) (arg) +#define ANALYZER_SKIP_THIS_PATH() +#define ANALYZER_ALLOW_UNUSED(var) static_cast(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::value, void>::type MakeCheckOpValueString(std::ostream* os, const T& v) { - (*os) << static_cast::type>(v); + (*os) << static_cast::type>(v); } // We need an explicit overload for std::nullptr_t. @@ -685,17 +756,21 @@ std::string* MakeCheckOpString( // 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 \ 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 04f349c..9b79f98 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 #endif // OS_WIN +#if defined(OS_FUCHSIA) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(buffer.rip); +#elif defined(ARCH_CPU_ARM64) + *child_crash_addr = static_cast(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(info->si_addr); #else // OS_POSIX && !OS_MACOSX - struct ucontext* context = reinterpret_cast(context_ptr); + ucontext_t* context = reinterpret_cast(context_ptr); #if defined(ARCH_CPU_X86) crash_addr = static_cast(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(NULL)) + DCHECK_EQ(mock_log_source.Log(), static_cast(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 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 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 b5b03bb..8685117 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 char (&ArraySizeHelper(T (&array)[N]))[N]; #define arraysize(array) (sizeof(ArraySizeHelper(array))) @@ -77,30 +85,30 @@ template 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 instead: +// +// Factory& Factory::GetInstance() { +// static base::NoDestructor 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 526a495..93cbeb5 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 d829011..89f9505 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 my_class; -// -// // ... at runtime: -// new(my_class.void_data()) MyClass(); -// -// // ... use it: -// MyClass* mc = my_class.data_as(); -// -// // ... later, to destruct my_class: -// my_class.data_as()->MyClass::~MyClass(); -// -// Alternatively, a runtime sized aligned allocation can be created: -// -// float* my_array = static_cast(AlignedAlloc(size, alignment)); -// -// // ... later, to release the memory: -// AlignedFree(my_array); -// -// Or using unique_ptr: -// -// std::unique_ptr my_array( -// static_cast(AlignedAlloc(size, alignment))); - #ifndef BASE_MEMORY_ALIGNED_MEMORY_H_ #define BASE_MEMORY_ALIGNED_MEMORY_H_ #include #include +#include + #include "base/base_export.h" #include "base/compiler_specific.h" +#include "build/build_config.h" #if defined(COMPILER_MSVC) #include @@ -46,54 +20,21 @@ #include #endif -namespace base { - -// AlignedMemory is specialized for all supported alignments. -// Make sure we get a compiler error if someone uses an unsupported alignment. -template -struct AlignedMemory {}; - -#define BASE_DECL_ALIGNED_MEMORY(byte_alignment) \ - template \ - class AlignedMemory { \ - public: \ - ALIGNAS(byte_alignment) uint8_t data_[Size]; \ - void* void_data() { return static_cast(data_); } \ - const void* void_data() const { return static_cast(data_); } \ - template \ - Type* data_as() { \ - return static_cast(void_data()); \ - } \ - template \ - const Type* data_as() const { \ - return static_cast(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(AlignedAlloc(size, alignment)); +// +// // ... later, to release the memory: +// AlignedFree(my_array); +// +// Or using unique_ptr: +// +// std::unique_ptr my_array( +// static_cast(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 892c50e..e354f38 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(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 p( - static_cast(base::AlignedAlloc(8, 8))); + std::unique_ptr p( + static_cast(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 f6bc410..344ffa4 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 a0, a1, a2; - a0 = a0; + a0 = *&a0; // The *& defeats Clang's -Wself-assign warning. a1 = a2; - ASSERT_EQ(a0.get(), static_cast(NULL)); - ASSERT_EQ(a1.get(), static_cast(NULL)); - ASSERT_EQ(a2.get(), static_cast(NULL)); - ASSERT_TRUE(a0 == NULL); - ASSERT_TRUE(a1 == NULL); - ASSERT_TRUE(a2 == NULL); + ASSERT_EQ(a0.get(), static_cast(nullptr)); + ASSERT_EQ(a1.get(), static_cast(nullptr)); + ASSERT_EQ(a2.get(), static_cast(nullptr)); + ASSERT_TRUE(a0 == nullptr); + ASSERT_TRUE(a1 == nullptr); + ASSERT_TRUE(a2 == nullptr); { linked_ptr 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 a4(a0); @@ -60,7 +60,7 @@ TEST(LinkedPtrTest, Test) { linked_ptr 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 f401f62..0000000 --- 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 - -#include "base/compiler_specific.h" -#include "base/memory/aligned_memory.h" - -namespace base { - -template -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(); - } - inline const Type* get() const { - return space_.template data_as(); - } - - 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 - inline void Init(Ts&&... params) { - new(space_.void_data()) Type(std::forward(params)...); - } - - inline void InitFromMove(ManualConstructor&& o) { - Init(std::move(*o)); - } - - inline void Destroy() { - get()->~Type(); - } - - private: - AlignedMemory 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 0000000..c145336 --- /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 0000000..3d830b6 --- /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 + +#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 +#include "base/mac/scoped_mach_port.h" +#elif defined(OS_FUCHSIA) +#include +#elif defined(OS_WIN) +#include "base/win/scoped_handle.h" +#include "base/win/windows_types.h" +#elif defined(OS_POSIX) +#include +#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 0000000..4a8b440 --- /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 + +#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(std::numeric_limits::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(&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(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(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(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(*memory) & (kMapMinimumAlignment - 1)); + return true; +} + +// static +PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode, + size_t size) { + if (size == 0) + return {}; + + if (size > static_cast(std::numeric_limits::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 0000000..d4b6d5c --- /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 +#include +#include + +#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; + +#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(std::numeric_limits::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(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(*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(std::numeric_limits::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 0000000..5a83ee9 --- /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 +#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(®ion); + 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(std::numeric_limits::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(std::numeric_limits::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(®ion, 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::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(®ion, 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(®ion); + 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(&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 0000000..157a677 --- /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::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 0000000..3cb2ec3 --- /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; +// 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 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::Initializer I(&fnPtr, +// reinterpret_cast(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 +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* 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 +struct DsoSpecific { + static T value; +}; +template +T DsoSpecific::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::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::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 + static AutoWritableMemory Create(ProtectedMemory& 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 +ProtectedMemory::Initializer::Initializer(ProtectedMemory* 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 0000000..852ef4e --- /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 0000000..a90023b --- /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 + +#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 +class UnsanitizedCfiCall { + public: + explicit UnsanitizedCfiCall(FunctionType function) : function_(function) {} + UnsanitizedCfiCall(UnsanitizedCfiCall&&) = default; + + template + NO_SANITIZE("cfi-icall") + auto operator()(Args&&... args) { + return function_(std::forward(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 p; +// UnsanitizedCfiCall(p)(5); /* In place of (*p)(5); */ + +template +auto UnsanitizedCfiCall(const ProtectedMemory& PM) { +#if PROTECTED_MEMORY_ENABLED + DCHECK(&PM >= ProtectedMemoryStart && &PM < ProtectedMemoryEnd); +#endif // PROTECTED_MEMORY_ENABLED + return internal::UnsanitizedCfiCall(*PM); +} + +// struct S { void (*fp)(int); } s; +// ProtectedMemory p; +// UnsanitizedCfiCall(p, &S::fp)(5); /* In place of p->fp(5); */ + +template +auto UnsanitizedCfiCall(const ProtectedMemory& PM, Member member) { +#if PROTECTED_MEMORY_ENABLED + DCHECK(&PM >= ProtectedMemoryStart && &PM < ProtectedMemoryEnd); +#endif // PROTECTED_MEMORY_ENABLED + return internal::UnsanitizedCfiCall(*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 0000000..d003d79 --- /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 +#include +#include + +#if defined(OS_LINUX) +#include +#endif // defined(OS_LINUX) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include +#include +#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(start) & page_mask; + return mprotect(reinterpret_cast(page_start), + reinterpret_cast(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(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(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(&ptr), &size, + VM_REGION_BASIC_INFO_64, reinterpret_cast(®ion_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 0000000..b7daed3 --- /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 init; + +TEST_F(ProtectedMemoryTest, Initializer) { + static ProtectedMemory::Initializer I(&init, 4); + EXPECT_EQ(*init, 4); +} + +PROTECTED_MEMORY_SECTION ProtectedMemory 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 writable_data; + EXPECT_DCHECK_DEATH({ AssertMemoryIsReadOnly(&writable_data->foo); }); +} + +TEST_F(ProtectedMemoryTest, FailsIfDefinedOutsideOfProtectMemoryRegion) { + ProtectedMemory data; + EXPECT_DCHECK_DEATH({ AutoWritableMemory::Create(data); }); +} + +TEST_F(ProtectedMemoryTest, UnsanitizedCfiCallOutsideOfProtectedMemoryRegion) { + ProtectedMemory 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 icall_pm1; + +TEST_F(ProtectedMemoryTest, BadMemberCall) { + static ProtectedMemory::Initializer I( + &icall_pm1, BadIcall(reinterpret_cast(&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 icall_pm2; + +TEST_F(ProtectedMemoryTest, BadFnPtrCall) { + static ProtectedMemory::Initializer I( + &icall_pm2, reinterpret_cast(&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 8747ac9..42f4f49 100644 --- a/base/memory/ptr_util.h +++ b/base/memory/ptr_util.h @@ -18,57 +18,6 @@ std::unique_ptr WrapUnique(T* ptr) { return std::unique_ptr(ptr); } -namespace internal { - -template -struct MakeUniqueResult { - using Scalar = std::unique_ptr; -}; - -template -struct MakeUniqueResult { - using Array = std::unique_ptr; -}; - -template -struct MakeUniqueResult { - 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(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 -// auto ptr = MakeUnique("hello world!"); -// -// // arr is a std::unique_ptr -// auto arr = MakeUnique(5); - -// Overload for non-array types. Arguments are forwarded to T's constructor. -template -typename internal::MakeUniqueResult::Scalar MakeUnique(Args&&... args) { - return std::unique_ptr(new T(std::forward(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 internal::MakeUniqueResult::Array MakeUnique(size_t size) { - return std::unique_ptr(new typename std::remove_extent::type[size]()); -} - -// Overload to reject array types of known bound, e.g. T[n]. -template -typename internal::MakeUniqueResult::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 5dbc183..ab8b2ab 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 #include -#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 +struct IsRefCountedType : std::false_type {}; + +template +struct IsRefCountedType()->AddRef()), + decltype(std::declval()->Release())>> + : std::true_type {}; + template struct NeedsScopedRefptrButGetsRawPtr { + static_assert(!std::is_reference::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::value && - (std::is_convertible::value || - std::is_convertible::value)) + value = std::is_pointer::value && + IsRefCountedType>::value }; }; -template -struct ParamsUseScopedRefptrCorrectly { - enum { value = 0 }; -}; - -template <> -struct ParamsUseScopedRefptrCorrectly> { - enum { value = 1 }; -}; - -template -struct ParamsUseScopedRefptrCorrectly> { - enum { value = !NeedsScopedRefptrButGetsRawPtr::value && - ParamsUseScopedRefptrCorrectly>::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 0000000..6b654c9 --- /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 + +#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 0000000..4f92762 --- /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 + +#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 039f255..b9fa15f 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 be493f6..249f70e 100644 --- a/base/memory/ref_counted.h +++ b/base/memory/ref_counted.h @@ -7,33 +7,21 @@ #include -#include -#include -#include +#include #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 scoped_refptr; - namespace base { - -template -scoped_refptr 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 friend scoped_refptr 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 @@ -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 +template +class RefCounted; + +template +struct DefaultRefCountedTraits { + static void Destruct(const T* x) { + RefCounted::DeleteInternal(x); + } +}; + +template > 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(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(this)); } } @@ -258,6 +328,12 @@ class RefCounted : public subtle::RefCountedBase { ~RefCounted() = default; private: + friend struct DefaultRefCountedTraits; + template + 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(this)); } } @@ -316,7 +393,10 @@ class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase { private: friend struct DefaultRefCountedThreadSafeTraits; - static void DeleteInternal(const T* x) { delete x; } + template + 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 -scoped_refptr AdoptRef(T* obj) { - using Tag = typename std::decay::type; - static_assert(std::is_same::value, - "Use AdoptRef only for the reference count starts from one."); - - DCHECK(obj); - DCHECK(obj->HasOneRef()); - obj->Adopted(); - return scoped_refptr(obj, subtle::kAdoptRefTag); -} - -namespace subtle { - -template -scoped_refptr AdoptRefIfNeeded(T* obj, StartRefCountFromZeroTag) { - return scoped_refptr(obj); -} - -template -scoped_refptr 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 -scoped_refptr MakeShared(Args&&... args) { - T* obj = new T(std::forward(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 { -// ... -// private: -// friend class RefCounted; // Allow destruction by RefCounted<>. -// ~MyFoo(); // Destructor must be private/protected. -// }; -// -// void some_function() { -// scoped_refptr foo = new MyFoo(); -// foo->Method(param); -// // |foo| is released when this function returns -// } -// -// void some_other_function() { -// scoped_refptr foo = new MyFoo(); -// ... -// foo = nullptr; // explicitly releases |foo| -// ... -// if (foo) -// foo->Method(param); -// } -// -// The above examples show how scoped_refptr acts like a pointer to T. -// Given two scoped_refptr classes, it is also possible to exchange -// references between the two objects, like so: -// -// { -// scoped_refptr a = new MyFoo(); -// scoped_refptr 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 a = new MyFoo(); -// scoped_refptr b; -// -// b = a; -// // now, |a| and |b| each own a reference to the same MyFoo object. -// } -// -template -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& r) : ptr_(r.ptr_) { - if (ptr_) - AddRef(ptr_); - } - - // Copy conversion constructor. - template ::value>::type> - scoped_refptr(const scoped_refptr& 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 ::value>::type> - scoped_refptr(scoped_refptr&& 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& 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& operator=(const scoped_refptr& r) { - return *this = r.ptr_; - } - - template - scoped_refptr& operator=(const scoped_refptr& r) { - return *this = r.get(); - } - - scoped_refptr& operator=(scoped_refptr&& r) { - scoped_refptr(std::move(r)).swap(*this); - return *this; - } - - template - scoped_refptr& operator=(scoped_refptr&& r) { - scoped_refptr(std::move(r)).swap(*this); - return *this; - } - - void swap(scoped_refptr& r) { - T* tmp = ptr_; - ptr_ = r.ptr_; - r.ptr_ = tmp; - } - - explicit operator bool() const { return ptr_ != nullptr; } - - template - bool operator==(const scoped_refptr& rhs) const { - return ptr_ == rhs.get(); - } - - template - bool operator!=(const scoped_refptr& rhs) const { - return !operator==(rhs); - } - - template - bool operator<(const scoped_refptr& rhs) const { - return ptr_ < rhs.get(); - } - - protected: - T* ptr_ = nullptr; - - private: - template - friend scoped_refptr base::AdoptRef(U*); - - scoped_refptr(T* p, base::subtle::AdoptRefTag) : ptr_(p) {} - - // Friend required for move constructors that set r.ptr_ to null. - template - friend class scoped_refptr; - - // Non-inline helpers to allow: - // class Opaque; - // extern template class scoped_refptr; - // Otherwise the compiler will complain that Opaque is an incomplete type. - static void AddRef(T* ptr); - static void Release(T* ptr); -}; - -// static -template -void scoped_refptr::AddRef(T* ptr) { - ptr->AddRef(); -} - -// static -template -void scoped_refptr::Release(T* ptr) { - ptr->Release(); -} - -// Handy utility for creating a scoped_refptr out of a T* explicitly without -// having to retype all the template arguments -template -scoped_refptr make_scoped_refptr(T* t) { - return scoped_refptr(t); -} - -template -bool operator==(const scoped_refptr& lhs, const U* rhs) { - return lhs.get() == rhs; -} - -template -bool operator==(const T* lhs, const scoped_refptr& rhs) { - return lhs == rhs.get(); -} - -template -bool operator==(const scoped_refptr& lhs, std::nullptr_t null) { - return !static_cast(lhs); -} - -template -bool operator==(std::nullptr_t null, const scoped_refptr& rhs) { - return !static_cast(rhs); -} - -template -bool operator!=(const scoped_refptr& lhs, const U* rhs) { - return !operator==(lhs, rhs); -} - -template -bool operator!=(const T* lhs, const scoped_refptr& rhs) { - return !operator==(lhs, rhs); -} - -template -bool operator!=(const scoped_refptr& lhs, std::nullptr_t null) { - return !operator==(lhs, null); -} - -template -bool operator!=(std::nullptr_t null, const scoped_refptr& rhs) { - return !operator==(null, rhs); -} - -template -std::ostream& operator<<(std::ostream& out, const scoped_refptr& 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 0000000..dd30106 --- /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 + +#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(scoped_refptr task_runner) +// : RefCountedDeleteOnSequence(std::move(task_runner)) {} +// ... +// private: +// friend class RefCountedDeleteOnSequence; +// friend class DeleteHelper; +// +// ~Foo(); +// }; +template +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 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() = 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(this); + if (owning_task_runner_->RunsTasksInCurrentSequence()) + delete t; + else + owning_task_runner_->DeleteSoon(FROM_HERE, t); + } + + const scoped_refptr 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 26b78f3..23a5ffc 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 + #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& initializer) : data_(initializer) { @@ -38,9 +41,11 @@ RefCountedBytes::RefCountedBytes(const std::vector& 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::TakeVector( std::vector* to_destroy) { - scoped_refptr bytes(new RefCountedBytes); + auto bytes = MakeRefCounted(); bytes->data_.swap(*to_destroy); return bytes; } @@ -48,34 +53,80 @@ scoped_refptr 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::TakeString( std::string* to_destroy) { - scoped_refptr self(new RefCountedString); + auto self = MakeRefCounted(); to_destroy->swap(self->data_); return self; } const unsigned char* RefCountedString::front() const { - return data_.empty() ? NULL : - reinterpret_cast(data_.data()); + return data_.empty() ? nullptr + : reinterpret_cast(data_.data()); } size_t RefCountedString::size() const { return data_.size(); } +RefCountedSharedMemory::RefCountedSharedMemory( + std::unique_ptr 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(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(mapping_.memory()); +} + +size_t RefCountedSharedMemoryMapping::size() const { + return size_; +} + +// static +scoped_refptr +RefCountedSharedMemoryMapping::CreateFromWholeRegion( + const ReadOnlySharedMemoryRegion& region) { + ReadOnlySharedMemoryMapping mapping = region.Map(); + if (!mapping.IsValid()) + return nullptr; + return MakeRefCounted(std::move(mapping)); +} + } // namespace base diff --git a/base/memory/ref_counted_memory.h b/base/memory/ref_counted_memory.h index aa22c9e..92a7d7b 100644 --- a/base/memory/ref_counted_memory.h +++ b/base/memory/ref_counted_memory.h @@ -7,21 +7,25 @@ #include +#include #include #include #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 { + : public RefCountedThreadSafe { 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; + friend class RefCountedThreadSafe; 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(length ? data : NULL)), + : data_(static_cast(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& 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 TakeVector( std::vector* to_destroy); - // Overridden from RefCountedMemory: + // RefCountedMemory: const unsigned char* front() const override; size_t size() const override; const std::vector& data() const { return data_; } std::vector& 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 + T* front_as() { + return reinterpret_cast(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 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 shm, size_t size); + + // RefCountedMemory: + const unsigned char* front() const override; + size_t size() const override; + + private: + ~RefCountedSharedMemory() override; + + const std::unique_ptr 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 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 bd2ed01..b7498f9 100644 --- a/base/memory/ref_counted_memory_unittest.cc +++ b/base/memory/ref_counted_memory_unittest.cc @@ -6,13 +6,19 @@ #include +#include + +#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 mem = new RefCountedStaticMemory( - "static mem00", 10); + auto mem = MakeRefCounted("static mem00", 10); EXPECT_EQ(10U, mem->size()); EXPECT_EQ("static mem", std::string(mem->front_as(), 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 mem2; { - unsigned char data2[] = { 12, 11, 99 }; - mem2 = new RefCountedBytes(data2, 3); + const unsigned char kData[] = {12, 11, 99}; + mem2 = MakeRefCounted(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(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()[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 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(); + ASSERT_TRUE(shm->CreateAndMapAnonymous(sizeof(kData))); + memcpy(shm->memory(), kData, sizeof(kData)); + + auto mem = + MakeRefCounted(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 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(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 mem1 = RefCountedString::TakeString(&s1); - std::vector d2; - d2.push_back('s'); - d2.push_back('a'); - d2.push_back('m'); - d2.push_back('e'); + std::vector d2 = {'s', 'a', 'm', 'e'}; scoped_refptr 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 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 515f422..df1c30f 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 #include #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 { protected: - virtual ~SelfAssign() {} + virtual ~SelfAssign() = default; private: friend class base::RefCounted; @@ -22,7 +22,7 @@ class SelfAssign : public base::RefCounted { class Derived : public SelfAssign { protected: - ~Derived() override {} + ~Derived() override = default; private: friend class base::RefCounted; @@ -112,9 +112,29 @@ class Other : public base::RefCounted { private: friend class base::RefCounted; - ~Other() {} + ~Other() = default; }; +class HasPrivateDestructorWithDeleter; + +struct Deleter { + static void Destruct(const HasPrivateDestructorWithDeleter* x); +}; + +class HasPrivateDestructorWithDeleter + : public base::RefCounted { + public: + HasPrivateDestructorWithDeleter() = default; + + private: + friend struct Deleter; + ~HasPrivateDestructorWithDeleter() = default; +}; + +void Deleter::Destruct(const HasPrivateDestructorWithDeleter* x) { + delete x; +} + scoped_refptr Overloaded(scoped_refptr other) { return other; } @@ -127,11 +147,30 @@ class InitialRefCountIsOne : public base::RefCounted { public: REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE(); - InitialRefCountIsOne() {} + InitialRefCountIsOne() = default; private: friend class base::RefCounted; - ~InitialRefCountIsOne() {} + ~InitialRefCountIsOne() = default; +}; + +// Checks that the scoped_refptr is null before the reference counted object is +// destroyed. +class CheckRefptrNull : public base::RefCounted { + public: + // Set the last scoped_refptr that will have a reference to this object. + void set_scoped_refptr(scoped_refptr* ptr) { ptr_ = ptr; } + + protected: + virtual ~CheckRefptrNull() { + EXPECT_NE(ptr_, nullptr); + EXPECT_EQ(ptr_->get(), nullptr); + } + + private: + friend class base::RefCounted; + + scoped_refptr* ptr_ = nullptr; }; } // end namespace @@ -139,7 +178,13 @@ class InitialRefCountIsOne : public base::RefCounted { TEST(RefCountedUnitTest, TestSelfAssignment) { SelfAssign* p = new SelfAssign; scoped_refptr 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 initial = base::MakeOpaqueRefCounted(); - base::TestOpaqueRefCounted(initial); - - scoped_refptr assigned; - assigned = initial; - - scoped_refptr copied(initial); - - scoped_refptr moved(std::move(initial)); - - scoped_refptr move_assigned; - move_assigned = std::move(moved); -} - -TEST(RefCountedUnitTest, ScopedRefPtrToOpaqueThreadSafe) { - scoped_refptr initial = - base::MakeOpaqueRefCountedThreadSafe(); - base::TestOpaqueRefCountedThreadSafe(initial); - - scoped_refptr assigned; - assigned = initial; - - scoped_refptr copied(initial); - - scoped_refptr moved(std::move(initial)); - - scoped_refptr move_assigned; - move_assigned = std::move(moved); -} - TEST(RefCountedUnitTest, BooleanTesting) { scoped_refptr 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 p1(raw); + scoped_refptr& 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(new Derived); - scoped_refptr expected(derived); + const scoped_refptr derived(new Derived); + const scoped_refptr expected(derived); EXPECT_EQ(expected, Overloaded(derived)); - scoped_refptr other(new Other); + const scoped_refptr other(new Other); EXPECT_EQ(other, Overloaded(other)); } TEST(RefCountedUnitTest, TestOverloadResolutionMove) { scoped_refptr derived(new Derived); - scoped_refptr expected(derived); + const scoped_refptr expected(derived); EXPECT_EQ(expected, Overloaded(std::move(derived))); scoped_refptr other(new Other); - scoped_refptr other2(other); + const scoped_refptr other2(other); EXPECT_EQ(other2, Overloaded(std::move(other))); } +TEST(RefCountedUnitTest, TestMakeRefCounted) { + scoped_refptr derived = new Derived; + EXPECT_TRUE(derived->HasOneRef()); + derived.reset(); + + scoped_refptr derived2 = base::MakeRefCounted(); + EXPECT_TRUE(derived2->HasOneRef()); + derived2.reset(); +} + TEST(RefCountedUnitTest, TestInitialRefCountIsOne) { scoped_refptr obj = - base::MakeShared(); + base::MakeRefCounted(); EXPECT_TRUE(obj->HasOneRef()); - obj = nullptr; + obj.reset(); scoped_refptr obj2 = base::AdoptRef(new InitialRefCountIsOne); EXPECT_TRUE(obj2->HasOneRef()); - obj2 = nullptr; + obj2.reset(); - scoped_refptr obj3 = base::MakeShared(); + scoped_refptr obj3 = base::MakeRefCounted(); 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 obj = + base::MakeRefCounted(); +} + +TEST(RefCountedUnitTest, TestReset) { + ScopedRefPtrCountBase::reset_count(); + + // Create ScopedRefPtrCountBase that is referenced by |obj1| and |obj2|. + scoped_refptr obj1 = + base::MakeRefCounted(); + scoped_refptr 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 obj; + obj.reset(); + // |obj| should still be null after calling reset(). + EXPECT_EQ(obj.get(), nullptr); +} + +TEST(RefCountedUnitTest, CheckScopedRefptrNullBeforeObjectDestruction) { + scoped_refptr obj = base::MakeRefCounted(); + 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 obj = - base::MakeShared(); + base::MakeRefCounted(); 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 0000000..389d0cb --- /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 + +#include +#include +#include + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" + +template +class scoped_refptr; + +namespace base { + +template +class RefCounted; +template +class RefCountedThreadSafe; + +template +scoped_refptr AdoptRef(T* t); + +namespace subtle { + +enum AdoptRefTag { kAdoptRefTag }; +enum StartRefCountFromZeroTag { kStartRefCountFromZeroTag }; +enum StartRefCountFromOneTag { kStartRefCountFromOneTag }; + +template +constexpr bool IsRefCountPreferenceOverridden(const T*, + const RefCounted*) { + return !std::is_same, + std::decay_t>::value; +} + +template +constexpr bool IsRefCountPreferenceOverridden( + const T*, + const RefCountedThreadSafe*) { + return !std::is_same, + std::decay_t>::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 +scoped_refptr AdoptRef(T* obj) { + using Tag = std::decay_t; + static_assert(std::is_same::value, + "Use AdoptRef only for the reference count starts from one."); + + DCHECK(obj); + DCHECK(obj->HasOneRef()); + obj->Adopted(); + return scoped_refptr(obj, subtle::kAdoptRefTag); +} + +namespace subtle { + +template +scoped_refptr AdoptRefIfNeeded(T* obj, StartRefCountFromZeroTag) { + return scoped_refptr(obj); +} + +template +scoped_refptr 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 +scoped_refptr MakeRefCounted(Args&&... args) { + T* obj = new T(std::forward(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. +template +scoped_refptr WrapRefCounted(T* t) { + return scoped_refptr(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 { +// ... +// private: +// friend class RefCounted; // Allow destruction by RefCounted<>. +// ~MyFoo(); // Destructor must be private/protected. +// }; +// +// void some_function() { +// scoped_refptr foo = MakeRefCounted(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr foo = MakeRefCounted(); +// ... +// foo.reset(); // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr acts like a pointer to T. +// Given two scoped_refptr classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr a = MakeRefCounted(); +// scoped_refptr 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 a = MakeRefCounted(); +// scoped_refptr 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. 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 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 ::value>::type> + scoped_refptr(const scoped_refptr& 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 ::value>::type> + scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.ptr_) { + r.ptr_ = nullptr; + } + + ~scoped_refptr() { + static_assert(!base::subtle::IsRefCountPreferenceOverridden( + static_cast(nullptr), static_cast(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 + bool operator==(const scoped_refptr& rhs) const { + return ptr_ == rhs.get(); + } + + template + bool operator!=(const scoped_refptr& rhs) const { + return !operator==(rhs); + } + + template + bool operator<(const scoped_refptr& rhs) const { + return ptr_ < rhs.get(); + } + + protected: + T* ptr_ = nullptr; + + private: + template + friend scoped_refptr base::AdoptRef(U*); + + scoped_refptr(T* p, base::subtle::AdoptRefTag) : ptr_(p) {} + + // Friend required for move constructors that set r.ptr_ to null. + template + friend class scoped_refptr; + + // Non-inline helpers to allow: + // class Opaque; + // extern template class scoped_refptr; + // Otherwise the compiler will complain that Opaque is an incomplete type. + static void AddRef(T* ptr); + static void Release(T* ptr); +}; + +// static +template +void scoped_refptr::AddRef(T* ptr) { + ptr->AddRef(); +} + +// static +template +void scoped_refptr::Release(T* ptr) { + ptr->Release(); +} + +template +bool operator==(const scoped_refptr& lhs, const U* rhs) { + return lhs.get() == rhs; +} + +template +bool operator==(const T* lhs, const scoped_refptr& rhs) { + return lhs == rhs.get(); +} + +template +bool operator==(const scoped_refptr& lhs, std::nullptr_t null) { + return !static_cast(lhs); +} + +template +bool operator==(std::nullptr_t null, const scoped_refptr& rhs) { + return !static_cast(rhs); +} + +template +bool operator!=(const scoped_refptr& lhs, const U* rhs) { + return !operator==(lhs, rhs); +} + +template +bool operator!=(const T* lhs, const scoped_refptr& rhs) { + return !operator==(lhs, rhs); +} + +template +bool operator!=(const scoped_refptr& lhs, std::nullptr_t null) { + return !operator==(lhs, null); +} + +template +bool operator!=(std::nullptr_t null, const scoped_refptr& rhs) { + return !operator==(null, rhs); +} + +template +std::ostream& operator<<(std::ostream& out, const scoped_refptr& p) { + return out << p.get(); +} + +template +void swap(scoped_refptr& lhs, scoped_refptr& 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 a320b1e..0000000 --- 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 - -#include -#include - -#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 ScopedVector { - public: - typedef typename std::vector::allocator_type allocator_type; - typedef typename std::vector::size_type size_type; - typedef typename std::vector::difference_type difference_type; - typedef typename std::vector::pointer pointer; - typedef typename std::vector::const_pointer const_pointer; - typedef typename std::vector::reference reference; - typedef typename std::vector::const_reference const_reference; - typedef typename std::vector::value_type value_type; - typedef typename std::vector::iterator iterator; - typedef typename std::vector::const_iterator const_iterator; - typedef typename std::vector::reverse_iterator reverse_iterator; - typedef typename std::vector::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 elem) { v_.push_back(elem.release()); } - - void pop_back() { - DCHECK(!empty()); - delete v_.back(); - v_.pop_back(); - } - - std::vector& get() { return v_; } - const std::vector& get() const { return v_; } - void swap(std::vector& other) { v_.swap(other); } - void swap(ScopedVector& other) { v_.swap(other.v_); } - void release(std::vector* 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 - 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 x) { - return v_.insert(position, x.release()); - } - - // Lets the ScopedVector take ownership of elements in [first,last). - template - 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 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 916dab9..0000000 --- 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 -#include - -#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 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 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 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 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 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 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 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 scoped_vector; - scoped_vector.push_back(watcher.NewLifeCycleObject()); - EXPECT_FALSE(scoped_vector.empty()); - EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); - - ScopedVector 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 scoped_vector; - scoped_vector.push_back(watcher.NewLifeCycleObject()); - ScopedVector 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 -ScopedVector PassThru(ScopedVector scoper) { - return scoper; -} - -TEST(ScopedVectorTest, Passed) { - int deletes = 0; - ScopedVector deleter_vector; - deleter_vector.push_back(new DeleteCounter(&deletes)); - EXPECT_EQ(0, deletes); - base::Callback(void)> callback = - base::Bind(&PassThru, base::Passed(&deleter_vector)); - EXPECT_EQ(0, deletes); - ScopedVector result = callback.Run(); - EXPECT_EQ(0, deletes); - result.clear(); - EXPECT_EQ(1, deletes); -}; - -TEST(ScopedVectorTest, InsertRange) { - LifeCycleWatcher watchers[5]; - - std::vector 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 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 elem(new DeleteCounter(&delete_counter)); - EXPECT_EQ(0, delete_counter); - { - ScopedVector 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 4b66cc6..c573ef7 100644 --- a/base/memory/shared_memory.h +++ b/base/memory/shared_memory.h @@ -10,12 +10,14 @@ #include #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 #include #include @@ -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; - - 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 6f1d9cb..c126767 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(std::numeric_limits::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 0000000..085bde4 --- /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 dc33eea..7367188 100644 --- a/base/memory/shared_memory_handle.h +++ b/base/memory/shared_memory_handle.h @@ -7,11 +7,12 @@ #include +#include "base/unguessable_token.h" #include "build/build_config.h" #if defined(OS_WIN) -#include #include "base/process/process_handle.h" +#include "base/win/windows_types.h" #elif defined(OS_MACOSX) && !defined(OS_IOS) #include #include "base/base_export.h" @@ -21,20 +22,22 @@ #elif defined(OS_POSIX) #include #include "base/file_descriptor_posix.h" +#elif defined(OS_FUCHSIA) +#include #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 0000000..1b61535 --- /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 +#include + +#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 0000000..09dfb9c --- /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 + +#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 7fbfb7a..f98b734 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 +#include + +#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 b515828..2c24f86 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 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 0000000..2b42928 --- /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 + +#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 +#endif + +#if defined(OS_WIN) +#include +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include +#include "base/mac/mach_logging.h" +#endif + +#if defined(OS_FUCHSIA) +#include +#include +#include +#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(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(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 0000000..ace4c15 --- /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 + +#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 3443cd9..aa71895 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(std::numeric_limits::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(std::numeric_limits::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(-1); if (mmap_succeeded) { mapped_size_ = bytes; + mapped_id_ = shm_.GetGUID(); DCHECK_EQ(0U, reinterpret_cast(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(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 0000000..78ac630 --- /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 + +#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(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 +class SharedMemoryRegionTest : public ::testing::Test { + public: + void SetUp() override { + std::tie(region_, rw_mapping_) = + CreateMappedRegion(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 + 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(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(kDataSize); + ASSERT_TRUE(region.IsValid()); + ASSERT_TRUE(rw_mapping.IsValid()); + uint32_t* ptr = static_cast(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(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(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 +class DuplicatableSharedMemoryRegionTest + : public SharedMemoryRegionTest {}; + +typedef ::testing::Types + 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(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(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 d87fad0..dd76a86 100644 --- a/base/memory/shared_memory_unittest.cc +++ b/base/memory/shared_memory_unittest.cc @@ -10,19 +10,30 @@ #include #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 #include @@ -32,22 +43,31 @@ #include #endif +#if defined(OS_LINUX) +#include +#endif + #if defined(OS_WIN) #include "base/win/scoped_handle.h" #endif +#if defined(OS_FUCHSIA) +#include +#include +#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 { + 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(NULL)); - ASSERT_NE(memory2.memory(), static_cast(NULL)); + ASSERT_NE(memory1.memory(), static_cast(nullptr)); + ASSERT_NE(memory2.memory(), static_cast(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(memory.memory()); - ASSERT_NE(ptr, static_cast(NULL)); + ASSERT_NE(ptr, static_cast(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(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(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(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(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(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(memory.memory()); - ASSERT_NE(ptr, static_cast(NULL)); + ASSERT_NE(ptr, static_cast(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(memory.memory()); - ASSERT_NE(ptr, static_cast(NULL)); + ASSERT_NE(ptr, static_cast(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 f68ecaa..0000000 --- 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 5c58d5f..880ef0a 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 instead: +// +// Factory& Factory::GetInstance() { +// static base::NoDestructor 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. Calls operator new and operator delete on // the object. Registers automatic deletion at process exit. @@ -84,11 +78,10 @@ struct LeakySingletonTraits : public DefaultSingletonTraits { #endif }; - // Alternate traits for use with the Singleton. 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 { // process once you've unloaded. template 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 buffer_; + alignas(Type) static char buffer_[sizeof(Type)]; // Signal the object was already deleted, so it is not revived. static subtle::Atomic32 dead_; }; template -AlignedMemory - StaticMemorySingletonTraits::buffer_; +alignas(Type) char StaticMemorySingletonTraits::buffer_[sizeof(Type)]; template subtle::Atomic32 StaticMemorySingletonTraits::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(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(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(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 a15145c..06e53b2 100644 --- a/base/memory/singleton_unittest.cc +++ b/base/memory/singleton_unittest.cc @@ -16,6 +16,14 @@ static_assert(DefaultSingletonTraits::kRegisterAtExit == true, typedef void (*CallbackFunc)(); +template +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 { class CallbackSingleton { public: - CallbackSingleton() : callback_(NULL) { } + CallbackSingleton() : callback_(nullptr) {} CallbackFunc callback_; }; @@ -115,8 +123,8 @@ struct CallbackSingletonWithStaticTrait::Trait template class AlignedTestSingleton { public: - AlignedTestSingleton() {} - ~AlignedTestSingleton() {} + AlignedTestSingleton() = default; + ~AlignedTestSingleton() = default; static AlignedTestSingleton* GetInstance() { return Singleton>::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(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* align4 = AlignedTestSingleton::GetInstance(); - AlignedTestSingleton >* align32 = - AlignedTestSingleton >::GetInstance(); - AlignedTestSingleton >* align128 = - AlignedTestSingleton >::GetInstance(); - AlignedTestSingleton >* align4096 = - AlignedTestSingleton >::GetInstance(); + AlignedTestSingleton>* align32 = + AlignedTestSingleton>::GetInstance(); + AlignedTestSingleton>* align128 = + AlignedTestSingleton>::GetInstance(); + AlignedTestSingleton>* align4096 = + AlignedTestSingleton>::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 0000000..422b5a9 --- /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 + +#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 0000000..ea637cd --- /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 c179b80..d2a7d89 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) {} -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 3544439..34e7d2e 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); ~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. See base::AsWeakPtr() below for a helper // function that makes calling this easier. + // + // Precondition: t != nullptr template static WeakPtr StaticAsWeakPtr(Derived* t) { static_assert( std::is_base_of::value, "AsWeakPtr argument must inherit from SupportsWeakPtr"); - return AsWeakPtrImpl(t, *t); + return AsWeakPtrImpl(t); } private: @@ -182,10 +191,10 @@ class SupportsWeakPtrBase { // which is an instance of SupportsWeakPtr. We can then safely // static_cast the Base* to a Derived*. template - static WeakPtr AsWeakPtrImpl( - Derived* t, const SupportsWeakPtr&) { - WeakPtr ptr = t->Base::AsWeakPtr(); - return WeakPtr(ptr.ref_, static_cast(ptr.ptr_)); + static WeakPtr AsWeakPtrImpl(SupportsWeakPtr* t) { + WeakPtr ptr = t->AsWeakPtr(); + return WeakPtr( + ptr.ref_, static_cast(reinterpret_cast(ptr.ptr_))); } }; @@ -209,20 +218,30 @@ template class WeakPtrFactory; template 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 - WeakPtr(const WeakPtr& other) : WeakPtrBase(other), ptr_(other.ptr_) { + WeakPtr(const WeakPtr& 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(other.ptr_); + ptr_ = reinterpret_cast(t); } template - WeakPtr(WeakPtr&& other) - : WeakPtrBase(std::move(other)), ptr_(other.ptr_) {} + WeakPtr(WeakPtr&& 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(other.ptr_); + ptr_ = reinterpret_cast(t); + } - T* get() const { return ref_.is_valid() ? ptr_ : nullptr; } + T* get() const { + return ref_.is_valid() ? reinterpret_cast(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; 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(ptr)) {} }; // Allow callers to compare WeakPtrs against nullptr to test validity. @@ -275,22 +283,32 @@ bool operator==(std::nullptr_t, const WeakPtr& 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 that is used to pass around a weak reference to a bool. template -class WeakPtrFactory { +class WeakPtrFactory : public internal::WeakPtrFactoryBase { public: - explicit WeakPtrFactory(T* ptr) : ptr_(ptr) { - } + explicit WeakPtrFactory(T* ptr) + : WeakPtrFactoryBase(reinterpret_cast(ptr)) {} - ~WeakPtrFactory() { ptr_ = nullptr; } + ~WeakPtrFactory() = default; WeakPtr GetWeakPtr() { - DCHECK(ptr_); - return WeakPtr(weak_reference_owner_.GetRef(), ptr_); + return WeakPtr(weak_reference_owner_.GetRef(), + reinterpret_cast(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 SupportsWeakPtr : public internal::SupportsWeakPtrBase { public: - SupportsWeakPtr() {} + SupportsWeakPtr() = default; WeakPtr AsWeakPtr() { return WeakPtr(weak_reference_owner_.GetRef(), static_cast(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 1a4870e..f8dfb7c 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 { - 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; }; @@ -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 ptr = AsWeakPtr(&target); + EXPECT_EQ(&target, ptr.get()); +} + +TEST(WeakPtrTest, DerivedTargetMultipleInheritance) { + DerivedTargetMultipleInheritance d; + Target& b = d; + EXPECT_NE(static_cast(&d), static_cast(&b)); + const WeakPtr pb = AsWeakPtr(&b); + EXPECT_EQ(pb.get(), &b); + const WeakPtr pd = AsWeakPtr(&d); + EXPECT_EQ(pd.get(), &d); +} + TEST(WeakPtrFactoryTest, BooleanTesting) { int data; WeakPtrFactory factory(&data); diff --git a/base/memory/weak_ptr_unittest.nc b/base/memory/weak_ptr_unittest.nc index 9b1226b..b96b033 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 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::StaticAsWeakPtr(&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 ptr = AsWeakPtr(&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 ptr = AsWeakPtr(&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 ptr = AsWeakPtr(&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 ptr = AsWeakPtr(&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 ptr = AsWeakPtr(&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 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 0000000..063e672 --- /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 + +#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 0000000..f656db1 --- /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 316b5ec..6821730 100644 --- a/base/message_loop/incoming_task_queue.cc +++ b/base/message_loop/incoming_task_queue.cc @@ -7,8 +7,10 @@ #include #include +#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 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(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 17bea07..bdcd6d7 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 { 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 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; + + // 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 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 3d55920..b3e32de 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* GetTLSMessageLoop() { - static auto* lazy_tls_ptr = new base::ThreadLocalPointer(); - 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(pump); -} -#endif // !defined(OS_NACL_SFI) +MessageLoop::MessagePumpFactory* message_pump_for_ui_factory_ = nullptr; std::unique_ptr ReturnPump(std::unique_ptr 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 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 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(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() -#else -#define MESSAGE_PUMP_UI std::unique_ptr(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(new MessagePumpCFRunLoop()) -#else -#define MESSAGE_PUMP_DEFAULT \ - std::unique_ptr(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(); +#endif } + if (type == MessageLoop::TYPE_IO) return std::unique_ptr(new MessagePumpForIO()); @@ -199,108 +267,43 @@ std::unique_ptr 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(); +#else + return std::make_unique(); +#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::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( + WrapUnique(message_loop_controller_))), + unbound_task_runner_(MakeRefCounted( + 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(pump_.get())->Attach(this); +#endif } std::string MessageLoop::GetThreadName() const { @@ -357,7 +376,8 @@ std::string MessageLoop::GetThreadName() const { void MessageLoop::SetTaskRunner( scoped_refptr 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(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 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(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(pump_.get()) - ->StartForUnitTest(this, factory, test_done_event); +// static +MessageLoopCurrentForUI MessageLoopForUI::current() { + return MessageLoopCurrentForUI::Get(); } -void MessageLoopForUI::Abort() { - static_cast(pump_.get())->Abort(); +// static +bool MessageLoopForUI::IsCurrent() { + return MessageLoopCurrentForUI::IsSet(); } -#endif #if defined(OS_IOS) void MessageLoopForUI::Attach() { static_cast(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(pump_.get())->WatchFileDescriptor( - fd, - persistent, - mode, - controller, - delegate); +#if defined(OS_ANDROID) +void MessageLoopForUI::Abort() { + static_cast(pump_.get())->Abort(); } -#endif - -#endif // !defined(OS_NACL) -//------------------------------------------------------------------------------ -// MessageLoopForIO +bool MessageLoopForUI::IsAborted() { + return static_cast(pump_.get())->IsAborted(); +} -#if !defined(OS_NACL_SFI) +void MessageLoopForUI::QuitWhenIdle(base::OnceClosure callback) { + static_cast(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(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 fa054f4..e093502 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(MessagePumpFactory)(); + using MessagePumpFactory = std::unique_ptr(); // 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 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& task_runner() { + const scoped_refptr& 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 pump_; - using MessagePumpFactoryCallback = Callback()>; + using MessagePumpFactoryCallback = + OnceCallback()>; // 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 destruction_observers_; - ObserverList 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 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 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 + 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 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(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(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 0000000..4959b70 --- /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* GetTLSMessageLoop() { + static NoDestructor> 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& MessageLoopCurrent::task_runner() + const { + DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_); + return current_->task_runner(); +} + +void MessageLoopCurrent::SetTaskRunner( + scoped_refptr 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(loop); + return MessageLoopCurrentForUI( + loop_for_ui, static_cast(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(current_)->Attach(); +} +#endif // defined(OS_IOS) + +#if defined(OS_ANDROID) +void MessageLoopCurrentForUI::Abort() { + static_cast(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(loop); + return MessageLoopCurrentForIO( + loop_for_io, static_cast(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 0000000..61d1607 --- /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> 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& 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 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::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 0000000..4dd5f28 --- /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 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(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 0000000..867e8fe --- /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 +#include + +#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 Create(WaitableEvent* start_event, + std::unique_ptr 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) + : 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_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); + }; + + PostingThread(WaitableEvent* start_event, std::unique_ptr 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 { + public: + MessageLoopPerfTest() + : message_loop_task_runner_(SequencedTaskRunnerHandle::Get()), + run_posting_threads_(WaitableEvent::ResetPolicy::MANUAL, + WaitableEvent::InitialState::NOT_SIGNALED) {} + + static std::string ParamInfoToString( + ::testing::TestParamInfo 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 + void RunTest(const int num_posting_threads, TimeDelta duration) { + std::vector> threads; + for (int i = 0; i < num_posting_threads; ++i) { + threads.emplace_back(PostingThread::Create( + &run_posting_threads_, std::make_unique(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 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(GetParam(), TimeDelta::FromSeconds(3)); + perf_test::PrintResult("task_posting", "", + PostingThreadCountToString(GetParam()), + tasks_posted_duration().InMicroseconds() / + static_cast(num_tasks_posted()), + "us/task", true); + perf_test::PrintResult("task_running", "", + PostingThreadCountToString(GetParam()), + tasks_run_duration().InMicroseconds() / + static_cast(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 aece087..f251e3b 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 99a96a7..c7d48c2 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; 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 0000000..3ab9ba2 --- /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 +#include + +#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 task_source_observer) { + base::TimeTicks start = base::TimeTicks::Now(); + base::TimeTicks now; + FakeObserver* task_source_observer_raw = task_source_observer.get(); + scoped_refptr queue( + base::MakeRefCounted( + std::move(task_source_observer))); + scoped_refptr task_runner( + base::MakeRefCounted(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(num_posted), + "us/task", true); + } +}; + +TEST_F(BasicPostTaskPerfTest, OneTaskPerReload) { + Run(10000, 1, std::make_unique()); +} + +TEST_F(BasicPostTaskPerfTest, TenTasksPerReload) { + Run(10000, 10, std::make_unique()); +} + +TEST_F(BasicPostTaskPerfTest, OneHundredTasksPerReload) { + Run(1000, 100, std::make_unique()); +} + +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 pump_{std::make_unique()}; + debug::TaskAnnotator task_annotator_; + + DISALLOW_COPY_AND_ASSIGN(FakeObserverSimulatingOverhead); +}; + +TEST_F(BasicPostTaskPerfTest, OneTaskPerReloadWithOverhead) { + Run(10000, 1, std::make_unique()); +} + +TEST_F(BasicPostTaskPerfTest, TenTasksPerReloadWithOverhead) { + Run(10000, 10, std::make_unique()); +} + +TEST_F(BasicPostTaskPerfTest, OneHundredTasksPerReloadWithOverhead) { + Run(1000, 100, std::make_unique()); +} + +// 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(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 d403c70..c7e9aa0 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 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 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 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 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 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 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 6ffb16d..0000000 --- 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 - -#include - -#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 { - 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() {} - - 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 pump(factory()); - MessageLoop loop(std::move(pump)); - // Add tests to message loop - scoped_refptr 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 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 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 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 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 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 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 { - 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() { - *was_deleted_ = true; - if (post_on_delete_.get()) - ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, Bind(&RecordDeletionProbe::Run, post_on_delete_)); - } - - scoped_refptr post_on_delete_; - bool* was_deleted_; -}; - -void RunTest_EnsureDeletion(MessagePumpFactory factory) { - bool a_was_deleted = false; - bool b_was_deleted = false; - { - std::unique_ptr 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 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 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 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 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 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 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 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 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 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 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(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitTop(MessagePumpFactory factory) { - std::unique_ptr 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(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitNested(MessagePumpFactory factory) { - std::unique_ptr 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(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitBogus(MessagePumpFactory factory) { - std::unique_ptr 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(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitDeep(MessagePumpFactory factory) { - std::unique_ptr 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(task_index), order.Size()); -} - -// Tests RunLoopQuit works before RunWithID. -void RunTest_RunLoopQuitOrderBefore(MessagePumpFactory factory) { - std::unique_ptr 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 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(task_index), order.Size()); -} - -// Tests RunLoopQuit works after RunWithID. -void RunTest_RunLoopQuitOrderAfter(MessagePumpFactory factory) { - std::unique_ptr 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(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 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 b7ae28e..0000000 --- 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 9d771d5..7743479 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 TypeDefaultMessagePumpFactory() { - return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_DEFAULT); -} - -std::unique_ptr TypeIOMessagePumpFactory() { - return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_IO); -} - -std::unique_ptr TypeUIMessagePumpFactory() { - return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_UI); -} - class Foo : public RefCounted { 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() = 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::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 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,147 +198,383 @@ class TaskList { std::vector task_list_; }; -// 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); - MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK); - order->RecordEnd(MESSAGEBOX, cookie); -} +class DummyTaskObserver : public MessageLoop::TaskObserver { + public: + explicit DummyTaskObserver(int num_tasks) + : num_tasks_started_(0), num_tasks_processed_(0), num_tasks_(num_tasks) {} -// Will end the MessageBox. -void EndDialogFunc(TaskList* order, int cookie) { - order->RecordStart(ENDDIALOG, cookie); - HWND window = GetActiveWindow(); - if (window != NULL) { - EXPECT_NE(EndDialog(window, IDCONTINUE), 0); - // Cheap way to signal that the window wasn't found if RunEnd() isn't - // called. - order->RecordEnd(ENDDIALOG, cookie); + 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) - MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoopCurrent::Get()->SetNestableTasksAllowed(true); ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, - Bind(&RecursiveFunc, order, cookie, depth - 1, is_reentrant)); + BindOnce(&RecursiveFunc, order, cookie, depth - 1, is_reentrant)); } order->RecordEnd(RECURSIVE, cookie); } void QuitFunc(TaskList* order, int cookie) { order->RecordStart(QUITMESSAGELOOP, cookie); - MessageLoop::current()->QuitWhenIdle(); + RunLoop::QuitCurrentWhenIdleDeprecated(); order->RecordEnd(QUITMESSAGELOOP, cookie); } -void RecursiveFuncWin(scoped_refptr task_runner, - HANDLE event, - bool expect_window, - TaskList* order, - bool is_reentrant) { - task_runner->PostTask(FROM_HERE, - Bind(&RecursiveFunc, order, 1, 2, is_reentrant)); - task_runner->PostTask(FROM_HERE, - Bind(&MessageBoxFunc, order, 2, is_reentrant)); - task_runner->PostTask(FROM_HERE, - Bind(&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)); +void PostNTasks(int posts_remaining) { + if (posts_remaining > 1) { + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&PostNTasks, posts_remaining - 1)); + } +} - // Enforce that every tasks are sent before starting to run the main thread - // message loop. - ASSERT_TRUE(SetEvent(event)); +enum class TaskSchedulerAvailability { + NO_TASK_SCHEDULER, + // Unsupported in libchrome. + // WITH_TASK_SCHEDULER, +}; - // Poll for the MessageBox. Don't do this at home! At the speed we do it, - // you will never realize one MessageBox was shown. - for (; expect_window;) { - HWND window = FindWindow(L"#32770", kMessageBoxTitle); - if (window) { - // Dismiss it. - for (;;) { - HWND button = FindWindowEx(window, NULL, L"Button", NULL); - if (button != NULL) { - EXPECT_EQ(0, SendMessage(button, WM_LBUTTONDOWN, 0, 0)); - EXPECT_EQ(0, SendMessage(button, WM_LBUTTONUP, 0, 0)); - break; - } - } - break; - } +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"; } -// TODO(darin): These tests need to be ported since they test critical -// message loop functionality. +class MessageLoopTest + : public ::testing::TestWithParam { + public: + MessageLoopTest() = default; + ~MessageLoopTest() override = default; + + void SetUp() override { + // Unsupported in libchrome. +#if 0 + if (GetParam() == TaskSchedulerAvailability::WITH_TASK_SCHEDULER) + TaskScheduler::CreateAndStartWithDefaultParams("MessageLoopTest"); +#endif + } -// A side effect of this test is the generation a beep. Sorry. -void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); + 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 + } - Thread worker("RecursiveDenial2_worker"); - Thread::Options options; - options.message_loop_type = message_loop_type; - ASSERT_EQ(true, worker.StartWithOptions(options)); - 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)); - // Let the other thread execute. - WaitForSingleObject(event.Get(), INFINITE); - RunLoop().Run(); + static std::string ParamInfoToString( + ::testing::TestParamInfo param_info) { + return TaskSchedulerAvailabilityToString(param_info.param); + } - ASSERT_EQ(17u, 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(MESSAGEBOX, 2, true)); - EXPECT_EQ(order.Get(3), TaskItem(MESSAGEBOX, 2, false)); - EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, true)); - EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 3, false)); - // When EndDialogFunc is processed, the window is already dismissed, hence no - // "end" entry. - EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, true)); - EXPECT_EQ(order.Get(7), TaskItem(QUITMESSAGELOOP, 5, true)); - EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, false)); - EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 3, true)); - EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, false)); - EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 3, true)); - EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, false)); + private: + DISALLOW_COPY_AND_ASSIGN(MessageLoopTest); +}; + +#if defined(OS_ANDROID) +void DoNotRun() { + ASSERT_TRUE(false); } -// A side effect of this test is the generation a beep. Sorry. This test also -// needs to process windows messages on the current thread. -void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); +void RunTest_AbortDontRunMoreTasks(bool delayed, bool init_java_first) { + WaitableEvent test_done_event(WaitableEvent::ResetPolicy::MANUAL, + WaitableEvent::InitialState::NOT_SIGNALED); + std::unique_ptr java_thread; + if (init_java_first) { + java_thread = android::JavaHandlerThreadHelpers::CreateJavaFirst(); + } else { + java_thread = std::make_unique( + "JavaHandlerThreadForTesting from AbortDontRunMoreTasks"); + } + java_thread->Start(); + java_thread->ListenForUncaughtExceptionsForTesting(); - Thread worker("RecursiveSupport2_worker"); - Thread::Options options; - options.message_loop_type = message_loop_type; - ASSERT_EQ(true, worker.StartWithOptions(options)); - 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)); + 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 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("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) + MessageLoopCurrent::Get()->SetNestableTasksAllowed(true); + MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK); + order->RecordEnd(MESSAGEBOX, cookie); +} + +// Will end the MessageBox. +void EndDialogFunc(TaskList* order, int cookie) { + order->RecordStart(ENDDIALOG, cookie); + HWND window = GetActiveWindow(); + if (window != NULL) { + EXPECT_NE(EndDialog(window, IDCONTINUE), 0); + // Cheap way to signal that the window wasn't found if RunEnd() isn't + // called. + order->RecordEnd(ENDDIALOG, cookie); + } +} + +void RecursiveFuncWin(scoped_refptr task_runner, + HANDLE event, + bool expect_window, + TaskList* order, + bool is_reentrant) { + task_runner->PostTask(FROM_HERE, + BindOnce(&RecursiveFunc, order, 1, 2, is_reentrant)); + task_runner->PostTask(FROM_HERE, + BindOnce(&MessageBoxFunc, order, 2, is_reentrant)); + task_runner->PostTask(FROM_HERE, + 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, 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. + ASSERT_TRUE(SetEvent(event)); + + // Poll for the MessageBox. Don't do this at home! At the speed we do it, + // you will never realize one MessageBox was shown. + for (; expect_window;) { + HWND window = FindWindow(L"#32770", kMessageBoxTitle); + if (window) { + // Dismiss it. + for (;;) { + HWND button = FindWindowEx(window, NULL, L"Button", NULL); + if (button != NULL) { + EXPECT_EQ(0, SendMessage(button, WM_LBUTTONDOWN, 0, 0)); + EXPECT_EQ(0, SendMessage(button, WM_LBUTTONUP, 0, 0)); + break; + } + } + break; + } + } +} + +// TODO(darin): These tests need to be ported since they test critical +// message loop functionality. + +// A side effect of this test is the generation a beep. Sorry. +void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + Thread worker("RecursiveDenial2_worker"); + Thread::Options options; + options.message_loop_type = message_loop_type; + ASSERT_EQ(true, worker.StartWithOptions(options)); + TaskList order; + win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.task_runner()->PostTask( + FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(), + event.Get(), true, &order, false)); + // Let the other thread execute. + WaitForSingleObject(event.Get(), INFINITE); + RunLoop().Run(); + + ASSERT_EQ(17u, 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(MESSAGEBOX, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(MESSAGEBOX, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 3, false)); + // When EndDialogFunc is processed, the window is already dismissed, hence no + // "end" entry. + EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, true)); + EXPECT_EQ(order.Get(7), TaskItem(QUITMESSAGELOOP, 5, true)); + EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, false)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, false)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 3, true)); + EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, false)); +} + +// A side effect of this test is the generation a beep. Sorry. This test also +// needs to process windows messages on the current thread. +void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) { + MessageLoop loop(message_loop_type); + + Thread worker("RecursiveSupport2_worker"); + Thread::Options options; + options.message_loop_type = message_loop_type; + ASSERT_EQ(true, worker.StartWithOptions(options)); + TaskList order; + win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL)); + worker.task_runner()->PostTask( + 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,219 +611,1272 @@ 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) +#if defined(OS_WIN) + +class TestIOHandler : public MessagePumpForIO::IOHandler { + public: + TestIOHandler(const wchar_t* name, HANDLE signal, bool wait); + + void OnIOCompleted(MessagePumpForIO::IOContext* context, + DWORD bytes_transfered, + DWORD error) override; + + void Init(); + void WaitForIO(); + OVERLAPPED* context() { return &context_.overlapped; } + DWORD size() { return sizeof(buffer_); } + + private: + char buffer_[48]; + MessagePumpForIO::IOContext context_; + HANDLE signal_; + win::ScopedHandle file_; + bool wait_; +}; + +TestIOHandler::TestIOHandler(const wchar_t* name, HANDLE signal, bool wait) + : signal_(signal), wait_(wait) { + memset(buffer_, 0, sizeof(buffer_)); + + file_.Set(CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL)); + EXPECT_TRUE(file_.IsValid()); +} + +void TestIOHandler::Init() { + MessageLoopCurrentForIO::Get()->RegisterIOHandler(file_.Get(), this); + + DWORD read; + EXPECT_FALSE(ReadFile(file_.Get(), buffer_, size(), &read, context())); + EXPECT_EQ(static_cast(ERROR_IO_PENDING), GetLastError()); + if (wait_) + WaitForIO(); +} + +void TestIOHandler::OnIOCompleted(MessagePumpForIO::IOContext* context, + DWORD bytes_transfered, + DWORD error) { + ASSERT_TRUE(context == &context_); + ASSERT_TRUE(SetEvent(signal_)); +} + +void TestIOHandler::WaitForIO() { + EXPECT_TRUE(MessageLoopCurrentForIO::Get()->WaitForIOCompletion(300, this)); + EXPECT_TRUE(MessageLoopCurrentForIO::Get()->WaitForIOCompletion(400, this)); +} + +void RunTest_IOHandler() { + win::ScopedHandle callback_called(CreateEvent(NULL, TRUE, FALSE, NULL)); + ASSERT_TRUE(callback_called.IsValid()); + + const wchar_t* kPipeName = L"\\\\.\\pipe\\iohandler_pipe"; + win::ScopedHandle server( + CreateNamedPipe(kPipeName, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + ASSERT_TRUE(server.IsValid()); + + Thread thread("IOHandler test"); + Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(thread.StartWithOptions(options)); + + TestIOHandler handler(kPipeName, callback_called.Get(), false); + thread.task_runner()->PostTask( + FROM_HERE, BindOnce(&TestIOHandler::Init, Unretained(&handler))); + // Make sure the thread runs and sleeps for lack of work. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + + const char buffer[] = "Hello there!"; + DWORD written; + EXPECT_TRUE(WriteFile(server.Get(), buffer, sizeof(buffer), &written, NULL)); + + DWORD result = WaitForSingleObject(callback_called.Get(), 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +void RunTest_WaitForIO() { + win::ScopedHandle callback1_called( + CreateEvent(NULL, TRUE, FALSE, NULL)); + win::ScopedHandle callback2_called( + CreateEvent(NULL, TRUE, FALSE, NULL)); + ASSERT_TRUE(callback1_called.IsValid()); + ASSERT_TRUE(callback2_called.IsValid()); + + const wchar_t* kPipeName1 = L"\\\\.\\pipe\\iohandler_pipe1"; + const wchar_t* kPipeName2 = L"\\\\.\\pipe\\iohandler_pipe2"; + win::ScopedHandle server1( + CreateNamedPipe(kPipeName1, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + win::ScopedHandle server2( + CreateNamedPipe(kPipeName2, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); + ASSERT_TRUE(server1.IsValid()); + ASSERT_TRUE(server2.IsValid()); + + Thread thread("IOHandler test"); + Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + ASSERT_TRUE(thread.StartWithOptions(options)); + + TestIOHandler handler1(kPipeName1, callback1_called.Get(), false); + TestIOHandler handler2(kPipeName2, callback2_called.Get(), true); + thread.task_runner()->PostTask( + 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, BindOnce(&TestIOHandler::Init, Unretained(&handler2))); + PlatformThread::Sleep(delay); + + // At this time handler1 is waiting to be called, and the thread is waiting + // on the Init method of handler2, filtering only handler2 callbacks. + + const char buffer[] = "Hello there!"; + DWORD written; + EXPECT_TRUE(WriteFile(server1.Get(), buffer, sizeof(buffer), &written, NULL)); + PlatformThread::Sleep(2 * delay); + EXPECT_EQ(static_cast(WAIT_TIMEOUT), + WaitForSingleObject(callback1_called.Get(), 0)) + << "handler1 has not been called"; + + EXPECT_TRUE(WriteFile(server2.Get(), buffer, sizeof(buffer), &written, NULL)); + + HANDLE objects[2] = { callback1_called.Get(), callback2_called.Get() }; + DWORD result = WaitForMultipleObjects(2, objects, TRUE, 1000); + EXPECT_EQ(WAIT_OBJECT_0, result); + + thread.Stop(); +} + +#endif // defined(OS_WIN) + +} // namespace + +//----------------------------------------------------------------------------- +// Each test is run against each type of MessageLoop. That way we are sure +// 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. + +namespace { + +struct MessageLoopTypedTestParams { + MessageLoopTypedTestParams( + MessageLoop::Type type_in, + TaskSchedulerAvailability task_scheduler_availability_in) { + type = type_in; + task_scheduler_availability = task_scheduler_availability_in; + } + + MessageLoop::Type type; + TaskSchedulerAvailability task_scheduler_availability; +}; + +class MessageLoopTypedTest + : public ::testing::TestWithParam { + 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 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(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"); +} + +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_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); +} + +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 { + 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() { + *was_deleted_ = true; + if (post_on_delete_.get()) + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&RecordDeletionProbe::Run, post_on_delete_)); + } + + scoped_refptr 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); +} + +/* 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); +} + +namespace { + +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(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(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(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; + + 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(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(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(task_index), order.Size()); +} + +// Tests RunLoopQuit works before RunWithID. +TEST_P(MessageLoopTypedTest, RunLoopQuitOrderBefore) { + MessageLoop loop(GetMessageLoopType()); -class TestIOHandler : public MessageLoopForIO::IOHandler { - public: - TestIOHandler(const wchar_t* name, HANDLE signal, bool wait); + TaskList order; - void OnIOCompleted(MessageLoopForIO::IOContext* context, - DWORD bytes_transfered, - DWORD error) override; + RunLoop run_loop; - void Init(); - void WaitForIO(); - OVERLAPPED* context() { return &context_.overlapped; } - DWORD size() { return sizeof(buffer_); } + run_loop.Quit(); - private: - char buffer_[48]; - MessageLoopForIO::IOContext context_; - HANDLE signal_; - win::ScopedHandle file_; - bool wait_; -}; + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&OrderedFunc, &order, 1)); // never runs + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce(&FuncThatQuitsNow)); // never runs -TestIOHandler::TestIOHandler(const wchar_t* name, HANDLE signal, bool wait) - : signal_(signal), wait_(wait) { - memset(buffer_, 0, sizeof(buffer_)); + run_loop.Run(); - file_.Set(CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, NULL)); - EXPECT_TRUE(file_.IsValid()); + ASSERT_EQ(0U, order.Size()); } -void TestIOHandler::Init() { - MessageLoopForIO::current()->RegisterIOHandler(file_.Get(), this); +// Tests RunLoopQuit works during RunWithID. +TEST_P(MessageLoopTypedTest, RunLoopQuitOrderDuring) { + MessageLoop loop(GetMessageLoopType()); - DWORD read; - EXPECT_FALSE(ReadFile(file_.Get(), buffer_, size(), &read, context())); - EXPECT_EQ(static_cast(ERROR_IO_PENDING), GetLastError()); - if (wait_) - WaitForIO(); -} + TaskList order; -void TestIOHandler::OnIOCompleted(MessageLoopForIO::IOContext* context, - DWORD bytes_transfered, DWORD error) { - ASSERT_TRUE(context == &context_); - ASSERT_TRUE(SetEvent(signal_)); -} + RunLoop run_loop; -void TestIOHandler::WaitForIO() { - EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(300, this)); - EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(400, this)); -} + 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 -void RunTest_IOHandler() { - win::ScopedHandle callback_called(CreateEvent(NULL, TRUE, FALSE, NULL)); - ASSERT_TRUE(callback_called.IsValid()); + run_loop.Run(); - const wchar_t* kPipeName = L"\\\\.\\pipe\\iohandler_pipe"; - win::ScopedHandle server( - CreateNamedPipe(kPipeName, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); - ASSERT_TRUE(server.IsValid()); + 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(task_index), order.Size()); +} - Thread thread("IOHandler test"); - Thread::Options options; - options.message_loop_type = MessageLoop::TYPE_IO; - ASSERT_TRUE(thread.StartWithOptions(options)); +// Tests RunLoopQuit works after RunWithID. +TEST_P(MessageLoopTypedTest, RunLoopQuitOrderAfter) { + MessageLoop loop(GetMessageLoopType()); - TestIOHandler handler(kPipeName, callback_called.Get(), false); - thread.task_runner()->PostTask( - FROM_HERE, Bind(&TestIOHandler::Init, Unretained(&handler))); - // Make sure the thread runs and sleeps for lack of work. - PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + TaskList order; - const char buffer[] = "Hello there!"; - DWORD written; - EXPECT_TRUE(WriteFile(server.Get(), buffer, sizeof(buffer), &written, NULL)); + RunLoop run_loop; - DWORD result = WaitForSingleObject(callback_called.Get(), 1000); - EXPECT_EQ(WAIT_OBJECT_0, result); + 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(task_index), order.Size()); +} - thread.Stop(); +// 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(); } -void RunTest_WaitForIO() { - win::ScopedHandle callback1_called( - CreateEvent(NULL, TRUE, FALSE, NULL)); - win::ScopedHandle callback2_called( - CreateEvent(NULL, TRUE, FALSE, NULL)); - ASSERT_TRUE(callback1_called.IsValid()); - ASSERT_TRUE(callback2_called.IsValid()); +TEST_P(MessageLoopTypedTest, NestableTasksAllowedAtTopLevel) { + MessageLoop loop(GetMessageLoopType()); + EXPECT_TRUE(MessageLoopCurrent::Get()->NestableTasksAllowed()); +} - const wchar_t* kPipeName1 = L"\\\\.\\pipe\\iohandler_pipe1"; - const wchar_t* kPipeName2 = L"\\\\.\\pipe\\iohandler_pipe2"; - win::ScopedHandle server1( - CreateNamedPipe(kPipeName1, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); - win::ScopedHandle server2( - CreateNamedPipe(kPipeName2, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL)); - ASSERT_TRUE(server1.IsValid()); - ASSERT_TRUE(server2.IsValid()); +// 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(); +} - Thread thread("IOHandler test"); - Thread::Options options; - options.message_loop_type = MessageLoop::TYPE_IO; - ASSERT_TRUE(thread.StartWithOptions(options)); +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(); +} - 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))); - // 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))); - PlatformThread::Sleep(delay); +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(); +} - // At this time handler1 is waiting to be called, and the thread is waiting - // on the Init method of handler2, filtering only handler2 callbacks. +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(); +} - const char buffer[] = "Hello there!"; - DWORD written; - EXPECT_TRUE(WriteFile(server1.Get(), buffer, sizeof(buffer), &written, NULL)); - PlatformThread::Sleep(2 * delay); - EXPECT_EQ(static_cast(WAIT_TIMEOUT), - WaitForSingleObject(callback1_called.Get(), 0)) - << "handler1 has not been called"; +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); - EXPECT_TRUE(WriteFile(server2.Get(), buffer, sizeof(buffer), &written, NULL)); +#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); - HANDLE objects[2] = { callback1_called.Get(), callback2_called.Get() }; - DWORD result = WaitForMultipleObjects(2, objects, TRUE, 1000); - EXPECT_EQ(WAIT_OBJECT_0, result); + // Post a WM_QUIT message to the current thread. + ::PostQuitMessage(0); - thread.Stop(); + // 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); } -#endif // defined(OS_WIN) - -} // namespace - -//----------------------------------------------------------------------------- -// Each test is run against each type of MessageLoop. That way we are sure -// 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); +TEST_P(MessageLoopTest, WmQuitIsNotIgnoredWithEnableWmQuit) { + MessageLoop loop(MessageLoop::TYPE_UI); + static_cast(&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(); +} -#if defined(OS_WIN) -TEST(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) { +TEST_P(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) { RunTest_PostDelayedTask_SharedTimer_SubPump(); } // This test occasionally hangs. See http://crbug.com/44567. -TEST(MessageLoopTest, DISABLED_RecursiveDenial2) { +TEST_P(MessageLoopTest, DISABLED_RecursiveDenial2) { RunTest_RecursiveDenial2(MessageLoop::TYPE_DEFAULT); RunTest_RecursiveDenial2(MessageLoop::TYPE_UI); RunTest_RecursiveDenial2(MessageLoop::TYPE_IO); } -TEST(MessageLoopTest, RecursiveSupport2) { +TEST_P(MessageLoopTest, RecursiveSupport2) { // This test requires a UI loop. RunTest_RecursiveSupport2(MessageLoop::TYPE_UI); } #endif // defined(OS_WIN) -class DummyTaskObserver : public MessageLoop::TaskObserver { - public: - explicit DummyTaskObserver(int num_tasks) - : num_tasks_started_(0), - num_tasks_processed_(0), - num_tasks_(num_tasks) {} - - ~DummyTaskObserver() override {} - - 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); -}; - -TEST(MessageLoopTest, TaskObserver) { +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 { - -class QuitDelegate : public MessageLoopForIO::Watcher { - public: - void OnFileCanWriteWithoutBlocking(int fd) override { - MessageLoop::current()->QuitWhenIdle(); - } - void OnFileCanReadWithoutBlocking(int fd) override { - MessageLoop::current()->QuitWhenIdle(); - } -}; + constexpr TimeDelta kFastTimer = TimeDelta::FromMilliseconds(5); + constexpr TimeDelta kSlowTimer = TimeDelta::FromMilliseconds(100); -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(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 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 new_runner(new TestSimpleTaskRunner()); scoped_refptr original_runner(loop.task_runner()); loop.SetTaskRunner(new_runner); scoped_refptr 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 slot; + + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + BindOnce(&SequenceLocalStorageSlot::Set, Unretained(&slot), 11)); + + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce( + [](SequenceLocalStorageSlot* 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 slot; + + { + MessageLoop loop; + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + BindOnce(&SequenceLocalStorageSlot::Set, Unretained(&slot), 11)); + + RunLoop().RunUntilIdle(); + EXPECT_EQ(slot.Get(), 11); + } + + MessageLoop loop; + ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, BindOnce( + [](SequenceLocalStorageSlot* 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) {}, + std::make_unique(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 loop = std::make_unique(); + + EXPECT_DCHECK_DEATH({ + PostTaskOnDestroy::PostTaskWithPostingDestructor(1000); + loop.reset(); + }); +} + +TEST(MessageLoopDestructionTest, DestroysFineWithReasonablePostTaskOnDestroy) { + std::unique_ptr loop = std::make_unique(); + + PostTaskOnDestroy::PostTaskWithPostingDestructor(10); + loop.reset(); +} + } // namespace base diff --git a/base/message_loop/message_pump.cc b/base/message_loop/message_pump.cc index 3d85b9b..9076176 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 c53be80..dec0c94 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 cf68270..4104e73 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 + +#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 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(&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 4cd7cd1..dd11adc 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 0000000..6aac1e6 --- /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 0000000..6614f3d --- /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 fd23745..2f1909b 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 607d3c9..512cea6 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(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 { 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 { --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 { 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 helper = new ConcurrentHelper(injector()); + RunLoop run_loop; + scoped_refptr 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 { 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 { 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 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 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 48cb98a..2a595e5 100644 --- a/base/message_loop/message_pump_libevent.cc +++ b/base/message_loop/message_pump_libevent.cc @@ -7,7 +7,7 @@ #include #include -#include +#include #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 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 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 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 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(context); + FdWatchController* controller = static_cast(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 1124560..002c36c 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 + #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 e); - // Used by MessagePumpLibevent to take ownership of event_. - event* ReleaseEvent(); + // Used by MessagePumpLibevent to take ownership of |event_|. + std::unique_ptr 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_; + 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 0000000..1850137 --- /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 0000000..74583d9 --- /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 084cdd3..39b3793 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(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 c356195..1b6d069 100644 --- a/base/metrics/bucket_ranges.h +++ b/base/metrics/bucket_ranges.h @@ -24,6 +24,7 @@ #include +#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 0000000..2707733 --- /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 + +#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(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 Iterator() const override { + return std::make_unique(); + } + bool AddSubtractImpl(SampleCountIterator* iter, Operator op) override { + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(DummyHistogramSamples); +}; + +} // namespace + +// static +DummyHistogram* DummyHistogram::GetInstance() { + static base::NoDestructor 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 DummyHistogram::SnapshotSamples() const { + return std::make_unique(); +} + +std::unique_ptr DummyHistogram::SnapshotDelta() { + return std::make_unique(); +} + +std::unique_ptr DummyHistogram::SnapshotFinalDelta() const { + return std::make_unique(); +} + +} // namespace base diff --git a/base/metrics/dummy_histogram.h b/base/metrics/dummy_histogram.h new file mode 100644 index 0000000..e2cb64e --- /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 + +#include +#include + +#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 SnapshotSamples() const override; + std::unique_ptr SnapshotDelta() override; + std::unique_ptr 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() : 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 6b38d55..ff37880 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 params; @@ -87,11 +89,8 @@ bool PickleFieldTrial(const FieldTrial::State& trial_state, Pickle* pickle) { *trial_state.trial_name, *trial_state.group_name, ¶ms); // 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 entropy_provider) : entropy_provider_(std::move(entropy_provider)), observer_list_(new ObserverListThreadSafe( - ObserverListBase::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)); @@ -676,6 +675,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 params; + if (params_associator->GetFieldTrialParamsWithoutFallback( + *trial.trial_name, *trial.group_name, ¶ms)) { + 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) { @@ -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(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 @@ -951,6 +995,18 @@ void FieldTrialList::RemoveObserver(Observer* observer) { global_->observer_list_->RemoveObserver(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_) @@ -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(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 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(field_trial_handle); +#elif defined(OS_WIN) HANDLE handle = reinterpret_cast(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), guid); } + +#elif defined(OS_POSIX) && !defined(OS_NACL) + +// static +SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata( + int fd, + const std::string& switch_value) { + std::vector 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), + 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 60a6592..ac4ea1c 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 { 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 { //------------------------------------------------------------------------------ // 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 > 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 3bac18d..af76eaf 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 b19c666..b35e2cc 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 0000000..4c6005e --- /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 kParam{ + &kFeature, "Param"}; + +#elif defined(NCTEST_INVALID_PARAM_TYPE) // [r"unsupported FeatureParam<> type"] + +constexpr base::FeatureParam kParam{ + &kFeature, "Param", 1u}; + +#elif defined(NCTEST_ENUM_NULL_OPTIONS) // [r"candidate template ignored: could not match"] + +constexpr base::FeatureParam kParam{ + &kFeature, "Param", FOO, nullptr}; + +#elif defined(NCTEST_ENUM_EMPTY_OPTIONS) // [r"zero-length arrays are not permitted"] + +constexpr base::FeatureParam::Option kParamOptions[] = {}; +constexpr base::FeatureParam 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 54672e6..7dbe737 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 CreateFieldTrial( +scoped_refptr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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()); FieldTrial* trial = FieldTrialList::Find("Some_name"); - ASSERT_NE(static_cast(NULL), trial); + ASSERT_NE(static_cast(nullptr), trial); EXPECT_EQ("Winner", trial->group_name()); EXPECT_EQ("Some_name", trial->trial_name()); trial = FieldTrialList::Find("xxx"); - ASSERT_NE(static_cast(NULL), trial); + ASSERT_NE(static_cast(nullptr), trial); EXPECT_EQ("yyyy", trial->group_name()); EXPECT_EQ("xxx", trial->trial_name()); } @@ -529,7 +538,7 @@ TEST_F(FieldTrialTest, RestoreNotEndingWithSlash) { std::set())); FieldTrial* trial = FieldTrialList::Find("tname"); - ASSERT_NE(static_cast(NULL), trial); + ASSERT_NE(static_cast(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 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())); RunLoop().RunUntilIdle(); @@ -653,12 +662,12 @@ TEST_F(FieldTrialTest, CreateTrialsFromStringWithIgnoredFieldTrials) { EXPECT_TRUE(active_groups.empty()); FieldTrial* trial = FieldTrialList::Find("Foo"); - ASSERT_NE(static_cast(NULL), trial); + ASSERT_NE(static_cast(nullptr), trial); EXPECT_EQ("Foo", trial->trial_name()); EXPECT_EQ("Foo_name", trial->group_name()); trial = FieldTrialList::Find("Bar"); - ASSERT_NE(static_cast(NULL), trial); + ASSERT_NE(static_cast(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(NULL), trial); + ASSERT_NE(static_cast(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 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 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 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 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 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 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 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 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 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 trial = CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group); @@ -993,7 +1026,7 @@ TEST_F(FieldTrialTest, DisabledTrialNotActive) { ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); scoped_refptr 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 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(kBucketCount); - scoped_refptr trial( - new base::FieldTrial("test", kBucketCount, "default", entropy)); + scoped_refptr 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 trial( - new base::FieldTrial("test", 2, "default", kEntropyValue)); + scoped_refptr 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 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 trial = - CreateFieldTrial("Abc", 10, "Default some name", NULL); + CreateFieldTrial("Abc", 10, "Default some name", nullptr); trial->AppendGroup("cba", 10); trial->group(); scoped_refptr trial2 = - CreateFieldTrial("Xyz", 10, "Default xxx", NULL); + CreateFieldTrial("Xyz", 10, "Default xxx", nullptr); trial2->AppendGroup("zyx", 10); trial2->group(); scoped_refptr 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())); @@ -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::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()); + + std::unique_ptr 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 shm(new SharedMemory(handle, true)); + std::unique_ptr 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 shm(new SharedMemory(handle, true)); + std::unique_ptr 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 shm(new SharedMemory(handle, true)); + std::unique_ptr 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 shm(new SharedMemory()); + std::unique_ptr 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 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 params; + params["p1"] = "v1"; + params["p2"] = "v2"; + FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams( + "t1", "g1", params); + EXPECT_EQ( + "", FieldTrialList::AllParamsToString(false, &MockEscapeQueryParamValue)); + + scoped_refptr 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 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 16e36ae..5b29ee1 100644 --- a/base/metrics/histogram.cc +++ b/base/metrics/histogram.cc @@ -9,17 +9,20 @@ #include "base/metrics/histogram.h" +#include #include #include #include #include +#include #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(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 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(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(minimum.InMicroseconds()), + static_cast(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 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(floor(exp(log_next) + 0.5)); + next = static_cast(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(bucket_ranges_->bucket_count()); + return static_cast(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(*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(*maximum - *minimum + 2)) { + check_okay = false; + *bucket_count = static_cast(*maximum - *minimum + 2); + } + + if (!check_okay) { + UmaHistogramSparse("Histogram.BadConstructionArguments", + static_cast(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 Histogram::SnapshotSamples() const { - return SnapshotSampleVector(); + return SnapshotAllSamples(); } std::unique_ptr Histogram::SnapshotDelta() { +#if DCHECK_IS_ON() DCHECK(!final_delta_created_); - - std::unique_ptr 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 snapshot = SnapshotUnloggedSamples(); + unlogged_samples_->Subtract(*snapshot); logged_samples_->Add(*snapshot); + return snapshot; } std::unique_ptr Histogram::SnapshotFinalDelta() const { +#if DCHECK_IS_ON() DCHECK(!final_delta_created_); final_delta_created_ = true; +#endif - std::unique_ptr 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 Histogram::SnapshotSampleVector() const { +std::unique_ptr Histogram::SnapshotAllSamples() const { + std::unique_ptr samples = SnapshotUnloggedSamples(); + samples->Add(*logged_samples_); + return samples; +} + +std::unique_ptr Histogram::SnapshotUnloggedSamples() const { std::unique_ptr 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 snapshot = SnapshotSampleVector(); + std::unique_ptr 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 snapshot = SnapshotSampleVector(); + std::unique_ptr 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 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(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 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(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,18 +1004,87 @@ 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::FactoryGet(name, + minimum, + maximum, + bucket_count, + flags))), + scale_(scale) { + DCHECK(histogram_); + DCHECK_LT(1, scale); + DCHECK_EQ(1, minimum); + CHECK_EQ(static_cast(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(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 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 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 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 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 CustomHistogram::ArrayToCustomRanges( - const Sample* values, uint32_t num_values) { +std::vector CustomHistogram::ArrayToCustomEnumRanges( + base::span values) { std::vector 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 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 a76dd63..bd5a9eb 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 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 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 SnapshotAllSamples() const; + + // Create a copy of unlogged samples. + std::unique_ptr 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 unlogged_samples_; - // Finally, provide the state that changes with the addition of each new - // sample. - std::unique_ptr samples_; - - // Also keep a previous uploaded state for calculating deltas. - std::unique_ptr logged_samples_; + // Accumulation of all samples that have been logged with SnapshotDelta(). + std::unique_ptr 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 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 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 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 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 ArrayToCustomRanges(const Sample* values, - uint32_t num_values); + static std::vector ArrayToCustomEnumRanges( + base::span 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 671cad2..990d9f5 100644 --- a/base/metrics/histogram_base.cc +++ b/base/metrics/histogram_base.cc @@ -7,17 +7,23 @@ #include #include +#include #include #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(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(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(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 buckets(new ListValue()); @@ -117,37 +157,12 @@ void HistogramBase::WriteJSON(std::string* output) const { root.SetDouble("sum", static_cast(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>::Leaky permanent_names; + static LazyInstance::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 4f5ba04..f128ff2 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 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 1eb8fd4..0314ef4 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 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 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 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 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 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::max() / 2; + HistogramBase* histogram = LinearHistogram::FactoryGet( + "TestAddTimeMillisecondsGranularity1", 1, sample_max, 100, 0); + int64_t large_positive = std::numeric_limits::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::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 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::min(); + add_count = 0; + while (large_negative < std::numeric_limits::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::max() / 2; + HistogramBase* histogram = LinearHistogram::FactoryGet( + "TestAddTimeMicrosecondsGranularity1", 1, sample_max, 100, 0); + int64_t large_positive = std::numeric_limits::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::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 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::min(); + add_count = 0; + while (large_negative < std::numeric_limits::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 3e5d154..a74b87f 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* 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(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 3bb04cb..57ebd2c 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* 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 b5fe976..6a5e3f4 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 0000000..31bf219 --- /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 0000000..60c0057 --- /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 +void UmaHistogramEnumeration(const std::string& name, T sample, T enum_size) { + static_assert(std::is_enum::value, + "Non enum passed to UmaHistogramEnumeration"); + DCHECK_LE(static_cast(enum_size), static_cast(INT_MAX)); + DCHECK_LT(static_cast(sample), static_cast(enum_size)); + return UmaHistogramExactLinear(name, static_cast(sample), + static_cast(enum_size)); +} + +// Same as above, but uses T::kMaxValue as the inclusive maximum value of the +// enum. +template +void UmaHistogramEnumeration(const std::string& name, T sample) { + static_assert(std::is_enum::value, + "Non enum passed to UmaHistogramEnumeration"); + DCHECK_LE(static_cast(T::kMaxValue), + static_cast(INT_MAX) - 1); + DCHECK_LE(static_cast(sample), + static_cast(T::kMaxValue)); + return UmaHistogramExactLinear(name, static_cast(sample), + static_cast(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 d39972a..93bd4bd 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. @@ -77,6 +123,20 @@ #define UMA_HISTOGRAM_PERCENTAGE(name, percent_as_int) \ 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 @@ -239,22 +322,6 @@ name, sample, enum_max, \ 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. @@ -305,8 +372,8 @@ // Samples should be one of the std::vector 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. +// CustomHistogram::ArrayToCustomEnumRanges to transform a C-style array of +// valid sample values to a std::vector. #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 c107a47..cc7c76a 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 +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 +struct EnumSizeTraits< + Enum, + std::enable_if_t::value>> { + static constexpr Enum Count() { + return static_cast( + static_cast>(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::value, \ + "|sample| should not be an enum type!"); \ + static_assert(!std::is_enum::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::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::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::value || \ - !std::is_enum::value || \ - std::is_same::type, \ - std::remove_const::type>::value, \ - "|sample| and |boundary| shouldn't be of different enums"); \ + using decayed_sample = std::decay::type; \ + using decayed_boundary = std::decay::type; \ + static_assert(!std::is_enum::value || \ + std::is_enum::value, \ + "Unexpected: |boundary| is enum, but |sample| is not."); \ + static_assert(!std::is_enum::value || \ + !std::is_enum::value || \ + std::is_same::value, \ + "|sample| and |boundary| shouldn't be of different enums"); \ static_assert( \ static_cast(boundary) < \ static_cast( \ @@ -155,6 +225,24 @@ static_cast(boundary), flag); \ } while (0) +#define INTERNAL_HISTOGRAM_SCALED_ENUMERATION_WITH_FLAG(name, sample, count, \ + scale, flag) \ + do { \ + using decayed_sample = std::decay::type; \ + static_assert(std::is_enum::value, \ + "Unexpected: |sample| is not at enum."); \ + constexpr auto boundary = \ + base::internal::EnumSizeTraits::Count(); \ + static_assert( \ + static_cast(boundary) < \ + static_cast( \ + std::numeric_limits::max()), \ + "|boundary| is out of range of HistogramBase::Sample"); \ + INTERNAL_HISTOGRAM_SCALED_EXACT_LINEAR_WITH_FLAG( \ + name, static_cast(sample), count, \ + static_cast(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 7571a9c..c4d333b 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 33a9c6e..3c592b0 100644 --- a/base/metrics/histogram_macros_unittest.cc +++ b/base/metrics/histogram_macros_unittest.cc @@ -37,13 +37,21 @@ TEST(HistogramMacro, UnscopedEnumeration) { TEST(HistogramMacro, ScopedEnumeration) { enum class TestEnum { + 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 3475cd5..6830637 100644 --- a/base/metrics/histogram_samples.cc +++ b/base/metrics/histogram_samples.cc @@ -4,13 +4,29 @@ #include "base/metrics/histogram_samples.h" +#include + #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::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::max() || + count > std::numeric_limits::max() || + bucket > std::numeric_limits::max()) { + return false; + } + bool count_is_negative = count < 0; + uint16_t count16 = static_cast(count_is_negative ? -count : count); + uint16_t bucket16 = static_cast(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 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 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 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 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(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(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 93f6d21..6908873 100644 --- a/base/metrics/histogram_samples.h +++ b/base/metrics/histogram_samples.h @@ -8,6 +8,7 @@ #include #include +#include #include #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 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::max() < + std::numeric_limits::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 0000000..74c743b --- /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 + +#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::max())); + ASSERT_FALSE(sample.Accumulate(9, 1)); + EXPECT_EQ(std::numeric_limits::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 a774ea6..c1b804e 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* 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* 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& 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 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(histogram)->bucket_ranges(); - std::vector 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(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(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 de4a2e1..cf7c149 100644 --- a/base/metrics/histogram_snapshot_manager.h +++ b/base/metrics/histogram_snapshot_manager.h @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -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 - void PrepareDeltas(ForwardHistogramIterator begin, - ForwardHistogramIterator end, + void PrepareDeltas(const std::vector& 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 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 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 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 3c13e1a..1e2c599 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 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& 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& 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& histograms = histogram_flattener_delta_recorder_.GetRecordedDeltaHistogramNames(); diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc index 02ed93b..e516acb 100644 --- a/base/metrics/histogram_unittest.cc +++ b/base/metrics/histogram_unittest.cc @@ -13,11 +13,15 @@ #include #include +#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 { } 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 { GlobalHistogramAllocator::ReleaseForTesting(); } + std::unique_ptr SnapshotAllSamples(Histogram* h) { + return h->SnapshotAllSamples(); + } + const bool use_persistent_histogram_allocator_; std::unique_ptr 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 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::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 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 samples = histogram->SnapshotSampleVector(); + std::unique_ptr 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 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 samples = histogram->SnapshotSampleVector(); + std::unique_ptr 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 snapshot = histogram->SnapshotSampleVector(); + std::unique_ptr 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 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(); + 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 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 5f44b67..cb01c41 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& samples = sample_records_[id]; - samples = MakeUnique(this, id); + samples = std::make_unique(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 PersistentHistogramAllocator::GetHistogram( Reference ref) { @@ -279,23 +283,27 @@ std::unique_ptr 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(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(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(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 PersistentHistogramAllocator::AllocateHistogram( @@ -310,10 +318,8 @@ std::unique_ptr 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 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( - 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( + 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 PersistentHistogramAllocator::AllocateHistogram( histogram_data->bucket_count = static_cast(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 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( - 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(histogram_pointer)); - } - } - return histogram_pointer; -} - std::unique_ptr 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 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( - histogram_data.ranges_ref, kTypeIdRangesArray, + histogram_ranges_ref, kTypeIdRangesArray, PersistentMemoryAllocator::kSizeAny); const uint32_t max_buckets = std::numeric_limits::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 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 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( - 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 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 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( + new GlobalHistogramAllocator(std::make_unique( base, size, page_size, id, name, false)))); } @@ -709,7 +671,7 @@ void GlobalHistogramAllocator::CreateWithLocalMemory( uint64_t id, StringPiece name) { Set(WrapUnique(new GlobalHistogramAllocator( - MakeUnique(size, id, name)))); + std::make_unique(size, id, name)))); } #if !defined(OS_NACL) @@ -726,20 +688,20 @@ bool GlobalHistogramAllocator::CreateWithFile( std::unique_ptr mmfile(new MemoryMappedFile()); if (exists) { + size = saturated_cast(file.GetLength()); mmfile->Initialize(std::move(file), MemoryMappedFile::READ_WRITE); } else { - mmfile->Initialize(std::move(file), {0, static_cast(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( - std::move(mmfile), size, id, name, false)))); + Set(WrapUnique(new GlobalHistogramAllocator( + std::make_unique(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(name.length()), name.data(), + static_cast(stamp.ToTimeT()), static_cast(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 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(stamp)); + } + + if (out_pid) { + int64_t pid; + if (!HexStringToInt64(parts[2], &pid)) + return false; + *out_pid = static_cast(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( + Set(WrapUnique(new GlobalHistogramAllocator( + std::make_unique( 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(allocator.release())); size_t existing = StatisticsRecorder::GetHistogramCount(); @@ -819,7 +902,7 @@ void GlobalHistogramAllocator::Set( // static GlobalHistogramAllocator* GlobalHistogramAllocator::Get() { return reinterpret_cast( - 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()) != 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 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 851d7ef..395511f 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 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 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 df250a3..7e07386 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 recovered; - PersistentHistogramAllocator recovery(MakeUnique( - allocator_memory_.get(), kAllocatorMemorySize, 0, 0, "", false)); + PersistentHistogramAllocator recovery( + std::make_unique( + 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(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(temp_size), file.GetLength()); + + char buffer[256]; + for (size_t pos = 0; pos < temp_size; pos += sizeof(buffer)) { + ASSERT_EQ(static_cast(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( - const_cast(new_allocator->memory_allocator()->data()), - new_allocator->memory_allocator()->size(), 0, 0, "", false)); + PersistentHistogramAllocator recovery1( + std::make_unique( + const_cast(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( - const_cast(new_allocator->memory_allocator()->data()), - new_allocator->memory_allocator()->size(), 0, 0, "", false)); + PersistentHistogramAllocator recovery2( + std::make_unique( + const_cast(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(histogram1) + ->bucket_ranges() + ->persistent_reference(); + ASSERT_NE(0U, ranges_ref); + EXPECT_EQ(ranges_ref, static_cast(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(ref1, 0, kRangesRefIndex + 1); + uint32_t* data2 = + allocator_->GetAsArray(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 0000000..e2a56d7 --- /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(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 0000000..397236d --- /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 0000000..0b9b1ce --- /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 + +#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( + 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 d381d87..9b18a00 100644 --- a/base/metrics/persistent_memory_allocator.cc +++ b/base/metrics/persistent_memory_allocator.cc @@ -8,17 +8,21 @@ #include #if defined(OS_WIN) +#include #include "winbase.h" -#elif defined(OS_POSIX) +#elif defined(OS_POSIX) || defined(OS_FUCHSIA) #include #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(size)), mem_page_(static_cast((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(block) + size; + volatile char* mem_begin = reinterpret_cast( + (reinterpret_cast(block) + sizeof(BlockHeader) + + (vm_page_size_ - 1)) & + ~static_cast(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(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(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*>(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*>(ref), + type, + size, + offset, + make_iterable) {} + +DelayedPersistentAllocation::DelayedPersistentAllocation( + PersistentMemoryAllocator* allocator, + std::atomic* 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* ref, + uint32_t type, + size_t size, + size_t offset, + bool make_iterable) + : allocator_(allocator), + type_(type), + size_(checked_cast(size)), + offset_(checked_cast(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(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 94a7744..978a362 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(GetBlockData(ref, T::kPersistentTypeId, size)); if (!mem) return nullptr; - DCHECK_EQ(0U, reinterpret_cast(mem) & (ALIGNOF(T) - 1)); + DCHECK_EQ(0U, reinterpret_cast(mem) & (alignof(T) - 1)); return new (mem) T(); } template @@ -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(mem) & (ALIGNOF(T) - 1)); + DCHECK_EQ(0U, reinterpret_cast(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 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 + // 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* ref, + uint32_t type, + size_t size, + bool make_iterable); + DelayedPersistentAllocation(PersistentMemoryAllocator* allocator, + std::atomic* 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* 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 c3027ec..75e4faa 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(length))); + } -namespace base { + int64_t actual_length; + DCHECK(GetFileSize(path, &actual_length)); + DCHECK_EQ(length, static_cast(actual_length)); +} + +} // namespace typedef PersistentMemoryAllocator::Reference Reference; @@ -472,6 +485,47 @@ TEST_F(PersistentMemoryAllocatorTest, IteratorParallelismTest) { #endif } +TEST_F(PersistentMemoryAllocatorTest, DelayedAllocationTest) { + std::atomic 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(mem2a) + 5, + reinterpret_cast(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(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 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(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 51cc0c7..f38b9d1 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(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(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(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(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 d50ab99..b25f582 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 CreateHistogramAllocator( size_t bytes) { - return MakeUnique( - MakeUnique(bytes, 0, "")); + return std::make_unique( + std::make_unique(bytes, 0, "")); } std::unique_ptr DuplicateHistogramAllocator( PersistentHistogramAllocator* original) { - return MakeUnique( - MakeUnique( + return std::make_unique( + std::make_unique( const_cast(original->data()), original->length(), 0, original->Id(), original->Name(), false)); } @@ -164,14 +164,14 @@ TEST(PersistentSampleMapIteratorTest, IterateTest) { std::unique_ptr 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 0000000..75bc336 --- /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 + +#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 8abd01e..c6dce29 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(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(meta()); +} void SampleMap::Accumulate(Sample value, Count count) { sample_counts_[value] += count; - IncreaseSum(static_cast(count) * value); - IncreaseRedundantCount(count); + IncreaseSumAndCount(strict_cast(count) * value, count); } Count SampleMap::GetCount(Sample value) const { @@ -109,11 +111,11 @@ std::unique_ptr 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(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 91a9dcf..83db56f 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 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 477b8af..cf8634e 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(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(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 SampleVector::Iterator() const { - return std::unique_ptr( - new SampleVectorIterator(counts_, counts_size_, bucket_ranges_)); +std::unique_ptr SampleVectorBase::Iterator() const { + // Handle the single-sample case. + SingleSample sample = single_sample().Load(); + if (sample.count != 0) { + return std::make_unique( + 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(counts(), counts_size(), + bucket_ranges_); + } + + // And the no-value case. + return std::make_unique(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::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(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(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(mem); +} + SampleVectorIterator::SampleVectorIterator( const std::vector* 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(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 ee26c52..278272d 100644 --- a/base/metrics/sample_vector.h +++ b/base/metrics/sample_vector.h @@ -14,28 +14,27 @@ #include #include +#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( + subtle::Acquire_Load(&counts_)); + } + + const HistogramBase::AtomicCount* counts() const { + return reinterpret_cast( + subtle::Acquire_Load(&counts_)); + } + + void set_counts(const HistogramBase::AtomicCount* counts) const { + subtle::Release_Store(&counts_, reinterpret_cast(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 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 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* 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 2d77d23..4921802 100644 --- a/base/metrics/sample_vector_unittest.cc +++ b/base/metrics/sample_vector_unittest.cc @@ -7,18 +7,29 @@ #include #include +#include #include #include #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 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 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 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 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 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 0000000..57c1c8f --- /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 factory) { + DCHECK(!g_factory); + g_factory = factory.release(); +} + +// static +void SingleSampleMetricsFactory::DeleteFactoryForTesting() { + DCHECK(g_factory); + delete g_factory; + g_factory = nullptr; +} + +std::unique_ptr +DefaultSingleSampleMetricsFactory::CreateCustomCountsMetric( + const std::string& histogram_name, + HistogramBase::Sample min, + HistogramBase::Sample max, + uint32_t bucket_count) { + return std::make_unique( + 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 0000000..b966cb1 --- /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 + +#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 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 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 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 0000000..d4d5913 --- /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(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 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 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 metric = + factory->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount); + std::unique_ptr metric2 = + factory->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount); + const char kMetricName2[] = "Single.Sample.Metric.2"; + std::unique_ptr 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 415d7f9..30175a0 100644 --- a/base/metrics/sparse_histogram.cc +++ b/base/metrics/sparse_histogram.cc @@ -7,6 +7,7 @@ #include #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 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 SparseHistogram::SnapshotSamples() const { std::unique_ptr 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 SparseHistogram::SnapshotDelta() { std::unique_ptr 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 SparseHistogram::SnapshotFinalDelta() const { std::unique_ptr 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 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 97709ba..913762c 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 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 samples_; + std::unique_ptr unlogged_samples_; std::unique_ptr logged_samples_; DISALLOW_COPY_AND_ASSIGN(SparseHistogram); diff --git a/base/metrics/sparse_histogram_unittest.cc b/base/metrics/sparse_histogram_unittest.cc index f4a7c94..72dd905 100644 --- a/base/metrics/sparse_histogram_unittest.cc +++ b/base/metrics/sparse_histogram_unittest.cc @@ -8,14 +8,16 @@ #include #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 { } 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 { GlobalHistogramAllocator::ReleaseForTesting(); } - std::unique_ptr NewSparseHistogram(const std::string& name) { + std::unique_ptr 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(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 histogram(NewSparseHistogram("Sparse")); + std::unique_ptr 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 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 snapshot = histogram->SnapshotSamples(); + std::unique_ptr 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 ba2101b..28773a1 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::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::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& 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 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 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* 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(); - 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("


"); + *output += "


"; } } // 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* 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 StatisticsRecorder::GetBucketRanges() { + std::vector 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& provider : *providers_) { + for (const WeakPtr& 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(base)->bucket_ranges()->set_persistent_reference(0); + } + + top_->histograms_.erase(found); } // static std::unique_ptr 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 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 histograms_deleter; - std::unique_ptr callbacks_deleter; - std::unique_ptr ranges_deleter; - std::unique_ptr 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::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 55be86a..08137a8 100644 --- a/base/metrics/statistics_recorder.h +++ b/base/metrics/statistics_recorder.h @@ -12,10 +12,10 @@ #include -#include -#include #include #include +#include +#include #include #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 HistogramMap; typedef std::vector Histograms; - typedef std::vector> 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& 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* 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 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 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 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 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> HistogramProviders; + + typedef std::unordered_map + HistogramMap; + // We keep a map of callbacks to histograms, so that as histograms are // created, we can set the callback properly. - typedef std::map CallbackMap; + typedef std::unordered_map 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*> RangesMap; + struct BucketRangesHash { + size_t operator()(const BucketRanges* a) const; + }; - friend struct LazyInstanceTraitsBase; - friend class StatisticsRecorderTest; + struct BucketRangesEqual { + bool operator()(const BucketRanges* a, const BucketRanges* b) const; + }; + + typedef std:: + unordered_set + 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 existing_histograms_; - std::unique_ptr existing_callbacks_; - std::unique_ptr existing_ranges_; - std::unique_ptr 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::Leaky lock_; + // + // Precondition: The global lock is already acquired. + static void InitLogOnShutdownWhileLocked(); + + HistogramMap histograms_; + CallbackMap callbacks_; + RangesMap ranges_; + HistogramProviders providers_; + std::unique_ptr record_checker_; + + // Previous global recorder that existed when this one was created. + StatisticsRecorder* previous_ = nullptr; + + // Global lock for internal synchronization. + static LazyInstance::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 48b6df3..a65283c 100644 --- a/base/metrics/statistics_recorder_unittest.cc +++ b/base/metrics/statistics_recorder_unittest.cc @@ -7,16 +7,20 @@ #include #include +#include #include #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 { 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 { 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 { 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 registered_ranges; - - StatisticsRecorder::GetHistograms(®istered_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(®istered_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(®istered_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(®istered_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(®istered_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(®istered_histograms); - EXPECT_EQ(0u, registered_histograms.size()); + EXPECT_THAT(StatisticsRecorder::GetHistograms(), IsEmpty()); // Register the Histogram. - EXPECT_EQ(histogram, - StatisticsRecorder::RegisterOrDeleteDuplicate(histogram)); - StatisticsRecorder::GetHistograms(®istered_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(®istered_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(®istered_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(®istered_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(®istered_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(®istered_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(®istered_histograms); - EXPECT_EQ(3u, registered_histograms.size()); + EXPECT_THAT(StatisticsRecorder::GetHistograms(), + UnorderedElementsAre(histogram1, histogram3, histogram4)); // Create a CustomHistogram. std::vector 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(®istered_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(®istered_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(®istered_histograms); - EXPECT_EQ(3u, registered_histograms.size()); + EXPECT_THAT(StatisticsRecorder::GetHistograms(), SizeIs(3)); } TEST_P(StatisticsRecorderTest, BucketRangesSharing) { - std::vector 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 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(); + 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 65ac918..9fcc9e8 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 3eca3dd..454ed83 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 0000000..72012a3 --- /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 e2b9ca7..04356d9 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 3459716..19ff7a4 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 0000000..aabc6e6 --- /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 +#include + +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 s(5, '-'); +// return *s; +// } +// +// More complex initialization with a lambda: +// const std::string& GetSessionNonce() { +// static const base::NoDestructor nonce([] { +// std::string s(16); +// crypto::RandString(s.data(), s.size()); +// return s; +// }()); +// return *nonce; +// } +// +// NoDestructor 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 over: +// - The CR_DEFINE_STATIC_LOCAL() helper macro. +// - A function scoped static T* or T& that is dynamically initialized. +// - A global base::LazyInstance. +// +// Note that since the destructor is never run, this *will* leak memory if used +// as a stack or member variable. Furthermore, a NoDestructor should never +// have global scope as that may require a static initializer. +template +class NoDestructor { + public: + // Not constexpr; just write static constexpr T x = ...; if the value should + // be a constexpr. + template + explicit NoDestructor(Args&&... args) { + new (storage_) T(std::forward(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(storage_); } + T* get() { return reinterpret_cast(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> 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(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 0000000..8f9d4a4 --- /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 +#include + +#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 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 test_forwarding( + copy_only, std::move(move_only)); +} + +TEST(NoDestructorTest, Accessors) { + static NoDestructor 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> 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 0000000..0bb8dd1 --- /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 0000000..896b124 --- /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(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(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(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); +``` + +### 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 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`. + +## 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 0000000..ede3344 --- /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 + +#include +#include + +#include "base/numerics/checked_math_impl.h" + +namespace base { +namespace internal { + +template +class CheckedNumeric { + static_assert(std::is_arithmetic::value, + "CheckedNumeric: T must be a numeric type."); + + public: + using type = T; + + constexpr CheckedNumeric() = default; + + // Copy constructor. + template + constexpr CheckedNumeric(const CheckedNumeric& rhs) + : state_(rhs.state_.value(), rhs.IsValid()) {} + + template + friend class CheckedNumeric; + + // This is not an explicit constructor because we implicitly upgrade regular + // numerics to CheckedNumerics to make them easier to use. + template + constexpr CheckedNumeric(Src value) // NOLINT(runtime/explicit) + : state_(value) { + static_assert(std::is_arithmetic::value, "Argument must be numeric."); + } + + // This is not an explicit constructor because we want a seamless conversion + // from StrictNumeric types. + template + constexpr CheckedNumeric( + StrictNumeric value) // NOLINT(runtime/explicit) + : state_(static_cast(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 + constexpr bool IsValid() const { + return state_.is_valid() && + IsValueInRangeForNumericType(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 +#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()) + ? ((*result = static_cast(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 + constexpr StrictNumeric ValueOrDie() const { + return BASE_NUMERICS_LIKELY(IsValid()) + ? static_cast(state_.value()) + : CheckHandler::template HandleFailure(); + } + + // 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 + constexpr StrictNumeric ValueOrDefault(const Src default_value) const { + return BASE_NUMERICS_LIKELY(IsValid()) + ? static_cast(state_.value()) + : checked_cast(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 + constexpr CheckedNumeric::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 + friend U GetNumericValueForTest(const CheckedNumeric& src); + + // Prototypes for the supported arithmetic operator overloads. + template + constexpr CheckedNumeric& operator+=(const Src rhs); + template + constexpr CheckedNumeric& operator-=(const Src rhs); + template + constexpr CheckedNumeric& operator*=(const Src rhs); + template + constexpr CheckedNumeric& operator/=(const Src rhs); + template + constexpr CheckedNumeric& operator%=(const Src rhs); + template + constexpr CheckedNumeric& operator<<=(const Src rhs); + template + constexpr CheckedNumeric& operator>>=(const Src rhs); + template + constexpr CheckedNumeric& operator&=(const Src rhs); + template + constexpr CheckedNumeric& operator|=(const Src rhs); + template + 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::value || + std::is_floating_point::value + ? CheckedNumeric( + NegateWrapper(state_.value()), + IsValid() && (!std::is_signed::value || + std::is_floating_point::value || + NegateWrapper(state_.value()) != + std::numeric_limits::lowest())) + : FastRuntimeNegate(); + } + + constexpr CheckedNumeric operator~() const { + return CheckedNumeric( + InvertWrapper(state_.value()), IsValid()); + } + + constexpr CheckedNumeric Abs() const { + return !IsValueNegative(state_.value()) ? *this : -*this; + } + + template + constexpr CheckedNumeric::type> Max( + const U rhs) const { + using R = typename UnderlyingType::type; + using result_type = typename MathWrapper::type; + // TODO(jschuh): This can be converted to the MathOp version and remain + // constexpr once we have C++14 support. + return CheckedNumeric( + static_cast( + IsGreater::Test(state_.value(), Wrapper::value(rhs)) + ? state_.value() + : Wrapper::value(rhs)), + state_.is_valid() && Wrapper::is_valid(rhs)); + } + + template + constexpr CheckedNumeric::type> Min( + const U rhs) const { + using R = typename UnderlyingType::type; + using result_type = typename MathWrapper::type; + // TODO(jschuh): This can be converted to the MathOp version and remain + // constexpr once we have C++14 support. + return CheckedNumeric( + static_cast( + IsLess::Test(state_.value(), Wrapper::value(rhs)) + ? state_.value() + : Wrapper::value(rhs)), + state_.is_valid() && Wrapper::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::type> + UnsignedAbs() const { + return CheckedNumeric::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