diff options
author | Patrick Rohr <prohr@google.com> | 2023-05-30 14:46:30 -0700 |
---|---|---|
committer | Patrick Rohr <prohr@google.com> | 2023-05-30 14:46:30 -0700 |
commit | 6ebb9017cd1a36fa33f054df1886756ac6de6953 (patch) | |
tree | 497480d492f1db4ca4db6d63e9173903f5de1770 /base | |
parent | 97ffc279ed0a0b52e81b26e803a265c574db2e82 (diff) | |
parent | 6e619ff2daf1f025aed9c3b67a7492b4b858f981 (diff) | |
download | cronet-6ebb9017cd1a36fa33f054df1886756ac6de6953.tar.gz |
Merge branch 'upstream-import' into upstream-staging
Update Cronet to version 114.0.5735.53.
Test: none
Change-Id: I0383be13fece212e77e387cef8f4dcf886ed579d
Diffstat (limited to 'base')
123 files changed, 3849 insertions, 1261 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn index 467f64fe8..b5f3e520b 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -480,6 +480,8 @@ component("base") { "metrics/dummy_histogram.h", "metrics/field_trial.cc", "metrics/field_trial.h", + "metrics/field_trial_list_including_low_anonymity.cc", + "metrics/field_trial_list_including_low_anonymity.h", "metrics/field_trial_param_associator.cc", "metrics/field_trial_param_associator.h", "metrics/field_trial_params.cc", @@ -626,8 +628,6 @@ component("base") { "sequence_token.h", "state_transitions.h", "stl_util.h", - "strings/abseil_string_conversions.cc", - "strings/abseil_string_conversions.h", "strings/abseil_string_number_conversions.cc", "strings/abseil_string_number_conversions.h", "strings/escape.cc", @@ -661,6 +661,7 @@ component("base") { "strings/stringprintf.cc", "strings/stringprintf.h", "strings/sys_string_conversions.h", + "strings/to_string.h", "strings/utf_offset_string_conversions.cc", "strings/utf_offset_string_conversions.h", "strings/utf_string_conversion_utils.cc", @@ -934,6 +935,7 @@ component("base") { "types/optional_util.h", "types/pass_key.h", "types/strong_alias.h", + "types/supports_ostream_operator.h", "types/token_type.h", "types/variant_util.h", "unguessable_token.cc", @@ -2157,6 +2159,7 @@ component("base") { "ios/sim_header_shims.h", "mac/mach_port_rendezvous.cc", "mac/mach_port_rendezvous.h", + "process/kill_mac.cc", "process/launch_mac.cc", "process/memory_mac.mm", "process/port_provider_mac.cc", @@ -2164,6 +2167,7 @@ component("base") { "process/process_handle_mac.cc", "process/process_iterator_ios.mm", "process/process_mac.cc", + "process/process_metrics_posix.cc", "process/process_posix.cc", "sync_socket_posix.cc", "synchronization/waitable_event_watcher_mac.cc", @@ -2779,6 +2783,10 @@ test("base_perftests") { # build, only do stack trace perftest for unofficial build sources += [ "debug/stack_trace_perftest.cc" ] } + + if (build_allocation_stack_trace_recorder) { + sources += [ "debug/allocation_trace_perftest.cc" ] + } } test("base_i18n_perftests") { @@ -2952,6 +2960,7 @@ if (is_apple) { sources = [ "ios/crb_protocol_observers_unittest.mm", "mac/bind_objc_block_unittest_arc.mm", + "mac/scoped_nsobject_unittest_arc.mm", ] configs += [ "//build/config/compiler:enable_arc" ] deps = [ @@ -3255,7 +3264,6 @@ test("base_unittests") { "sequence_token_unittest.cc", "state_transitions_unittest.cc", "stl_util_unittest.cc", - "strings/abseil_string_conversions_unittest.cc", "strings/abseil_string_number_conversions_unittest.cc", "strings/escape_unittest.cc", "strings/no_trigraphs_unittest.cc", @@ -3270,6 +3278,7 @@ test("base_unittests") { "strings/stringize_macros_unittest.cc", "strings/stringprintf_unittest.cc", "strings/sys_string_conversions_unittest.cc", + "strings/to_string_test.cc", "strings/utf_offset_string_conversions_unittest.cc", "strings/utf_string_conversions_unittest.cc", "substring_set_matcher/string_pattern_unittest.cc", @@ -3403,6 +3412,7 @@ test("base_unittests") { "types/optional_util_unittest.cc", "types/pass_key_unittest.cc", "types/strong_alias_unittest.cc", + "types/supports_ostream_operator_test.cc", "types/token_type_unittest.cc", "types/variant_util_unittest.cc", "unguessable_token_unittest.cc", @@ -4026,8 +4036,6 @@ if (is_win && toolchain_has_rust) { "//testing/gtest", ] data_deps = [ "//build/rust/tests/test_control_flow_guard" ] - cfg_exe = rebase_path("${root_out_dir}/test_control_flow_guard.exe") - defines = [ "RUST_CFG_WIN_UNITTEST_PATH_TO_RUST_EXE=\"${cfg_exe}\"" ] sources = [ "memory/rust_cfg_win_unittest.cc" ] } } @@ -4252,7 +4260,6 @@ if (is_android) { "android/java/src/org/chromium/base/BaseFeatureList.java", "android/java/src/org/chromium/base/BuildInfo.java", "android/java/src/org/chromium/base/BundleUtils.java", - "android/java/src/org/chromium/base/ByteArrayGenerator.java", "android/java/src/org/chromium/base/Callback.java", "android/java/src/org/chromium/base/CallbackController.java", "android/java/src/org/chromium/base/CollectionUtil.java", @@ -4290,6 +4297,7 @@ if (is_android) { "android/java/src/org/chromium/base/Promise.java", "android/java/src/org/chromium/base/RadioUtils.java", "android/java/src/org/chromium/base/RequiredCallback.java", + "android/java/src/org/chromium/base/ResettersForTesting.java", "android/java/src/org/chromium/base/StreamUtil.java", "android/java/src/org/chromium/base/StrictModeContext.java", "android/java/src/org/chromium/base/SysUtils.java", @@ -4500,6 +4508,7 @@ if (is_android) { "test/android/javatests/src/org/chromium/base/test/LoadNative.java", "test/android/javatests/src/org/chromium/base/test/MockitoErrorHandler.java", "test/android/javatests/src/org/chromium/base/test/ResetCachedFlagValuesTestHook.java", + "test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java", "test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java", "test/android/javatests/src/org/chromium/base/test/TestListInstrumentationRunListener.java", "test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java", @@ -4645,6 +4654,7 @@ if (is_android) { "android/junit/src/org/chromium/base/PathUtilsTest.java", "android/junit/src/org/chromium/base/PiiEliderTest.java", "android/junit/src/org/chromium/base/PromiseTest.java", + "android/junit/src/org/chromium/base/ResettersForTestingTest.java", "android/junit/src/org/chromium/base/TimeUtilsTest.java", "android/junit/src/org/chromium/base/TraceEventTest.java", "android/junit/src/org/chromium/base/UnownedUserDataHostTest.java", diff --git a/base/allocator/dispatcher/tls.cc b/base/allocator/dispatcher/tls.cc index c33ac1f8c..652481067 100644 --- a/base/allocator/dispatcher/tls.cc +++ b/base/allocator/dispatcher/tls.cc @@ -9,14 +9,30 @@ #include "base/check.h" #include "base/dcheck_is_on.h" #include "base/immediate_crash.h" +#include "build/build_config.h" #include <sys/mman.h> +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) +#include <sys/prctl.h> +#endif + namespace base::allocator::dispatcher::internal { void* MMapAllocator::AllocateMemory(size_t size_in_bytes) { void* const mmap_res = mmap(nullptr, size_in_bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) +#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME) + if (mmap_res != MAP_FAILED) { + // Allow the anonymous memory region allocated by mmap(MAP_ANONYMOUS) to + // be identified in /proc/$PID/smaps. This helps improve visibility into + // Chromium's memory usage on Android. + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, mmap_res, size_in_bytes, + "tls-mmap-allocator"); + } +#endif +#endif return (mmap_res != MAP_FAILED) ? mmap_res : nullptr; } @@ -81,4 +97,4 @@ bool PThreadTLSSystem::SetThreadSpecificData(void* data) { } // namespace base::allocator::dispatcher::internal -#endif // USE_LOCAL_TLS_EMULATION()
\ No newline at end of file +#endif // USE_LOCAL_TLS_EMULATION() diff --git a/base/allocator/partition_alloc_features.cc b/base/allocator/partition_alloc_features.cc index 8cae69412..425f428b3 100644 --- a/base/allocator/partition_alloc_features.cc +++ b/base/allocator/partition_alloc_features.cc @@ -162,11 +162,18 @@ const base::FeatureParam<bool> kBackupRefPtrAsanEnableExtractionCheckParam{ const base::FeatureParam<bool> kBackupRefPtrAsanEnableInstantiationCheckParam{ &kPartitionAllocBackupRefPtr, "asan-enable-instantiation-check", true}; -// If enabled, switches the bucket distribution to an alternate one. Only one of -// these features may b e enabled at a time. +// If enabled, switches the bucket distribution to an alternate one. +// +// We enable this by default everywhere except for 32-bit Android, since we saw +// regressions there. BASE_FEATURE(kPartitionAllocUseAlternateDistribution, "PartitionAllocUseAlternateDistribution", - FEATURE_DISABLED_BY_DEFAULT); +#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS) + FEATURE_DISABLED_BY_DEFAULT +#else + FEATURE_ENABLED_BY_DEFAULT +#endif // BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS) +); const base::FeatureParam<AlternateBucketDistributionMode>::Option kPartitionAllocAlternateDistributionOption[] = { {AlternateBucketDistributionMode::kDefault, "default"}, diff --git a/base/allocator/partition_alloc_support.cc b/base/allocator/partition_alloc_support.cc index 4a48f7678..d2d56a9ad 100644 --- a/base/allocator/partition_alloc_support.cc +++ b/base/allocator/partition_alloc_support.cc @@ -39,6 +39,7 @@ #include "base/strings/string_piece.h" #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" +#include "base/system/sys_info.h" #include "base/task/single_thread_task_runner.h" #include "base/thread_annotations.h" #include "base/threading/platform_thread.h" @@ -1077,6 +1078,13 @@ void PartitionAllocSupport::ReconfigureAfterFeatureListInit( #endif // BUILDFLAG(USE_ASAN_BACKUP_REF_PTR) #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) + // No specified type means we are in the browser. + auto bucket_distribution = + process_type == "" + ? base::features::kPartitionAllocAlternateBucketDistributionParam + .Get() + : base::features::AlternateBucketDistributionMode::kDefault; + allocator_shim::ConfigurePartitions( allocator_shim::EnableBrp(brp_config.enable_brp), allocator_shim::EnableBrpZapping(brp_config.enable_brp_zapping), @@ -1086,9 +1094,7 @@ void PartitionAllocSupport::ReconfigureAfterFeatureListInit( allocator_shim::UseDedicatedAlignedPartition( brp_config.use_dedicated_aligned_partition), allocator_shim::AddDummyRefCount(brp_config.add_dummy_ref_count), - allocator_shim::AlternateBucketDistribution( - base::features::kPartitionAllocAlternateBucketDistributionParam - .Get())); + allocator_shim::AlternateBucketDistribution(bucket_distribution)); #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) // If BRP is not enabled, check if any of PCScan flags is enabled. @@ -1194,7 +1200,7 @@ void PartitionAllocSupport::ReconfigureAfterTaskRunnerInit( #if BUILDFLAG(IS_ANDROID) // Lower thread cache limits to avoid stranding too much memory in the caches. - if (base::SysInfo::IsLowEndDevice()) { + if (base::SysInfo::IsLowEndDeviceOrPartialLowEndModeEnabled()) { ::partition_alloc::ThreadCacheRegistry::Instance().SetThreadCacheMultiplier( ::partition_alloc::ThreadCache::kDefaultMultiplier / 2.); } diff --git a/base/allocator/partition_allocator/BUILD.gn b/base/allocator/partition_allocator/BUILD.gn index 6e79959df..4d64e3fdb 100644 --- a/base/allocator/partition_allocator/BUILD.gn +++ b/base/allocator/partition_allocator/BUILD.gn @@ -427,6 +427,7 @@ buildflag_header("partition_alloc_buildflags") { "ENABLE_BACKUP_REF_PTR_SUPPORT=$enable_backup_ref_ptr_support", "ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=$enable_backup_ref_ptr_slow_checks", "ENABLE_BACKUP_REF_PTR_FEATURE_FLAG=$enable_backup_ref_ptr_feature_flag", + "ENABLE_RAW_PTR_EXPERIMENTAL=$enable_raw_ptr_experimental", "ENABLE_DANGLING_RAW_PTR_CHECKS=$enable_dangling_raw_ptr_checks", "ENABLE_DANGLING_RAW_PTR_FEATURE_FLAG=$enable_dangling_raw_ptr_feature_flag", "ENABLE_DANGLING_RAW_PTR_PERF_EXPERIMENT=$enable_dangling_raw_ptr_perf_experiment", diff --git a/base/allocator/partition_allocator/page_allocator_internals_posix.h b/base/allocator/partition_allocator/page_allocator_internals_posix.h index c7ab6de0b..30009c960 100644 --- a/base/allocator/partition_allocator/page_allocator_internals_posix.h +++ b/base/allocator/partition_allocator/page_allocator_internals_posix.h @@ -35,7 +35,7 @@ #include <Security/Security.h> #include <mach/mach.h> #endif -#if BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) #include <sys/prctl.h> #endif #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) @@ -62,7 +62,8 @@ namespace partition_alloc::internal { namespace { -#if BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) +#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME) const char* PageTagToName(PageTag tag) { // Important: All the names should be string literals. As per prctl.h in // //third_party/android_ndk the kernel keeps a pointer to the name instead @@ -84,6 +85,7 @@ const char* PageTagToName(PageTag tag) { return ""; } } +#endif #endif // BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_MAC) @@ -195,15 +197,17 @@ uintptr_t SystemAllocPagesInternal(uintptr_t hint, ret = nullptr; } -#if BUILDFLAG(IS_ANDROID) - // On Android, anonymous mappings can have a name attached to them. This is - // useful for debugging, and double-checking memory attribution. +#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) +#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME) + // On Android and Linux, anonymous mappings can have a name attached to them. + // This is useful for debugging, and double-checking memory attribution. if (ret) { // No error checking on purpose, testing only. prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ret, length, PageTagToName(page_tag)); } #endif +#endif return reinterpret_cast<uintptr_t>(ret); } diff --git a/base/allocator/partition_allocator/partition_alloc.gni b/base/allocator/partition_allocator/partition_alloc.gni index 7a431fd46..558c3820c 100644 --- a/base/allocator/partition_allocator/partition_alloc.gni +++ b/base/allocator/partition_allocator/partition_alloc.gni @@ -15,7 +15,8 @@ if (is_apple) { if (is_nacl) { # NaCl targets don't use 64-bit pointers. has_64_bit_pointers = false -} else if (current_cpu == "x64" || current_cpu == "arm64") { +} else if (current_cpu == "x64" || current_cpu == "arm64" || + current_cpu == "loong64") { has_64_bit_pointers = true } else if (current_cpu == "x86" || current_cpu == "arm") { has_64_bit_pointers = false @@ -94,6 +95,20 @@ declare_args() { force_enable_raw_ptr_exclusion = false } +declare_args() { + # Determines whether `raw_ptr_experimental<T>` is an alias for + # `raw_ptr<T>` or `T*` (true raw pointer). + # + # Members rewritten as `raw_ptr_experimental` rely on this as an + # escape hatch to degrade to a `T*` if `raw_ptr` performance proves + # problematic. Defaults to match standard `raw_ptr` support. + # + # One side effect of this is that `raw_ptr_experimental<T> foo_` must + # not use `foo_.get()`; this is incoherent when `foo_` is a `T*`. + # Use `base::to_address()` instead. + enable_raw_ptr_experimental = enable_backup_ref_ptr_support +} + assert(!enable_pointer_compression_support || glue_core_pools, "Pointer compression relies on core pools being contiguous.") @@ -195,6 +210,7 @@ if (is_nacl) { if (!use_partition_alloc) { use_partition_alloc_as_malloc = false enable_backup_ref_ptr_support = false + enable_raw_ptr_experimental = false use_asan_backup_ref_ptr = false use_asan_unowned_ptr = false use_hookable_raw_ptr = false @@ -218,6 +234,10 @@ assert( assert(enable_backup_ref_ptr_support || !enable_backup_ref_ptr_slow_checks, "Can't enable additional BackupRefPtr checks if it isn't enabled at all") +assert( + enable_backup_ref_ptr_support || !enable_raw_ptr_experimental, + "Can't make `raw_ptr_experimental` = `raw_ptr` when the latter is wholly disabled") + # enable_dangling_raw_ptr_checks can only be used if enable_backup_ref_ptr_support # is true. assert( diff --git a/base/allocator/partition_allocator/partition_alloc_hooks.cc b/base/allocator/partition_allocator/partition_alloc_hooks.cc index 79c4ccfe3..0de4de448 100644 --- a/base/allocator/partition_allocator/partition_alloc_hooks.cc +++ b/base/allocator/partition_allocator/partition_alloc_hooks.cc @@ -124,6 +124,8 @@ bool PartitionAllocHooks::ReallocOverrideHookIfEnabled(size_t* out, return false; } +// Do not unset the hook if there are remaining quarantined slots +// not to break checks on unquarantining. void PartitionAllocHooks::SetQuarantineOverrideHook( QuarantineOverrideHook* hook) { quarantine_override_hook_.store(hook, std::memory_order_release); diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h index c52453fce..c1a797ade 100644 --- a/base/allocator/partition_allocator/partition_root.h +++ b/base/allocator/partition_allocator/partition_root.h @@ -436,11 +436,6 @@ struct PA_ALIGNAS(64) PA_COMPONENT_EXPORT(PARTITION_ALLOC) PartitionRoot { PA_ALWAYS_INLINE static PartitionRoot* FromAddrInFirstSuperpage( uintptr_t address); - PA_ALWAYS_INLINE void DecreaseTotalSizeOfAllocatedBytes(SlotSpan* slot_span) - PA_EXCLUSIVE_LOCKS_REQUIRED(lock_); - PA_ALWAYS_INLINE void IncreaseTotalSizeOfAllocatedBytes(SlotSpan* slot_span, - size_t raw_size) - PA_EXCLUSIVE_LOCKS_REQUIRED(lock_); PA_ALWAYS_INLINE void DecreaseTotalSizeOfAllocatedBytes(uintptr_t addr, size_t len) PA_EXCLUSIVE_LOCKS_REQUIRED(lock_); @@ -1063,8 +1058,18 @@ PA_ALWAYS_INLINE void PartitionAllocFreeForRefCounting(uintptr_t slot_start) { // supports reference counts. PA_DCHECK(root->brp_enabled()); - // memset() can be really expensive. + // Iterating over the entire slot can be really expensive. #if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON) + auto hook = PartitionAllocHooks::GetQuarantineOverrideHook(); + // If we have a hook the object segment is not necessarily filled + // with |kQuarantinedByte|. + if (PA_LIKELY(!hook)) { + unsigned char* object = + static_cast<unsigned char*>(root->SlotStartToObject(slot_start)); + for (size_t i = 0; i < slot_span->GetUsableSize(root); ++i) { + PA_DCHECK(object[i] == kQuarantinedByte); + } + } DebugMemset(SlotStartAddr2Ptr(slot_start), kFreedByte, slot_span->GetUtilizedSlotSize() #if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT) @@ -1145,7 +1150,8 @@ PartitionRoot<thread_safe>::AllocFromBucket(Bucket* bucket, *usable_size = slot_span->GetUsableSize(this); } PA_DCHECK(slot_span->GetUtilizedSlotSize() <= slot_span->bucket->slot_size); - IncreaseTotalSizeOfAllocatedBytes(slot_span, raw_size); + IncreaseTotalSizeOfAllocatedBytes( + slot_start, slot_span->GetSlotSizeForBookkeeping(), raw_size); #if BUILDFLAG(USE_FREESLOT_BITMAP) if (!slot_span->bucket->is_direct_mapped()) { @@ -1414,12 +1420,15 @@ template <bool thread_safe> PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::FreeInSlotSpan( uintptr_t slot_start, SlotSpan* slot_span) { - DecreaseTotalSizeOfAllocatedBytes(slot_span); + DecreaseTotalSizeOfAllocatedBytes(slot_start, + slot_span->GetSlotSizeForBookkeeping()); + #if BUILDFLAG(USE_FREESLOT_BITMAP) if (!slot_span->bucket->is_direct_mapped()) { internal::FreeSlotBitmapMarkSlotAsFree(slot_start); } #endif + return slot_span->Free(slot_start); } @@ -1498,7 +1507,11 @@ PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::RawFreeBatch( // corresponding pages were faulted in (without acquiring the lock). So there // is no need to touch pages manually here before the lock. ::partition_alloc::internal::ScopedGuard guard{lock_}; - DecreaseTotalSizeOfAllocatedBytes(slot_span); + // TODO(thiabaud): Fix the accounting here. The size is correct, but the + // pointer is not. This only affects local tools that record each allocation, + // not our metrics. + DecreaseTotalSizeOfAllocatedBytes( + 0u, slot_span->GetSlotSizeForBookkeeping() * size); slot_span->AppendFreeList(head, tail, size); } @@ -1586,24 +1599,6 @@ PartitionRoot<thread_safe>::FromAddrInFirstSuperpage(uintptr_t address) { template <bool thread_safe> PA_ALWAYS_INLINE void -PartitionRoot<thread_safe>::IncreaseTotalSizeOfAllocatedBytes( - SlotSpan* slot_span, - size_t raw_size) { - IncreaseTotalSizeOfAllocatedBytes(reinterpret_cast<uintptr_t>(slot_span), - slot_span->GetSlotSizeForBookkeeping(), - raw_size); -} - -template <bool thread_safe> -PA_ALWAYS_INLINE void -PartitionRoot<thread_safe>::DecreaseTotalSizeOfAllocatedBytes( - SlotSpan* slot_span) { - DecreaseTotalSizeOfAllocatedBytes(reinterpret_cast<uintptr_t>(slot_span), - slot_span->GetSlotSizeForBookkeeping()); -} - -template <bool thread_safe> -PA_ALWAYS_INLINE void PartitionRoot<thread_safe>::IncreaseTotalSizeOfAllocatedBytes(uintptr_t addr, size_t len, size_t raw_size) { diff --git a/base/allocator/partition_allocator/pointers/raw_ptr.h b/base/allocator/partition_allocator/pointers/raw_ptr.h index 9ff1d2130..1b83201db 100644 --- a/base/allocator/partition_allocator/pointers/raw_ptr.h +++ b/base/allocator/partition_allocator/pointers/raw_ptr.h @@ -160,6 +160,14 @@ struct TraitsToImpl; template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty> class raw_ptr; +#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL) +template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty> +using raw_ptr_experimental = raw_ptr<T, Traits>; +#else +template <typename T, RawPtrTraits Traits = RawPtrTraits::kEmpty> +using raw_ptr_experimental = T*; +#endif // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL) + } // namespace base // This type is to be used internally, or in callbacks arguments when it is @@ -1127,6 +1135,7 @@ using RemovePointerT = typename RemovePointer<T>::type; } // namespace base using base::raw_ptr; +using base::raw_ptr_experimental; // DisableDanglingPtrDetection option for raw_ptr annotates // "intentional-and-safe" dangling pointers. It is meant to be used at the @@ -1149,6 +1158,17 @@ constexpr auto DanglingUntriaged = base::RawPtrTraits::kMayDangle; // instead of the raw_ptr. constexpr auto AllowPtrArithmetic = base::RawPtrTraits::kAllowPtrArithmetic; +// Temporary flag for `raw_ptr` / `raw_ref`. This is used by finch experiments +// to differentiate pointers added recently for the ChromeOS ash rewrite. +// +// See launch plan: +// https://docs.google.com/document/d/105OVhNl-2lrfWElQSk5BXYv-nLynfxUrbC4l8cZ0CoU/edit# +// +// This is not meant to be added manually. You can ignore this flag. +// +// TODO(https://crbug.com/1435441) Implement the ExperimentalAsh Trait. +constexpr auto ExperimentalAsh = base::RawPtrTraits::kMayDangle; + namespace std { // Override so set/map lookups do not create extra raw_ptr. This also allows diff --git a/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc b/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc index 5508c9cd1..7644478a2 100644 --- a/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc +++ b/base/allocator/partition_allocator/pointers/raw_ptr_unittest.cc @@ -6,6 +6,7 @@ #include <climits> #include <cstddef> +#include <cstdint> #include <string> #include <thread> #include <type_traits> @@ -25,6 +26,7 @@ #include "base/allocator/partition_allocator/pointers/raw_ref.h" #include "base/allocator/partition_allocator/tagging.h" #include "base/cpu.h" +#include "base/cxx20_to_address.h" #include "base/logging.h" #include "base/memory/raw_ptr_asan_service.h" #include "base/task/thread_pool.h" @@ -1475,6 +1477,59 @@ TEST_F(RawPtrTest, CrossKindAssignment) { CountersMatch()); } +// Without the explicitly customized `raw_ptr::to_address()`, +// `base::to_address()` will use the dereference operator. This is not +// what we want; this test enforces extraction semantics for +// `to_address()`. +TEST_F(RawPtrTest, ToAddressDoesNotDereference) { + CountingRawPtr<int> ptr = nullptr; + int* raw = base::to_address(ptr); + std::ignore = raw; + EXPECT_THAT((CountingRawPtrExpectations<RawPtrCountingImpl>{ + .get_for_dereference_cnt = 0, + .get_for_extraction_cnt = 1, + .get_for_comparison_cnt = 0, + .get_for_duplication_cnt = 0}), + CountersMatch()); +} + +TEST_F(RawPtrTest, ToAddressGivesBackRawAddress) { + int* raw = nullptr; + raw_ptr<int> miracle = raw; + EXPECT_EQ(base::to_address(raw), base::to_address(miracle)); +} + +// Verifies that `raw_ptr_experimental` is aliased appropriately. +// +// The `DisableDanglingPtrDetection` trait is arbitrarily chosen and is +// just there to ensure that `raw_ptr_experimental` knows how to field +// the traits template argument. +#if BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL) +static_assert( + std::is_same_v<raw_ptr_experimental<int, DisableDanglingPtrDetection>, + raw_ptr<int, DisableDanglingPtrDetection>>); +static_assert( + std::is_same_v<raw_ptr_experimental<const int, DisableDanglingPtrDetection>, + raw_ptr<const int, DisableDanglingPtrDetection>>); +static_assert( + std::is_same_v< + const raw_ptr_experimental<const int, DisableDanglingPtrDetection>, + const raw_ptr<const int, DisableDanglingPtrDetection>>); +#else // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL) +// `DisableDanglingPtrDetection` means nothing here and is silently +// ignored. +static_assert( + std::is_same_v<raw_ptr_experimental<int, DisableDanglingPtrDetection>, + int*>); +static_assert( + std::is_same_v<raw_ptr_experimental<const int, DisableDanglingPtrDetection>, + const int*>); +static_assert( + std::is_same_v< + const raw_ptr_experimental<const int, DisableDanglingPtrDetection>, + const int* const>); +#endif // BUILDFLAG(ENABLE_RAW_PTR_EXPERIMENTAL) + } // namespace namespace base { @@ -2122,6 +2177,28 @@ TEST_F(BackupRefPtrTest, Duplicate) { } #endif // BUILDFLAG(BACKUP_REF_PTR_POISON_OOB_PTR) +#if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON) +TEST_F(BackupRefPtrTest, WriteAfterFree) { + constexpr uint64_t kPayload = 0x1234567890ABCDEF; + + raw_ptr<uint64_t> ptr = + static_cast<uint64_t*>(allocator_.root()->Alloc(sizeof(uint64_t), "")); + + // Now |ptr| should be quarantined. + allocator_.root()->Free(ptr); + + EXPECT_DEATH_IF_SUPPORTED( + { + // Write something different from |kQuarantinedByte|. + *ptr = kPayload; + // Write-after-Free should lead to crash + // on |PartitionAllocFreeForRefCounting|. + ptr = nullptr; + }, + ""); +} +#endif // BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON) + namespace { constexpr uint8_t kCustomQuarantineByte = 0xff; static_assert(kCustomQuarantineByte != @@ -2138,13 +2215,20 @@ TEST_F(BackupRefPtrTest, QuarantineHook) { uint8_t* native_ptr = static_cast<uint8_t*>(allocator_.root()->Alloc(sizeof(uint8_t), "")); *native_ptr = 0; - raw_ptr<uint8_t> smart_ptr = native_ptr; - - allocator_.root()->Free(smart_ptr); - // Access the allocation through the native pointer to avoid triggering - // dereference checks in debug builds. - EXPECT_EQ(*partition_alloc::internal::TagPtr(native_ptr), - kCustomQuarantineByte); + { + raw_ptr<uint8_t> smart_ptr = native_ptr; + + allocator_.root()->Free(smart_ptr); + // Access the allocation through the native pointer to avoid triggering + // dereference checks in debug builds. + EXPECT_EQ(*partition_alloc::internal::TagPtr(native_ptr), + kCustomQuarantineByte); + + // Leaving |smart_ptr| filled with |kCustomQuarantineByte| can + // cause a crash because we have a DCHECK that expects it to be filled with + // |kQuarantineByte|. We need to ensure it is unquarantined before + // unregistering the hook. + } // <- unquarantined here partition_alloc::PartitionAllocHooks::SetQuarantineOverrideHook(nullptr); } diff --git a/base/android/field_trial_list.cc b/base/android/field_trial_list.cc index 64b176352..d2b01fcaf 100644 --- a/base/android/field_trial_list.cc +++ b/base/android/field_trial_list.cc @@ -11,6 +11,7 @@ #include "base/base_jni_headers/FieldTrialList_jni.h" #include "base/lazy_instance.h" #include "base/metrics/field_trial.h" +#include "base/metrics/field_trial_list_including_low_anonymity.h" #include "base/metrics/field_trial_params.h" using base::android::ConvertJavaStringToUTF8; @@ -77,15 +78,39 @@ static ScopedJavaLocalRef<jstring> JNI_FieldTrialList_GetVariationParameter( env, parameters[ConvertJavaStringToUTF8(env, jparameter_key)]); } +// JNI_FieldTrialList_LogActiveTrials() is static function, this makes friending +// it a hassle because it must be declared in the file that the friend +// declaration is in, but its declaration can't be included in multiple places +// or things get messy and the linker gets mad. This helper class exists only to +// friend the JNI function and is, in turn, friended by +// FieldTrialListIncludingLowAnonymity which allows for the private +// GetActiveFieldTrialGroups() to be reached. +class AndroidFieldTrialListLogActiveTrialsFriendHelper { + private: + friend void ::JNI_FieldTrialList_LogActiveTrials(JNIEnv* env); + + static bool AddObserver(base::FieldTrialList::Observer* observer) { + return base::FieldTrialListIncludingLowAnonymity::AddObserver(observer); + } + + static void GetActiveFieldTrialGroups( + base::FieldTrial::ActiveGroups* active_groups) { + base::FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups( + active_groups); + } +}; + static void JNI_FieldTrialList_LogActiveTrials(JNIEnv* env) { DCHECK(!g_trial_logger.IsCreated()); // This need only be called once. LOG(INFO) << "Logging active field trials..."; - base::FieldTrialList::AddObserver(&g_trial_logger.Get()); + AndroidFieldTrialListLogActiveTrialsFriendHelper::AddObserver( + &g_trial_logger.Get()); // Log any trials that were already active before adding the observer. std::vector<base::FieldTrial::ActiveGroup> active_groups; - base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + AndroidFieldTrialListLogActiveTrialsFriendHelper::GetActiveFieldTrialGroups( + &active_groups); for (const base::FieldTrial::ActiveGroup& group : active_groups) { TrialLogger::Log(group.trial_name, group.group_name); } diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java index 1b8658001..6c81339f0 100644 --- a/base/android/java/src/org/chromium/base/BuildInfo.java +++ b/base/android/java/src/org/chromium/base/BuildInfo.java @@ -287,14 +287,11 @@ public class BuildInfo { public static boolean targetsAtLeastU() { int target = ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion; - // Logic for pre-API-finalization: - return SdkLevel.isAtLeastU() && target == Build.VERSION_CODES.CUR_DEVELOPMENT; - // Logic for after API finalization but before public SDK release has to // just hardcode the appropriate SDK integer. This will include Android // builds with the finalized SDK, and also pre-API-finalization builds // (because CUR_DEVELOPMENT == 10000). - // return target >= <integer placeholder for U>; + return target >= 34; // Once the public SDK is upstreamed we can use the defined constant, // deprecate this, then eventually inline this at all callsites and diff --git a/base/android/java/src/org/chromium/base/ByteArrayGenerator.java b/base/android/java/src/org/chromium/base/ByteArrayGenerator.java deleted file mode 100644 index 71749d516..000000000 --- a/base/android/java/src/org/chromium/base/ByteArrayGenerator.java +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2014 The Chromium Authors -// 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 java.io.FileInputStream; -import java.io.IOException; -import java.security.GeneralSecurityException; - -/** - * Reads /dev/urandom to generate byte arrays for used in cryptographic functions. - */ -public class ByteArrayGenerator { - /** - * Polls random data to generate the array. - * @param numBytes Length of the array to generate. - * @return byte[] containing randomly generated data. - */ - public byte[] getBytes(int numBytes) throws IOException, GeneralSecurityException { - FileInputStream fis = null; - try { - fis = new FileInputStream("/dev/urandom"); - byte[] bytes = new byte[numBytes]; - if (bytes.length != fis.read(bytes)) { - throw new GeneralSecurityException("Not enough random data available"); - } - return bytes; - } finally { - if (fis != null) { - fis.close(); - } - } - } -} diff --git a/base/android/java/src/org/chromium/base/ResettersForTesting.java b/base/android/java/src/org/chromium/base/ResettersForTesting.java new file mode 100644 index 000000000..ecd25b0d1 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ResettersForTesting.java @@ -0,0 +1,120 @@ +// Copyright 2023 The Chromium Authors +// 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 java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; + +/** + * ResettersForTesting provides functionality for reset values set for testing. This class is used + * directly by test runners, but lives in prod code to simplify usage. + * + * It is required to invoke {@link #register(Runnable)} whenever a method called `set*ForTesting`, + * such `setFooForTesting(Foo foo)` is invoked. Typical usage looks like this: + * + * <code> + * class MyClass { + * private static MyClass sInstance; + * + * public static MyClass getInstance() { + * if (sInstance == null) sInstance = new MyClass(); + * return sInstance; + * } + * + * public static void setMyClassForTesting(MyClass myClassObj) { + * sInstance = myClassObj; + * ResettersForTesting.register(() -> sInstance = null); + * } + * } + * </code> + * + * This is not only used for singleton instances, but can also be used for resetting other static + * members. + * + * <code> + * class NeedsFoo { + * private static Foo sFooForTesting; + * + * public void doThing() { + * Foo foo; + * if (sFooForTesting != null) { + * foo = sFooForTesting; + * } else {* foo = new FooImpl(); + * } + * foo.doItsThing(); + * } + * + * public static void setFooForTesting(Foo foo) { + * sFooForTesting = foo; + * ResettersForTesting.register(() -> sFooForTesting = null); + * } + * } + * </code> + * + * For cases where it is important that a particular resetter runs only once, even if the + * `set*ForTesting` method is invoked multiple times, there is another variation that can be used. + * In particular, since a lambda always ends up creating a new instance in Chromium builds, we can + * avoid this by having a single static instance of the resetter, like this: + * + * <code> + * private static class NeedsFooSingleDestroy { + * private static final class LazyHolder { + * private static Foo INSTANCE = new Foo(); + * } + * + * private static LazyHolder sFoo; + * + * private static Runnable sOneShotResetter = () -> { + * sFoo.INSTANCE.destroy(); + * sFoo = new Foo(); + * }; + * + * public static void setFooForTesting(Foo foo) { + * sFoo.INSTANCE = foo; + * ResettersForTesting.register(sResetter); + * } + * } + * </code> + */ +public class ResettersForTesting { + // LinkedHashSet is a set that provides ordering and enables one-shot resetters to only be + // invoked once. For example, the following `sResetter` will only be in the set a single time. + // <code> + // private static final Runnable sResetter = () -> { ... } + // ... + // ResettersForTesting.register(sResetter); + // </code> + private static final LinkedHashSet<Runnable> sResetters = new LinkedHashSet<>(); + + /** + * Register a {@link Runnable} that will automatically execute during test tear down. + * @param runnable the {@link Runnable} to execute. + */ + public static void register(Runnable runnable) { + synchronized (sResetters) { + sResetters.add(runnable); + } + } + + /** + * Execute and clear all the currently registered resetters. + * + * This is not intended to be invoked manually, but is intended to be invoked by the test + * runners automatically during tear down. + */ + public static void executeResetters() { + ArrayList<Runnable> resetters; + synchronized (sResetters) { + resetters = new ArrayList<>(sResetters); + sResetters.clear(); + } + + // Ensure that resetters are run in reverse order, enabling nesting of values as well as + // being more similar to C++ destruction order. + Collections.reverse(resetters); + for (Runnable resetter : resetters) resetter.run(); + } +} diff --git a/base/android/java/src/org/chromium/base/ThreadUtils.java b/base/android/java/src/org/chromium/base/ThreadUtils.java index d5b7ee21e..aab0888d5 100644 --- a/base/android/java/src/org/chromium/base/ThreadUtils.java +++ b/base/android/java/src/org/chromium/base/ThreadUtils.java @@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.task.PostTask; +import org.chromium.base.task.TaskTraits; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -24,9 +25,9 @@ public class ThreadUtils { private static final Object sLock = new Object(); - private static boolean sWillOverride; + private static volatile boolean sWillOverride; - private static Handler sUiThreadHandler; + private static volatile Handler sUiThreadHandler; private static boolean sThreadAssertsDisabled; @@ -75,51 +76,45 @@ public class ThreadUtils { } } - public static void setWillOverrideUiThread(boolean willOverrideUiThread) { - synchronized (sLock) { - sWillOverride = willOverrideUiThread; - } + public static void setWillOverrideUiThread() { + sWillOverride = true; + assert sUiThreadHandler == null; } @VisibleForTesting public static void clearUiThreadForTesting() { - synchronized (sLock) { - sUiThreadHandler = null; - sWillOverride = false; - PostTask.resetUiThreadForTesting(); // IN-TEST - } + sWillOverride = false; + PostTask.resetUiThreadForTesting(); // IN-TEST + sUiThreadHandler = null; } public static void setUiThread(Looper looper) { + assert looper != null; synchronized (sLock) { - assert looper != null; - if (sUiThreadHandler != null && sUiThreadHandler.getLooper() != looper) { + if (sUiThreadHandler == null) { + Handler uiThreadHandler = new Handler(looper); + // Set up the UI Thread TaskExecutor before signaling readiness. + PostTask.onUiThreadReady(uiThreadHandler); + // volatile write signals readiness since other threads read it without acquiring + // sLock. + sUiThreadHandler = uiThreadHandler; + // Must come after PostTask is initialized since it uses PostTask. + TraceEvent.onUiThreadReady(); + } else if (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); } - PostTask.onUiThreadReady(); } - TraceEvent.onUiThreadReady(); } public static Handler getUiThreadHandler() { - boolean createdHandler = false; - synchronized (sLock) { - if (sUiThreadHandler == null) { - if (sWillOverride) { - throw new RuntimeException("Did not yet override the UI thread"); - } - sUiThreadHandler = new Handler(Looper.getMainLooper()); - PostTask.onUiThreadReady(); - createdHandler = true; - } - } - if (createdHandler) { - TraceEvent.onUiThreadReady(); + if (sUiThreadHandler != null) return sUiThreadHandler; + + if (sWillOverride) { + throw new RuntimeException("Did not yet override the UI thread"); } + setUiThread(Looper.getMainLooper()); return sUiThreadHandler; } @@ -127,42 +122,25 @@ public class ThreadUtils { * Run the supplied Runnable on the main thread. The method will block until the Runnable * completes. * - * @deprecated Use {@link - * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Runnable) - * TestThreadUtils.runOnUiThreadBlocking(r)} instead. For non-test usage (heavily - * discouraged) use {@link org.chromium.base.task.PostTask#runSynchronously(TaskTraits, - * Runnable) PostTask.runSynchronously(TaskTraits, Runnable)} with task traits chosen from - * {@link org.chromium.base.task.TaskTraits}. If the call site can't import - * content, it means it shouldn't be posting to the UI thread at all; all such usages will - * gradually get rewritten. + * Note that non-test usage of this function is heavily discouraged. For non-tests, use + * callbacks rather than blocking threads. + * * @param r The Runnable to run. */ - @Deprecated public static void runOnUiThreadBlocking(final Runnable r) { - if (runningOnUiThread()) { - r.run(); - } else { - FutureTask<Void> task = new FutureTask<Void>(r, null); - postOnUiThread(task); - try { - task.get(); - } catch (Exception e) { - throw new RuntimeException("Exception occurred while waiting for runnable", e); - } - } + PostTask.runSynchronously(TaskTraits.UI_DEFAULT, r); } /** * Run the supplied Callable on the main thread, wrapping any exceptions in a RuntimeException. * The method will block until the Callable completes. * - * @deprecated Use {@link - * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlockingNoException(Callable) - * TestThreadUtils.runOnUiThreadBlockingNoException(c)} instead. + * Note that non-test usage of this function is heavily discouraged. For non-tests, use + * callbacks rather than blocking threads. + * * @param c The Callable to run * @return The result of the callable */ - @Deprecated public static <T> T runOnUiThreadBlockingNoException(Callable<T> c) { try { return runOnUiThreadBlocking(c); @@ -175,43 +153,26 @@ public class ThreadUtils { * Run the supplied Callable on the main thread, The method will block until the Callable * completes. * - * @deprecated Use {@link - * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Callable) - * TestThreadUtils.runOnUiThreadBlocking(c)} instead. + * Note that non-test usage of this function is heavily discouraged. For non-tests, use + * callbacks rather than blocking threads. + * * @param c The Callable to run * @return The result of the callable * @throws ExecutionException c's exception */ - @Deprecated public static <T> T runOnUiThreadBlocking(Callable<T> c) throws ExecutionException { - FutureTask<T> task = new FutureTask<T>(c); - runOnUiThread(task); - try { - return task.get(); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted waiting for callable", e); - } + return PostTask.runSynchronously(TaskTraits.UI_DEFAULT, c); } /** * Run the supplied FutureTask on the main thread. The method will block only if the current * thread is the main thread. * - * @deprecated Use {@link org.chromium.base.task.PostTask#runOrPostTask(TaskTraits, Runnable) - * PostTask.runOrPostTask(TaskTraits, Runnable)} with task traits chosen from {@link - * org.chromium.base.task.TaskTraits}. - * If the call site can't import content, it means it shouldn't be posting to the UI - * thread at all; all such usages will gradually get rewritten. * @param task The FutureTask to run * @return The queried task (to aid inline construction) */ - @Deprecated public static <T> FutureTask<T> runOnUiThread(FutureTask<T> task) { - if (runningOnUiThread()) { - task.run(); - } else { - postOnUiThread(task); - } + PostTask.runOrPostTask(TaskTraits.UI_DEFAULT, task); return task; } @@ -219,15 +180,9 @@ public class ThreadUtils { * Run the supplied Callable on the main thread. The method will block only if the current * thread is the main thread. * - * @deprecated Use {@link org.chromium.base.task.PostTask#runOrPostTask(TaskTraits, Runnable) - * PostTask.runOrPostTask(TaskTraits, Runnable)} with task traits chosen from {@link - * org.chromium.base.task.TaskTraits}. - * If the call site can't import content, it means it shouldn't be posting to the UI - * thread at all; all such usages will gradually get rewritten. * @param c The Callable to run * @return A FutureTask wrapping the callable to retrieve results */ - @Deprecated public static <T> FutureTask<T> runOnUiThread(Callable<T> c) { return runOnUiThread(new FutureTask<T>(c)); } @@ -236,37 +191,21 @@ public class ThreadUtils { * Run the supplied Runnable on the main thread. The method will block only if the current * thread is the main thread. * - * @deprecated Use {@link org.chromium.base.task.PostTask#runOrPostTask(TaskTraits, Runnable) - * PostTask.runOrPostTask(TaskTraits, Runnable)} with task traits chosen from {@link - * org.chromium.base.task.TaskTraits}. - * If the call site can't import content, it means it shouldn't be posting to the UI - * thread at all; all such usages will gradually get rewritten. * @param r The Runnable to run */ - @Deprecated public static void runOnUiThread(Runnable r) { - if (runningOnUiThread()) { - r.run(); - } else { - getUiThreadHandler().post(r); - } + PostTask.runOrPostTask(TaskTraits.UI_DEFAULT, r); } /** * Post the supplied FutureTask to run on the main thread. The method will not block, even if * called on the UI thread. * - * @deprecated Use {@link org.chromium.base.task.PostTask#postTask(TaskTraits, Runnable) - * PostTask.postTask(TaskTraits, Runnable)} with task traits chosen from {@link - * org.chromium.base.task.TaskTraits}. - * If the call site can't import content, it means it shouldn't be posting to the UI - * thread at all; all such usages will gradually get rewritten. * @param task The FutureTask to run * @return The queried task (to aid inline construction) */ - @Deprecated public static <T> FutureTask<T> postOnUiThread(FutureTask<T> task) { - getUiThreadHandler().post(task); + PostTask.postTask(TaskTraits.UI_DEFAULT, task); return task; } @@ -274,33 +213,21 @@ public class ThreadUtils { * Post the supplied Runnable to run on the main thread. The method will not block, even if * called on the UI thread. * - * @deprecated Use {@link org.chromium.base.task.PostTask#postTask(TaskTraits, Runnable) - * PostTask.postTask(TaskTraits, Runnable)} with task traits chosen from {@link - * org.chromium.base.task.TaskTraits}. - * If the call site can't import content, it means it shouldn't be posting to the UI - * thread at all; all such usages will gradually get rewritten. - * @param task The Runnable to run + * @param r The Runnable to run */ - @Deprecated - public static void postOnUiThread(Runnable task) { - getUiThreadHandler().post(task); + public static void postOnUiThread(Runnable r) { + PostTask.postTask(TaskTraits.UI_DEFAULT, r); } /** * 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. * - * @deprecated Use {@link org.chromium.base.task.PostTask#postDelayedTask(TaskTraits, Runnable, - * long) PostTask.postDelayedTask(TaskTraits, Runnable, long)} with task traits chosen - * from {@link org.chromium.base.task.TaskTraits}. - * If the call site can't import content, it means it shouldn't be posting to the UI - * thread at all; all such usages will gradually get rewritten. - * @param task The Runnable to run + * @param r The Runnable to run * @param delayMillis The delay in milliseconds until the Runnable will be run */ - @Deprecated - public static void postOnUiThreadDelayed(Runnable task, long delayMillis) { - getUiThreadHandler().postDelayed(task, delayMillis); + public static void postOnUiThreadDelayed(Runnable r, long delayMillis) { + PostTask.postDelayedTask(TaskTraits.UI_DEFAULT, r, delayMillis); } /** diff --git a/base/android/java/src/org/chromium/base/TraceEvent.java b/base/android/java/src/org/chromium/base/TraceEvent.java index e25550173..bbb57c23a 100644 --- a/base/android/java/src/org/chromium/base/TraceEvent.java +++ b/base/android/java/src/org/chromium/base/TraceEvent.java @@ -23,7 +23,6 @@ import org.chromium.base.task.TaskTraits; import org.chromium.build.annotations.MainDex; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; /** * Java mirror of Chrome trace event API. See base/trace_event/trace_event.h. @@ -45,8 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @MainDex public class TraceEvent implements AutoCloseable { private static volatile boolean sEnabled; // True when tracing into Chrome's tracing service. - private static AtomicBoolean sNativeTracingReady = new AtomicBoolean(); - private static AtomicBoolean sUiThreadReady = new AtomicBoolean(); + private static volatile boolean sUiThreadReady; private static boolean sEventNameFilteringEnabled; @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -302,7 +300,7 @@ public class TraceEvent implements AutoCloseable { ThreadUtils.getUiThreadLooper().setMessageLogging( enabled ? LooperMonitorHolder.sInstance : null); } - if (sUiThreadReady.get()) { + if (sUiThreadReady) { ViewHierarchyDumper.updateEnabledState(); } } @@ -335,15 +333,12 @@ public class TraceEvent implements AutoCloseable { } public static void onNativeTracingReady() { - // Register an enabled observer, such that java traces are always - // enabled with native. - sNativeTracingReady.set(true); TraceEventJni.get().registerEnabledObserver(); } // Called by ThreadUtils. static void onUiThreadReady() { - sUiThreadReady.set(true); + sUiThreadReady = true; if (sEnabled) { ViewHierarchyDumper.updateEnabledState(); } @@ -648,21 +643,18 @@ public class TraceEvent implements AutoCloseable { } public static void updateEnabledState() { - if (!ThreadUtils.runningOnUiThread()) { - ThreadUtils.postOnUiThread(() -> { updateEnabledState(); }); - return; - } - - if (TraceEventJni.get().viewHierarchyDumpEnabled()) { - if (sInstance == null) { - sInstance = new ViewHierarchyDumper(); - } - enable(); - } else { - if (sInstance != null) { - disable(); + PostTask.runOrPostTask(TaskTraits.UI_DEFAULT, () -> { + if (TraceEventJni.get().viewHierarchyDumpEnabled()) { + if (sInstance == null) { + sInstance = new ViewHierarchyDumper(); + } + enable(); + } else { + if (sInstance != null) { + disable(); + } } - } + }); } private static void dumpView(ActivityInfo collection, int parentId, View v) { diff --git a/base/android/java/src/org/chromium/base/WrappedClassLoader.java b/base/android/java/src/org/chromium/base/WrappedClassLoader.java index ceff39578..45a6bb0c7 100644 --- a/base/android/java/src/org/chromium/base/WrappedClassLoader.java +++ b/base/android/java/src/org/chromium/base/WrappedClassLoader.java @@ -11,12 +11,12 @@ import dalvik.system.BaseDexClassLoader; * to the first one that returns a match. */ public class WrappedClassLoader extends ClassLoader { - private ClassLoader mPrimaryClassLoader; - private ClassLoader mSecondaryClassLoader; + private final ClassLoader mPrimaryClassLoader; + private final ClassLoader mSecondaryClassLoader; public WrappedClassLoader(ClassLoader primary, ClassLoader secondary) { - this.mPrimaryClassLoader = primary; - this.mSecondaryClassLoader = secondary; + mPrimaryClassLoader = primary; + mSecondaryClassLoader = secondary; } @Override @@ -24,7 +24,12 @@ public class WrappedClassLoader extends ClassLoader { try { return mPrimaryClassLoader.loadClass(name); } catch (ClassNotFoundException e) { - return mSecondaryClassLoader.loadClass(name); + try { + return mSecondaryClassLoader.loadClass(name); + } catch (ClassNotFoundException e2) { + e.addSuppressed(e2); + throw e; + } } } diff --git a/base/android/java/src/org/chromium/base/task/PostTask.java b/base/android/java/src/org/chromium/base/task/PostTask.java index 7ff7ee64c..1f1e264d4 100644 --- a/base/android/java/src/org/chromium/base/task/PostTask.java +++ b/base/android/java/src/org/chromium/base/task/PostTask.java @@ -4,6 +4,8 @@ package org.chromium.base.task; +import android.os.Handler; + import org.chromium.base.Log; import org.chromium.base.ThreadUtils; import org.chromium.base.annotations.CalledByNative; @@ -130,11 +132,9 @@ public class PostTask { * * Use this only for trivial tasks as it ignores task priorities. * - * @deprecated In tests, use {@link - * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Runnable) - * TestThreadUtils.runOnUiThreadBlocking(Runnable)} instead. Non-test usage is heavily - * discouraged. For non-tests, use callbacks rather than blocking threads. If you - * absolutely must block the thread, use FutureTask.get(). + * Note that non-test usage of this function is heavily discouraged. For non-tests, use + * callbacks rather than blocking threads. + * * @param taskTraits The TaskTraits that describe the desired TaskRunner. * @param task The task to be run with the specified traits. * @return The result of the callable @@ -154,11 +154,9 @@ public class PostTask { * * Use this only for trivial tasks as it ignores task priorities. * - * @deprecated In tests, use {@link - * org.chromium.content_public.browser.test.util.TestThreadUtils#runOnUiThreadBlocking(Runnable) - * TestThreadUtils.runOnUiThreadBlocking(Runnable)} instead. Non-test usage is heavily - * discouraged. For non-tests, use callbacks rather than blocking threads. If you - * absolutely must block the thread, use FutureTask.get(). + * Note that non-test usage of this function is heavily discouraged. For non-tests, use + * callbacks rather than blocking threads. + * * @param taskTraits The TaskTraits that describe the desired TaskRunner. * @param task The task to be run with the specified traits. */ @@ -273,12 +271,15 @@ public class PostTask { } /** Called once when the UI thread has been initialized */ - public static void onUiThreadReady() { + public static void onUiThreadReady(Handler uiThreadHandler) { assert sUiThreadTaskExecutor == null; - sUiThreadTaskExecutor = new UiThreadTaskExecutor(); + sUiThreadTaskExecutor = new UiThreadTaskExecutor(uiThreadHandler); } public static void resetUiThreadForTesting() { + // UI Thread cannot be reset cleanly after native initialization. + assert !sNativeInitialized; + sUiThreadTaskExecutor = null; } } diff --git a/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java b/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java index 2f349d85b..b02436670 100644 --- a/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java +++ b/base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java @@ -49,4 +49,15 @@ public class SingleThreadTaskRunnerImpl extends TaskRunnerImpl implements Single mHandler.post(mRunPreNativeTaskClosure); } } + + @Override + protected boolean schedulePreNativeDelayedTask(Runnable task, long delay) { + if (mHandler == null) return false; + // In theory it would be fine to delay these tasks until native is initialized and post them + // to the native task runner, but in practice some tests don't initialize native and still + // expect delayed tasks to eventually run. There's no good reason not to support them here, + // there are so few of these tasks that they're very unlikely to cause performance problems. + mHandler.postDelayed(task, delay); + return true; + } } diff --git a/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java b/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java index 21fd7fea3..0ab9c3991 100644 --- a/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java +++ b/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java @@ -161,7 +161,7 @@ public class TaskRunnerImpl implements TaskRunner { if (delay == 0) { mPreNativeTasks.add(task); schedulePreNativeTask(); - } else { + } else if (!schedulePreNativeDelayedTask(task, delay)) { Pair<Runnable, Long> preNativeDelayedTask = new Pair<>(task, delay); mPreNativeDelayedTasks.add(preNativeDelayedTask); } @@ -201,6 +201,16 @@ public class TaskRunnerImpl implements TaskRunner { } /** + * Overridden in subclasses that support Delayed tasks pre-native. + * + * @return true if the task has been scheduled and does not need to be forwarded to the native + * task runner. + */ + protected boolean schedulePreNativeDelayedTask(Runnable task, long delay) { + return false; + } + + /** * Runs a single task and returns when its finished. */ // The trace event name is derived from string literals. diff --git a/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java b/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java index 460bf1f04..95b57a895 100644 --- a/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java +++ b/base/android/java/src/org/chromium/base/task/UiThreadTaskExecutor.java @@ -6,8 +6,6 @@ package org.chromium.base.task; import android.os.Handler; -import org.chromium.base.ThreadUtils; - /** * This {@link TaskExecutor} is for tasks posted with UI Thread {@link TaskTraits}. It maps to * content::BrowserTaskExecutor in C++, except that in Java the UI thread is a base/ concept and @@ -20,8 +18,7 @@ public class UiThreadTaskExecutor implements TaskExecutor { private final SingleThreadTaskRunner mUserVisibleTaskRunner; private final SingleThreadTaskRunner mUserBlockingTaskRunner; - public UiThreadTaskExecutor() { - Handler handler = ThreadUtils.getUiThreadHandler(); + public UiThreadTaskExecutor(Handler handler) { mBestEffortTaskRunner = new SingleThreadTaskRunnerImpl(handler, TaskTraits.UI_BEST_EFFORT); mUserVisibleTaskRunner = new SingleThreadTaskRunnerImpl(handler, TaskTraits.UI_USER_VISIBLE); diff --git a/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java b/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java new file mode 100644 index 000000000..eb369f61a --- /dev/null +++ b/base/android/junit/src/org/chromium/base/ResettersForTestingTest.java @@ -0,0 +1,145 @@ +// Copyright 2023 The Chromium Authors +// 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 static junit.framework.Assert.assertEquals; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import org.chromium.base.test.BaseRobolectricTestRunner; + +/** + * Unit tests for {@link ResettersForTesting}. + */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class ResettersForTestingTest { + private static class ResetsToNull { + public static String str; + public static void setStrForTesting(String newStr) { + str = newStr; + ResettersForTesting.register(() -> str = null); + } + } + + private static class ResetsToOldValue { + public static String str; + + public static void setStrForTesting(String newStr) { + String oldValue = str; + str = newStr; + ResettersForTesting.register(() -> str = oldValue); + } + } + + private static class ResetsToNullAndIncrements { + public static String str; + public static int resetCount; + + public static void setStrForTesting(String newStr) { + str = newStr; + ResettersForTesting.register(() -> { + str = null; + resetCount++; + }); + } + } + + private static class ResetsToNullAndIncrementsWithOneShotResetter { + public static String str; + public static int resetCount; + private static Runnable sResetter = () -> { + str = null; + resetCount++; + }; + + public static void setStrForTesting(String newStr) { + str = newStr; + ResettersForTesting.register(sResetter); + } + } + + @Test + public void testTypicalUsage() { + ResetsToNull.setStrForTesting("foo"); + assertEquals("foo", ResetsToNull.str); + ResettersForTesting.executeResetters(); + Assert.assertNull(ResetsToNull.str); + } + + @Test + public void testResetsToPreviousValue() { + // Inject a previous value to verify that we can get back to it. + ResetsToOldValue.str = "bar"; + + ResetsToOldValue.setStrForTesting("foo"); + assertEquals("foo", ResetsToOldValue.str); + + // After resetting the value, it should be back to the first value. + ResettersForTesting.executeResetters(); + assertEquals("bar", ResetsToOldValue.str); + } + + @Test + public void testMultipleResets() { + // Inject an outer value to verify we can get back to this. + ResetsToOldValue.str = "qux"; + + // Then set the next value. + ResetsToOldValue.setStrForTesting("foo"); + assertEquals("foo", ResetsToOldValue.str); + + // Now, next that value into another one to ensure the unwinding works. + ResetsToOldValue.setStrForTesting("bar"); + assertEquals("bar", ResetsToOldValue.str); + + // Since we are invoking the resetters in the reverse order, we should now be back to start. + ResettersForTesting.executeResetters(); + assertEquals("qux", ResetsToOldValue.str); + } + + @Test + public void testResettersExecutedOnlyOnce() { + // Force set this to 0 for this particular test. + ResetsToNullAndIncrements.resetCount = 0; + ResetsToNullAndIncrements.str = null; + + // Set the initial value and register the resetter. + ResetsToNullAndIncrements.setStrForTesting("some value"); + assertEquals("some value", ResetsToNullAndIncrements.str); + + // Now, execute all resetters and ensure it's only executed once. + ResettersForTesting.executeResetters(); + assertEquals(1, ResetsToNullAndIncrements.resetCount); + + // Execute the resetters again, and verify it does not invoke the same resetter again. + ResettersForTesting.executeResetters(); + assertEquals(1, ResetsToNullAndIncrements.resetCount); + } + + @Test + public void testResettersExecutedOnlyOnceForOneShotResetters() { + // Force set this to 0 for this particular test. + ResetsToNullAndIncrementsWithOneShotResetter.resetCount = 0; + ResetsToNullAndIncrementsWithOneShotResetter.str = null; + + // Set the initial value and register the resetter twice. + ResetsToNullAndIncrementsWithOneShotResetter.setStrForTesting("some value"); + ResetsToNullAndIncrementsWithOneShotResetter.setStrForTesting("some other value"); + assertEquals("some other value", ResetsToNullAndIncrementsWithOneShotResetter.str); + + // Now, execute all resetters and ensure it's only executed once, since it is a single + // instance of the same resetter. + ResettersForTesting.executeResetters(); + assertEquals(1, ResetsToNullAndIncrementsWithOneShotResetter.resetCount); + + // Execute the resetters again, and verify it does not invoke the same resetter again. + ResettersForTesting.executeResetters(); + assertEquals(1, ResetsToNullAndIncrementsWithOneShotResetter.resetCount); + } +} diff --git a/base/android/proguard/chromium_code.flags b/base/android/proguard/chromium_code.flags index 5f5bb55df..b8ee9ea74 100644 --- a/base/android/proguard/chromium_code.flags +++ b/base/android/proguard/chromium_code.flags @@ -13,12 +13,8 @@ @androidx.annotation.Keep <methods>; } -# Even unused methods kept due to explicit jni registration: -# https://crbug.com/688465. --keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class !org.chromium.base.library_loader.**,** { - native <methods>; -} --keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class org.chromium.base.library_loader.** { +# Allow unused native methods to be removed, but prevent renaming on those that are kept. +-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class ** { native <methods>; } diff --git a/base/android/proguard/remove_logging.flags b/base/android/proguard/remove_logging.flags index 2e01eea2e..3bc707c28 100644 --- a/base/android/proguard/remove_logging.flags +++ b/base/android/proguard/remove_logging.flags @@ -2,17 +2,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - -# Remove Log.d(), Log.v(), and all isLoggable() where the return value is -# unused (e.g. if it was guarding a call to Log.d()). --assumenosideeffects class android.util.Log { - static int d(...); - static int v(...); - # Don't use "return false" here because some code guards info logs behind - # isLoggable(DEBUG) as a way to enable logs via adb. - # https://crbug.com/1366880 - static boolean isLoggable(...); -} +# Remove Log.d(), Log.v(), and corresponding isLoggable() calls. +# Log.DEBUG = 3, Log.VERBOSE = 2. +# https://stackoverflow.com/questions/73876633/what-does-the-r8-maximumremovedandroidloglevel-option +-maximumremovedandroidloglevel 3 # Makes try-with-resources less inefficient. Saved 3.8kb when added. -assumenosideeffects class java.lang.Throwable { diff --git a/base/check_op.h b/base/check_op.h index 07ad07d1e..d323229fc 100644 --- a/base/check_op.h +++ b/base/check_op.h @@ -15,6 +15,7 @@ #include "base/debug/debugging_buildflags.h" #include "base/memory/raw_ptr_exclusion.h" #include "base/template_util.h" +#include "base/types/supports_ostream_operator.h" // This header defines the (DP)CHECK_EQ etc. macros. // diff --git a/base/containers/span.h b/base/containers/span.h index 65b392b7a..ce3e01727 100644 --- a/base/containers/span.h +++ b/base/containers/span.h @@ -6,6 +6,7 @@ #define BASE_CONTAINERS_SPAN_H_ #include <stddef.h> +#include <stdint.h> #include <array> #include <iterator> diff --git a/base/critical_closure.h b/base/critical_closure.h index d2673de35..7e3369ea8 100644 --- a/base/critical_closure.h +++ b/base/critical_closure.h @@ -82,6 +82,10 @@ class PendingCriticalClosure { inline OnceClosure MakeCriticalClosure(StringPiece task_name, OnceClosure closure, bool is_immediate) { + // Wrapping a null closure in a critical closure has unclear semantics and + // most likely indicates a bug. CHECK-ing early allows detecting and + // investigating these cases more easily. + CHECK(!closure.is_null()); if (is_immediate) { return base::BindOnce(&internal::ImmediateCriticalClosure::Run, Owned(new internal::ImmediateCriticalClosure( diff --git a/base/critical_closure_internal_ios.mm b/base/critical_closure_internal_ios.mm index 736caca89..859a3a636 100644 --- a/base/critical_closure_internal_ios.mm +++ b/base/critical_closure_internal_ios.mm @@ -11,21 +11,27 @@ namespace internal { ImmediateCriticalClosure::ImmediateCriticalClosure(StringPiece task_name, OnceClosure closure) - : critical_action_(task_name), closure_(std::move(closure)) {} + : critical_action_(task_name), closure_(std::move(closure)) { + CHECK(!closure_.is_null()); +} ImmediateCriticalClosure::~ImmediateCriticalClosure() {} void ImmediateCriticalClosure::Run() { + CHECK(!closure_.is_null()); std::move(closure_).Run(); } PendingCriticalClosure::PendingCriticalClosure(StringPiece task_name, OnceClosure closure) - : task_name_(task_name), closure_(std::move(closure)) {} + : task_name_(task_name), closure_(std::move(closure)) { + CHECK(!closure_.is_null()); +} PendingCriticalClosure::~PendingCriticalClosure() {} void PendingCriticalClosure::Run() { + CHECK(!closure_.is_null()); critical_action_.emplace(task_name_); std::move(closure_).Run(); } diff --git a/base/debug/allocation_trace_perftest.cc b/base/debug/allocation_trace_perftest.cc new file mode 100644 index 000000000..78e348f5e --- /dev/null +++ b/base/debug/allocation_trace_perftest.cc @@ -0,0 +1,211 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <thread> +#include <vector> + +#include "base/debug/allocation_trace.h" +#include "base/strings/stringprintf.h" +#include "base/timer/lap_timer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/perf/perf_result_reporter.h" + +namespace base { +namespace debug { +namespace { +// Change kTimeLimit to something higher if you need more time to capture a +// trace. +constexpr base::TimeDelta kTimeLimit = base::Seconds(3); +constexpr int kWarmupRuns = 100; +constexpr int kTimeCheckInterval = 1000; +constexpr char kMetricStackTraceDuration[] = ".duration_per_run"; +constexpr char kMetricStackTraceThroughput[] = ".throughput"; + +enum class HandlerFunctionSelector { OnAllocation, OnFree }; + +// An executor to perform the actual notification of the recorder. The correct +// handler function is selected using template specialization based on the +// HandlerFunctionSelector. +template <HandlerFunctionSelector HandlerFunction> +struct HandlerFunctionExecutor { + void operator()(base::debug::tracer::AllocationTraceRecorder& recorder) const; +}; + +template <> +struct HandlerFunctionExecutor<HandlerFunctionSelector::OnAllocation> { + void operator()( + base::debug::tracer::AllocationTraceRecorder& recorder) const { + // Since the recorder just stores the value, we can use any value for + // address and size that we want. + recorder.OnAllocation( + &recorder, sizeof(recorder), + base::allocator::dispatcher::AllocationSubsystem::kPartitionAllocator, + nullptr); + } +}; + +template <> +struct HandlerFunctionExecutor<HandlerFunctionSelector::OnFree> { + void operator()( + base::debug::tracer::AllocationTraceRecorder& recorder) const { + recorder.OnFree(&recorder); + } +}; +} // namespace + +class AllocationTraceRecorderPerfTest + : public testing::TestWithParam< + std::tuple<HandlerFunctionSelector, size_t>> { + protected: + // The result data of a single thread. From the results of all the single + // threads the final results will be calculated. + struct ResultData { + TimeDelta time_per_lap; + float laps_per_second = 0.0; + int number_of_laps = 0; + }; + + // The data of a single test thread. + struct ThreadRunnerData { + std::thread thread; + ResultData result_data; + }; + + // Create and setup the result reporter. + const char* GetHandlerDescriptor(HandlerFunctionSelector handler_function); + perf_test::PerfResultReporter SetUpReporter( + HandlerFunctionSelector handler_function, + size_t number_of_allocating_threads); + + // Select the correct test function which shall be used for the current test. + using TestFunction = + void (*)(base::debug::tracer::AllocationTraceRecorder& recorder, + ResultData& result_data); + + static TestFunction GetTestFunction(HandlerFunctionSelector handler_function); + template <HandlerFunctionSelector HandlerFunction> + static void TestFunctionImplementation( + base::debug::tracer::AllocationTraceRecorder& recorder, + ResultData& result_data); + + // The test management function. Using the the above auxiliary functions it is + // responsible to setup the result reporter, select the correct test function, + // spawn the specified number of worker threads and post process the results. + void PerformTest(HandlerFunctionSelector handler_function, + size_t number_of_allocating_threads); +}; + +const char* AllocationTraceRecorderPerfTest::GetHandlerDescriptor( + HandlerFunctionSelector handler_function) { + switch (handler_function) { + case HandlerFunctionSelector::OnAllocation: + return "OnAllocation"; + case HandlerFunctionSelector::OnFree: + return "OnFree"; + } +} + +perf_test::PerfResultReporter AllocationTraceRecorderPerfTest::SetUpReporter( + HandlerFunctionSelector handler_function, + size_t number_of_allocating_threads) { + const std::string story_name = base::StringPrintf( + "(%s;%zu-threads)", GetHandlerDescriptor(handler_function), + number_of_allocating_threads); + + perf_test::PerfResultReporter reporter("AllocationRecorderPerf", story_name); + reporter.RegisterImportantMetric(kMetricStackTraceDuration, "ns"); + reporter.RegisterImportantMetric(kMetricStackTraceThroughput, "runs/s"); + return reporter; +} + +AllocationTraceRecorderPerfTest::TestFunction +AllocationTraceRecorderPerfTest::GetTestFunction( + HandlerFunctionSelector handler_function) { + switch (handler_function) { + case HandlerFunctionSelector::OnAllocation: + return TestFunctionImplementation<HandlerFunctionSelector::OnAllocation>; + case HandlerFunctionSelector::OnFree: + return TestFunctionImplementation<HandlerFunctionSelector::OnFree>; + } +} + +void AllocationTraceRecorderPerfTest::PerformTest( + HandlerFunctionSelector handler_function, + size_t number_of_allocating_threads) { + perf_test::PerfResultReporter reporter = + SetUpReporter(handler_function, number_of_allocating_threads); + + TestFunction test_function = GetTestFunction(handler_function); + + base::debug::tracer::AllocationTraceRecorder the_recorder; + + std::vector<ThreadRunnerData> notifying_threads; + notifying_threads.reserve(number_of_allocating_threads); + + // Setup the threads. After creation, each thread immediately starts running. + // We expect the creation of the threads to be so quick that the delay from + // first to last thread is negligible. + for (size_t i = 0; i < number_of_allocating_threads; ++i) { + auto& last_item = notifying_threads.emplace_back(); + + last_item.thread = std::thread{test_function, std::ref(the_recorder), + std::ref(last_item.result_data)}; + } + + TimeDelta average_time_per_lap; + float average_laps_per_second = 0; + + // Wait for each thread to finish and collect its result data. + for (auto& item : notifying_threads) { + item.thread.join(); + // When finishing, each threads writes its results into result_data. So, + // from here we gather its performance statistics. + average_time_per_lap += item.result_data.time_per_lap; + average_laps_per_second += item.result_data.laps_per_second; + } + + average_time_per_lap /= number_of_allocating_threads; + average_laps_per_second /= number_of_allocating_threads; + + reporter.AddResult(kMetricStackTraceDuration, average_time_per_lap); + reporter.AddResult(kMetricStackTraceThroughput, average_laps_per_second); +} + +template <HandlerFunctionSelector HandlerFunction> +void AllocationTraceRecorderPerfTest::TestFunctionImplementation( + base::debug::tracer::AllocationTraceRecorder& recorder, + ResultData& result_data) { + LapTimer timer(kWarmupRuns, kTimeLimit, kTimeCheckInterval, + LapTimer::TimerMethod::kUseTimeTicks); + + HandlerFunctionExecutor<HandlerFunction> handler_executor; + + timer.Start(); + do { + handler_executor(recorder); + + timer.NextLap(); + } while (!timer.HasTimeLimitExpired()); + + result_data.time_per_lap = timer.TimePerLap(); + result_data.laps_per_second = timer.LapsPerSecond(); + result_data.number_of_laps = timer.NumLaps(); +} + +INSTANTIATE_TEST_SUITE_P( + , + AllocationTraceRecorderPerfTest, + ::testing::Combine(::testing::Values(HandlerFunctionSelector::OnAllocation, + HandlerFunctionSelector::OnFree), + ::testing::Values(1, 5, 10, 20, 40, 80))); + +TEST_P(AllocationTraceRecorderPerfTest, TestNotification) { + const auto parameters = GetParam(); + const HandlerFunctionSelector handler_function = std::get<0>(parameters); + const size_t number_of_threads = std::get<1>(parameters); + PerformTest(handler_function, number_of_threads); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/debug.gni b/base/debug/debug.gni index f13f156b5..848d61a2a 100644 --- a/base/debug/debug.gni +++ b/base/debug/debug.gni @@ -14,7 +14,7 @@ declare_args() { # # Although it should work on other platforms as well, for the above reasons, # we currently enable it only for Android when compiling for Arm64. - build_allocation_stack_trace_recorder = current_cpu == "arm64" && is_android + build_allocation_stack_trace_recorder = false } assert(!(build_allocation_stack_trace_recorder && is_fuchsia), diff --git a/base/debug/dump_without_crashing.h b/base/debug/dump_without_crashing.h index d5d014d5a..d3fc807a7 100644 --- a/base/debug/dump_without_crashing.h +++ b/base/debug/dump_without_crashing.h @@ -65,8 +65,11 @@ NOT_TAIL_CALLED BASE_EXPORT bool DumpWithoutCrashing( // using base::FastHash to generate the hash. // `location` Location of the file from where the function is called. // `time_between_dumps` Time until the next dump should be captured. -// Note: The unique identifier, as of now, is not comparable across different -// runs or builds and is stable only for a process lifetime. +// Note: +// - The unique identifier, as of now, is not comparable across different +// runs or builds and is stable only for a process lifetime. +// - The unique identifier is not recorded in the crash report. See +// crash_logging.h for such a purpose. NOT_TAIL_CALLED BASE_EXPORT bool DumpWithoutCrashingWithUniqueId( size_t unique_identifier, const base::Location& location = base::Location::Current(), diff --git a/base/features.cc b/base/features.cc index a94eadb8f..48f240113 100644 --- a/base/features.cc +++ b/base/features.cc @@ -22,4 +22,19 @@ BASE_FEATURE(kSupportsUserDataFlatHashMap, "SupportsUserDataFlatHashMap", FEATURE_DISABLED_BY_DEFAULT); +#if BUILDFLAG(IS_ANDROID) +// Force to enable LowEndDeviceMode partially on Android mid-range devices. +// Such devices aren't considered low-end, but we'd like experiment with +// a subset of low-end features to see if we get a good memory vs. performance +// tradeoff. +// +// TODO(crbug.com/1434873): |#if| out 32-bit before launching or going to +// high Stable %, because we will enable the feature only for <8GB 64-bit +// devices, where we didn't ship yet. However, we first need a larger +// population to collect data. +BASE_FEATURE(kPartialLowEndModeOnMidRangeDevices, + "PartialLowEndModeOnMidRangeDevices", + base::FEATURE_DISABLED_BY_DEFAULT); +#endif // BUILDFLAG(IS_ANDROID) + } // namespace base::features diff --git a/base/features.h b/base/features.h index c61e07c0a..ccd15327f 100644 --- a/base/features.h +++ b/base/features.h @@ -20,6 +20,10 @@ BASE_EXPORT BASE_DECLARE_FEATURE(kOptimizeDataUrls); BASE_EXPORT BASE_DECLARE_FEATURE(kSupportsUserDataFlatHashMap); +#if BUILDFLAG(IS_ANDROID) +BASE_EXPORT BASE_DECLARE_FEATURE(kPartialLowEndModeOnMidRangeDevices); +#endif + } // namespace base::features #endif // BASE_FEATURES_H_ diff --git a/base/files/file_path.cc b/base/files/file_path.cc index 12c684bed..59bbc6e15 100644 --- a/base/files/file_path.cc +++ b/base/files/file_path.cc @@ -789,7 +789,7 @@ int FilePath::CompareIgnoreCase(StringPieceType string1, #elif BUILDFLAG(IS_APPLE) // Mac OS X specific implementation of file string comparisons. -// cf. http://developer.apple.com/mac/library/technotes/tn/tn1150.html#UnicodeSubtleties +// cf. https://developer.apple.com/library/archive/technotes/tn/tn1150.html#UnicodeSubtleties // // "When using CreateTextEncoding to create a text encoding, you should set // the TextEncodingBase to kTextEncodingUnicodeV2_0, set the @@ -815,11 +815,12 @@ int FilePath::CompareIgnoreCase(StringPieceType string1, // Ignored characters are mapped to zero. // // cf. downloadable file linked in -// http://developer.apple.com/mac/library/technotes/tn/tn1150.html#StringComparisonAlgorithm +// https://developer.apple.com/library/archive/technotes/tn/tn1150.html#Downloads namespace { -const UInt16 lower_case_table[] = { +// clang-format off +const UInt16 lower_case_table[11 * 256] = { // High-byte indices ( == 0 iff no case mapping and no ignorables ) /* 0 */ 0x0100, 0x0200, 0x0000, 0x0300, 0x0400, 0x0500, 0x0000, 0x0000, @@ -1205,11 +1206,12 @@ const UInt16 lower_case_table[] = { /* F */ 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7, 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF, }; +// clang-format on -// Returns the next non-ignorable codepoint within string starting from the -// position indicated by index, or zero if there are no more. -// The passed-in index is automatically advanced as the characters in the input -// HFS-decomposed UTF-8 strings are read. +// Returns the next non-ignorable codepoint within `string` starting from the +// position indicated by `index`, or zero if there are no more. +// The passed-in `index` is automatically advanced as the characters in the +// input HFS-decomposed UTF-8 strings are read. inline base_icu::UChar32 HFSReadNextNonIgnorableCodepoint(const char* string, size_t length, size_t* index) { @@ -1220,12 +1222,16 @@ inline base_icu::UChar32 HFSReadNextNonIgnorableCodepoint(const char* string, CBU8_NEXT(reinterpret_cast<const uint8_t*>(string), *index, length, codepoint); DCHECK_GT(codepoint, 0); - if (codepoint > 0) { + + // Note: Here, there are no lower case conversion implemented in the + // Supplementary Multilingual Plane (codepoint > 0xFFFF). + + if (codepoint > 0 && codepoint <= 0xFFFF) { // Check if there is a subtable for this upper byte. int lookup_offset = lower_case_table[codepoint >> 8]; if (lookup_offset != 0) codepoint = lower_case_table[lookup_offset + (codepoint & 0x00FF)]; - // Note: codepoint1 may be again 0 at this point if the character was + // Note: `codepoint` may be again 0 at this point if the character was // an ignorable. } } diff --git a/base/files/file_path_unittest.cc b/base/files/file_path_unittest.cc index 7b30b15b8..9673a48bc 100644 --- a/base/files/file_path_unittest.cc +++ b/base/files/file_path_unittest.cc @@ -1203,6 +1203,13 @@ TEST_F(FilePathTest, CompareIgnoreCase) { {{FPL("K\u0301U\u032DO\u0304\u0301N"), FPL("\u1E31\u1E77\u1E53n")}, 0}, {{FPL("k\u0301u\u032Do\u0304\u0301n"), FPL("\u1E30\u1E76\u1E52n")}, 0}, {{FPL("k\u0301u\u032Do\u0304\u0302n"), FPL("\u1E30\u1E76\u1E52n")}, 1}, + + // Codepoints > 0xFFFF + // Here, we compare the `Adlam Letter Shu` in its capital and small version. + {{FPL("\U0001E921"), FPL("\U0001E943")}, -1}, + {{FPL("\U0001E943"), FPL("\U0001E921")}, 1}, + {{FPL("\U0001E921"), FPL("\U0001E921")}, 0}, + {{FPL("\U0001E943"), FPL("\U0001E943")}, 0}, #endif }; diff --git a/base/files/file_util.h b/base/files/file_util.h index 11fa046db..0b0c7df15 100644 --- a/base/files/file_util.h +++ b/base/files/file_util.h @@ -272,6 +272,8 @@ BASE_EXPORT bool ReadFromFD(int fd, char* buffer, size_t bytes); // Performs the same function as CreateAndOpenTemporaryStreamInDir(), but // returns the file-descriptor wrapped in a ScopedFD, rather than the stream // wrapped in a ScopedFILE. +// The caller is responsible for deleting the file `path` points to, if +// appropriate. BASE_EXPORT ScopedFD CreateAndOpenFdForTemporaryFileInDir(const FilePath& dir, FilePath* path); @@ -370,18 +372,25 @@ BASE_EXPORT bool GetTempDir(FilePath* path); BASE_EXPORT FilePath GetHomeDir(); // Returns a new temporary file in |dir| with a unique name. The file is opened -// for exclusive read, write, and delete access (note: exclusivity is unique to -// Windows). On Windows, the returned file supports File::DeleteOnClose. +// for exclusive read, write, and delete access. // On success, |temp_file| is populated with the full path to the created file. +// +// NOTE: Exclusivity is unique to Windows. On Windows, the returned file +// supports File::DeleteOnClose. On other platforms, the caller is responsible +// for deleting the file `temp_file` points to, if appropriate. BASE_EXPORT File CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* temp_file); -// Creates a temporary file. The full path is placed in |path|, and the +// Creates a temporary file. The full path is placed in `path`, and the // function returns true if was successful in creating the file. The file will // be empty and all handles closed after this function returns. +// The caller is responsible for deleting the file `path` points to, if +// appropriate. BASE_EXPORT bool CreateTemporaryFile(FilePath* path); -// Same as CreateTemporaryFile but the file is created in |dir|. +// Same as CreateTemporaryFile() but the file is created in `dir`. +// The caller is responsible for deleting the file `temp_file` points to, if +// appropriate. BASE_EXPORT bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file); @@ -391,11 +400,14 @@ BASE_EXPORT FilePath FormatTemporaryFileName(FilePath::StringPieceType identifier); // Create and open a temporary file stream for exclusive read, write, and delete -// access (note: exclusivity is unique to Windows). The full path is placed in -// |path|. Returns the opened file stream, or null in case of error. +// access. The full path is placed in `path`. Returns the opened file stream, or +// null in case of error. +// NOTE: Exclusivity is unique to Windows. On Windows, the returned file +// supports File::DeleteOnClose. On other platforms, the caller is responsible +// for deleting the file `path` points to, if appropriate. BASE_EXPORT ScopedFILE CreateAndOpenTemporaryStream(FilePath* path); -// Similar to CreateAndOpenTemporaryStream, but the file is created in |dir|. +// Similar to CreateAndOpenTemporaryStream(), but the file is created in `dir`. BASE_EXPORT ScopedFILE CreateAndOpenTemporaryStreamInDir(const FilePath& dir, FilePath* path); diff --git a/base/fuchsia/startup_context.cc b/base/fuchsia/startup_context.cc index e93634c29..78210c552 100644 --- a/base/fuchsia/startup_context.cc +++ b/base/fuchsia/startup_context.cc @@ -49,40 +49,6 @@ StartupContext::StartupContext( StartupContext::~StartupContext() = default; -StartupContext::StartupContext(fuchsia::sys::StartupInfo startup_info) { - std::unique_ptr<sys::ServiceDirectory> incoming_services; - - // Component manager generates |flat_namespace|, so things are horribly broken - // if |flat_namespace| is malformed. - CHECK_EQ(startup_info.flat_namespace.directories.size(), - startup_info.flat_namespace.paths.size()); - - // Find the /svc directory and wrap it into a sys::ServiceDirectory. - for (size_t i = 0; i < startup_info.flat_namespace.paths.size(); ++i) { - if (startup_info.flat_namespace.paths[i] == kServiceDirectoryPath) { - incoming_services = std::make_unique<sys::ServiceDirectory>( - std::move(startup_info.flat_namespace.directories[i])); - break; - } - } - - if (!incoming_services) { - LOG(WARNING) << "Component started without a service directory"; - - // Create a dummy ServiceDirectory with a channel that's not - // connected on the other end. - fidl::InterfaceHandle<fuchsia::io::Directory> dummy_directory; - std::ignore = dummy_directory.NewRequest(); - incoming_services = - std::make_unique<sys::ServiceDirectory>(std::move(dummy_directory)); - } - - component_context_ = - std::make_unique<sys::ComponentContext>(std::move(incoming_services)); - outgoing_directory_request_ = - std::move(startup_info.launch_info.directory_request); -} - void StartupContext::ServeOutgoingDirectory() { DCHECK(outgoing_directory_request_); component_context_->outgoing()->Serve(std::move(outgoing_directory_request_)); diff --git a/base/fuchsia/startup_context.h b/base/fuchsia/startup_context.h index 44c7236de..fbe3f25cf 100644 --- a/base/fuchsia/startup_context.h +++ b/base/fuchsia/startup_context.h @@ -7,7 +7,6 @@ #include <fuchsia/component/runner/cpp/fidl.h> #include <fuchsia/io/cpp/fidl.h> -#include <fuchsia/sys/cpp/fidl.h> #include <lib/sys/cpp/component_context.h> #include <lib/zx/channel.h> @@ -22,7 +21,7 @@ class OutgoingDirectory; namespace base { -// Helper for unpacking a fuchsia.sys.StartupInfo and creating convenience +// Helper for unpacking component start info and creating convenience // wrappers for the various fields (e.g. the incoming & outgoing service // directories, resolve launch URL etc). // Embedders may derived from StartupContext to e.g. add bound pointers to @@ -33,10 +32,6 @@ class BASE_EXPORT StartupContext final { ::fuchsia::component::runner::ComponentStartInfo start_info); ~StartupContext(); - // TODO(https://crbug.com/1065707): Remove this overload once the CFv1 - // Runner implementations are removed. - explicit StartupContext(::fuchsia::sys::StartupInfo startup_info); - StartupContext(const StartupContext&) = delete; StartupContext& operator=(const StartupContext&) = delete; diff --git a/base/functional/callback.h b/base/functional/callback.h index 189259f2d..773662ed3 100644 --- a/base/functional/callback.h +++ b/base/functional/callback.h @@ -165,7 +165,7 @@ class TRIVIAL_ABI OnceCallback<R(Args...)> { template <typename ThenR, typename... ThenArgs> OnceCallback<ThenR(Args...)> Then(OnceCallback<ThenR(ThenArgs...)> then) && { CHECK(then); - return BindOnce( + return base::BindOnce( internal::ThenHelper< OnceCallback, OnceCallback<ThenR(ThenArgs...)>>::CreateTrampoline(), std::move(*this), std::move(then)); @@ -178,7 +178,7 @@ class TRIVIAL_ABI OnceCallback<R(Args...)> { OnceCallback<ThenR(Args...)> Then( RepeatingCallback<ThenR(ThenArgs...)> then) && { CHECK(then); - return BindOnce( + return base::BindOnce( internal::ThenHelper< OnceCallback, RepeatingCallback<ThenR(ThenArgs...)>>::CreateTrampoline(), diff --git a/base/ios/scoped_critical_action.mm b/base/ios/scoped_critical_action.mm index 038da26ab..5e032d0bf 100644 --- a/base/ios/scoped_critical_action.mm +++ b/base/ios/scoped_critical_action.mm @@ -24,7 +24,7 @@ namespace ios { BASE_FEATURE(kScopedCriticalActionReuseEnabled, "ScopedCriticalActionReuseEnabled", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); namespace { diff --git a/base/ios/sim_header_shims.h b/base/ios/sim_header_shims.h index 1d30a7137..3f9f5e060 100644 --- a/base/ios/sim_header_shims.h +++ b/base/ios/sim_header_shims.h @@ -16,7 +16,8 @@ #include <sys/param.h> // This file includes the necessary headers that are not part of the -// iOS public SDK in order to support multiprocess support on iOS. +// iOS public SDK in order to support multiprocess and memory instrumentations +// on iOS. __BEGIN_DECLS @@ -40,7 +41,16 @@ const char* bootstrap_strerror(kern_return_t r); #define BOOTSTRAP_NO_MEMORY 1105 #define BOOTSTRAP_NO_CHILDREN 1106 +// These values are copied from darwin-xnu/osfmk/mach/shared_region.h. +// https://github.com/apple/darwin-xnu/blob/8f02f2a044b9bb1ad951987ef5bab20ec9486310/osfmk/mach/shared_region.h#L86-L87 +#define SHARED_REGION_BASE_ARM64 0x180000000ULL +#define SHARED_REGION_SIZE_ARM64 0x100000000ULL + int proc_pidpath(int pid, void* buffer, uint32_t buffersize); +int proc_regionfilename(int pid, + uint64_t address, + void* buffer, + uint32_t buffersize); #define PROC_PIDPATHINFO_MAXSIZE (4 * MAXPATHLEN) __END_DECLS diff --git a/base/mac/bundle_locations.h b/base/mac/bundle_locations.h index 1aea08a9c..eb4986650 100644 --- a/base/mac/bundle_locations.h +++ b/base/mac/bundle_locations.h @@ -10,9 +10,6 @@ #if defined(__OBJC__) #import <Foundation/Foundation.h> -#else // __OBJC__ -class NSBundle; -class NSURL; #endif // __OBJC__ namespace base { @@ -45,23 +42,26 @@ namespace base::mac { // bundle is probably the one to use. // Methods for retrieving the various bundles. +BASE_EXPORT FilePath MainBundlePath(); +BASE_EXPORT FilePath OuterBundlePath(); +BASE_EXPORT FilePath FrameworkBundlePath(); +#if defined(__OBJC__) BASE_EXPORT NSBundle* MainBundle(); BASE_EXPORT NSURL* MainBundleURL(); -BASE_EXPORT FilePath MainBundlePath(); BASE_EXPORT NSBundle* OuterBundle(); BASE_EXPORT NSURL* OuterBundleURL(); -BASE_EXPORT FilePath OuterBundlePath(); BASE_EXPORT NSBundle* FrameworkBundle(); -BASE_EXPORT FilePath FrameworkBundlePath(); +#endif // __OBJC__ // Set the bundle that the preceding functions will return, overriding the -// default values. Restore the default by passing in |nil|. -BASE_EXPORT void SetOverrideOuterBundle(NSBundle* bundle); -BASE_EXPORT void SetOverrideFrameworkBundle(NSBundle* bundle); - -// Same as above but accepting a FilePath argument. +// default values. Restore the default by passing in `nil` or an empty +// `FilePath`. BASE_EXPORT void SetOverrideOuterBundlePath(const FilePath& file_path); BASE_EXPORT void SetOverrideFrameworkBundlePath(const FilePath& file_path); +#if defined(__OBJC__) +BASE_EXPORT void SetOverrideOuterBundle(NSBundle* bundle); +BASE_EXPORT void SetOverrideFrameworkBundle(NSBundle* bundle); +#endif // __OBJC__ } // namespace base::mac diff --git a/base/mac/bundle_locations.mm b/base/mac/bundle_locations.mm index 6835cadc2..111be3be2 100644 --- a/base/mac/bundle_locations.mm +++ b/base/mac/bundle_locations.mm @@ -61,9 +61,11 @@ static void AssignOverrideBundle(NSBundle* new_bundle, static void AssignOverridePath(const FilePath& file_path, NSBundle** override_bundle) { - NSString* path = base::SysUTF8ToNSString(file_path.value()); - NSBundle* new_bundle = [NSBundle bundleWithPath:path]; - DCHECK(new_bundle) << "Failed to load the bundle at " << file_path.value(); + NSBundle* new_bundle = nil; + if (!file_path.empty()) { + new_bundle = [NSBundle bundleWithURL:base::mac::FilePathToNSURL(file_path)]; + CHECK(new_bundle) << "Failed to load the bundle at " << file_path.value(); + } AssignOverrideBundle(new_bundle, override_bundle); } diff --git a/base/mac/foundation_util.h b/base/mac/foundation_util.h index cabf20207..159d1ffb8 100644 --- a/base/mac/foundation_util.h +++ b/base/mac/foundation_util.h @@ -7,6 +7,8 @@ #include <AvailabilityMacros.h> #include <CoreFoundation/CoreFoundation.h> +#include <CoreText/CoreText.h> +#include <Security/Security.h> #include <string> @@ -19,33 +21,8 @@ #import <Foundation/Foundation.h> @class NSFont; @class UIFont; -#else // __OBJC__ -class NSBundle; -class NSFont; -class NSString; -class UIFont; #endif // __OBJC__ -#if BUILDFLAG(IS_IOS) -#include <CoreText/CoreText.h> -#else -#include <ApplicationServices/ApplicationServices.h> -#endif - -// Adapted from NSPathUtilities.h and NSObjCRuntime.h. -#if __LP64__ || NS_BUILD_32_LIKE_64 -enum NSSearchPathDirectory : unsigned long; -typedef unsigned long NSSearchPathDomainMask; -#else -enum NSSearchPathDirectory : unsigned int; -typedef unsigned int NSSearchPathDomainMask; -#endif - -typedef struct CF_BRIDGED_TYPE(id) __SecAccessControl* SecAccessControlRef; -typedef struct CF_BRIDGED_TYPE(id) __SecCertificate* SecCertificateRef; -typedef struct CF_BRIDGED_TYPE(id) __SecKey* SecKeyRef; -typedef struct CF_BRIDGED_TYPE(id) __SecPolicy* SecPolicyRef; - namespace base { class FilePath; } @@ -82,6 +59,8 @@ OSType CreatorCodeForCFBundleRef(CFBundleRef bundle); // app bundle's creator code anyway. BASE_EXPORT OSType CreatorCodeForApplication(); +#if defined(__OBJC__) + // Searches for directories for the given key in only the given |domain_mask|. // If found, fills result (which must always be non-NULL) with the // first found directory and returns true. Otherwise, returns false. @@ -101,6 +80,8 @@ BASE_EXPORT bool GetLocalDirectory(NSSearchPathDirectory directory, BASE_EXPORT bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result); +#endif // __OBJC__ + // Returns the ~/Library directory. BASE_EXPORT FilePath GetUserLibraryPath(); @@ -157,11 +138,7 @@ BASE_EXPORT void SetBaseBundleID(const char* new_base_bundle_id); // ARC. Use the casting functions in base/mac/bridging.h instead. #if !defined(__has_feature) || !__has_feature(objc_arc) -#if !defined(__OBJC__) -#define OBJC_CPP_CLASS_DECL(x) class x; -#else // __OBJC__ -#define OBJC_CPP_CLASS_DECL(x) -#endif // __OBJC__ +#if defined(__OBJC__) // Convert toll-free bridged CFTypes to NSTypes and vice-versa. This does not // autorelease |cf_val|. This is useful for the case where there is a CFType in @@ -176,26 +153,19 @@ BASE_EXPORT void SetBaseBundleID(const char* new_base_bundle_id); // (http://www.gotw.ca/publications/mill17.htm) so the trusty combination // of macros and function overloading is used instead. -#define CF_TO_NS_CAST_DECL(TypeCF, TypeNS) \ -OBJC_CPP_CLASS_DECL(TypeNS) \ -\ -namespace base { \ -namespace mac { \ -BASE_EXPORT TypeNS* CFToNSCast(TypeCF##Ref cf_val); \ -BASE_EXPORT TypeCF##Ref NSToCFCast(TypeNS* ns_val); \ -} \ -} +#define CF_TO_NS_CAST_DECL(TypeCF, TypeNS) \ + namespace base::mac { \ + BASE_EXPORT TypeNS* CFToNSCast(TypeCF##Ref cf_val); \ + BASE_EXPORT TypeCF##Ref NSToCFCast(TypeNS* ns_val); \ + } -#define CF_TO_NS_MUTABLE_CAST_DECL(name) \ -CF_TO_NS_CAST_DECL(CF##name, NS##name) \ -OBJC_CPP_CLASS_DECL(NSMutable##name) \ -\ -namespace base { \ -namespace mac { \ -BASE_EXPORT NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val); \ -BASE_EXPORT CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val); \ -} \ -} +#define CF_TO_NS_MUTABLE_CAST_DECL(name) \ + CF_TO_NS_CAST_DECL(CF##name, NS##name) \ + \ + namespace base::mac { \ + BASE_EXPORT NSMutable##name* CFToNSCast(CFMutable##name##Ref cf_val); \ + BASE_EXPORT CFMutable##name##Ref NSToCFCast(NSMutable##name* ns_val); \ + } // List of toll-free bridged types taken from: // http://www.cocoadev.com/index.pl?TollFreeBridged @@ -228,6 +198,8 @@ CF_TO_NS_CAST_DECL(CTFont, NSFont) #undef CF_TO_NS_MUTABLE_CAST_DECL #undef OBJC_CPP_CLASS_DECL +#endif // __OBJC__ + #endif // !defined(__has_feature) || !__has_feature(objc_arc) namespace base::mac { @@ -350,6 +322,8 @@ T GetValueFromDictionary(CFDictionaryRef dict, CFStringRef key) { return value_specific; } +#if defined(__OBJC__) + // Converts |path| to an autoreleased NSURL. Returns nil if |path| is empty. BASE_EXPORT NSURL* FilePathToNSURL(const FilePath& path); @@ -363,6 +337,8 @@ BASE_EXPORT FilePath NSStringToFilePath(NSString* str); // |url| is not of scheme "file". BASE_EXPORT FilePath NSURLToFilePath(NSURL* url); +#endif // __OBJC__ + // Converts a non-null |path| to a CFURLRef. |path| must not be empty. // // This function only uses manually-owned resources, so it does not depend on an @@ -396,11 +372,12 @@ BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, id); BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSRange); BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, SEL); -#if !BUILDFLAG(IS_IOS) +#if BUILDFLAG(IS_MAC) BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSPoint); BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSRect); BASE_EXPORT extern std::ostream& operator<<(std::ostream& o, NSSize); -#endif -#endif +#endif // IS_MAC + +#endif // __OBJC__ #endif // BASE_MAC_FOUNDATION_UTIL_H_ diff --git a/base/mac/scoped_nsautorelease_pool.h b/base/mac/scoped_nsautorelease_pool.h index 1a824cc54..d556cd5f2 100644 --- a/base/mac/scoped_nsautorelease_pool.h +++ b/base/mac/scoped_nsautorelease_pool.h @@ -6,6 +6,7 @@ #define BASE_MAC_SCOPED_NSAUTORELEASE_POOL_H_ #include "base/base_export.h" +#include "base/memory/raw_ptr_exclusion.h" #include "base/threading/thread_checker.h" namespace base::mac { @@ -42,7 +43,9 @@ class BASE_EXPORT ScopedNSAutoreleasePool { void Recycle(); private: - void* autorelease_pool_ GUARDED_BY_CONTEXT(thread_checker_); + // This field is not a raw_ptr<> because it is a pointer to an Objective-C + // object. + RAW_PTR_EXCLUSION void* autorelease_pool_ GUARDED_BY_CONTEXT(thread_checker_); THREAD_CHECKER(thread_checker_); }; diff --git a/base/mac/scoped_nsobject.h b/base/mac/scoped_nsobject.h index 710a46a6a..c8a26b223 100644 --- a/base/mac/scoped_nsobject.h +++ b/base/mac/scoped_nsobject.h @@ -5,22 +5,36 @@ #ifndef BASE_MAC_SCOPED_NSOBJECT_H_ #define BASE_MAC_SCOPED_NSOBJECT_H_ -#include <type_traits> - // Include NSObject.h directly because Foundation.h pulls in many dependencies. // (Approx 100k lines of code versus 1.5k for NSObject.h). scoped_nsobject gets // singled out because it is most typically included from other header files. #import <Foundation/NSObject.h> +#include <type_traits> +#include <utility> + #include "base/base_export.h" #include "base/compiler_specific.h" #include "base/mac/scoped_typeref.h" -#if defined(__has_feature) && __has_feature(objc_arc) -#error "Cannot include base/mac/scoped_nsobject.h in file built with ARC." +#if !defined(__has_feature) || !__has_feature(objc_arc) +@class NSAutoreleasePool; #endif -@class NSAutoreleasePool; +// Note that this uses the direct runtime interface to reference counting. +// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support +// This is so this can work when compiled for ARC. Annotations are used to lie +// about the effects of these calls so that ARC will not insert any reference +// count calls of its own and leave the maintenance of the reference count to +// this class. + +extern "C" { +id objc_retain(__unsafe_unretained id value) + __attribute__((ns_returns_not_retained)); +void objc_release(__unsafe_unretained id value); +id objc_autorelease(__unsafe_unretained id value) + __attribute__((ns_returns_not_retained)); +} namespace base { @@ -42,14 +56,21 @@ namespace base { // NSAutoreleasePool; for Objective-C(++) code use @autoreleasepool instead. We // check for bad uses of scoped_nsobject and NSAutoreleasePool at compile time // with a template specialization (see below). +// +// If Automatic Reference Counting (aka ARC) is enabled then the ownership +// policy is not controllable by the user as ARC make it really difficult to +// transfer ownership (the reference passed to scoped_nsobject constructor is +// sunk by ARC, and a design that a function will maybe consume an argument +// based on a different argument isn't expressible with attributes). Due to +// that, the policy is always to |RETAIN| when using ARC. namespace internal { template <typename NST> struct ScopedNSProtocolTraits { static NST InvalidValue() { return nil; } - static NST Retain(NST nst) { return [nst retain]; } - static void Release(NST nst) { [nst release]; } + static NST Retain(__unsafe_unretained NST nst) { return objc_retain(nst); } + static void Release(__unsafe_unretained NST nst) { objc_release(nst); } }; } // namespace internal @@ -58,11 +79,53 @@ template <typename NST> class scoped_nsprotocol : public ScopedTypeRef<NST, internal::ScopedNSProtocolTraits<NST>> { public: - using ScopedTypeRef<NST, - internal::ScopedNSProtocolTraits<NST>>::ScopedTypeRef; + using Traits = internal::ScopedNSProtocolTraits<NST>; + +#if !defined(__has_feature) || !__has_feature(objc_arc) + explicit constexpr scoped_nsprotocol( + NST object = Traits::InvalidValue(), + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : ScopedTypeRef<NST, Traits>(object, policy) {} +#else + explicit constexpr scoped_nsprotocol(NST object = Traits::InvalidValue()) + : ScopedTypeRef<NST, Traits>(object, base::scoped_policy::RETAIN) {} +#endif + + scoped_nsprotocol(const scoped_nsprotocol<NST>& that) + : ScopedTypeRef<NST, Traits>(that) {} + + template <typename NSR> + explicit scoped_nsprotocol(const scoped_nsprotocol<NSR>& that_as_subclass) + : ScopedTypeRef<NST, Traits>(that_as_subclass) {} + + scoped_nsprotocol(scoped_nsprotocol<NST>&& that) + : ScopedTypeRef<NST, Traits>(std::move(that)) {} + + scoped_nsprotocol& operator=(const scoped_nsprotocol<NST>& that) { + ScopedTypeRef<NST, Traits>::operator=(that); + return *this; + } + + void reset(const scoped_nsprotocol<NST>& that) { + ScopedTypeRef<NST, Traits>::reset(that); + } + +#if !defined(__has_feature) || !__has_feature(objc_arc) + void reset(NST object = Traits::InvalidValue(), + base::scoped_policy::OwnershipPolicy policy = + base::scoped_policy::ASSUME) { + ScopedTypeRef<NST, Traits>::reset(object, policy); + } +#else + void reset(NST object = Traits::InvalidValue()) { + ScopedTypeRef<NST, Traits>::reset(object, base::scoped_policy::RETAIN); + } +#endif // Shift reference to the autorelease pool to be released later. - NST autorelease() { return [this->release() autorelease]; } + NST autorelease() __attribute__((ns_returns_not_retained)) { + return objc_autorelease(this->release()); + } }; // Free functions @@ -84,17 +147,97 @@ bool operator!=(C p1, const scoped_nsprotocol<C>& p2) { template <typename NST> class scoped_nsobject : public scoped_nsprotocol<NST*> { public: - using scoped_nsprotocol<NST*>::scoped_nsprotocol; + using Traits = typename scoped_nsprotocol<NST*>::Traits; + +#if !defined(__has_feature) || !__has_feature(objc_arc) + explicit constexpr scoped_nsobject( + NST* object = Traits::InvalidValue(), + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : scoped_nsprotocol<NST*>(object, policy) {} +#else + explicit constexpr scoped_nsobject(NST* object = Traits::InvalidValue()) + : scoped_nsprotocol<NST*>(object) {} +#endif + scoped_nsobject(const scoped_nsobject<NST>& that) + : scoped_nsprotocol<NST*>(that) {} + + template <typename NSR> + explicit scoped_nsobject(const scoped_nsobject<NSR>& that_as_subclass) + : scoped_nsprotocol<NST*>(that_as_subclass) {} + + scoped_nsobject(scoped_nsobject<NST>&& that) + : scoped_nsprotocol<NST*>(std::move(that)) {} + + scoped_nsobject& operator=(const scoped_nsobject<NST>& that) { + scoped_nsprotocol<NST*>::operator=(that); + return *this; + } + + void reset(const scoped_nsobject<NST>& that) { + scoped_nsprotocol<NST*>::reset(that); + } + +#if !defined(__has_feature) || !__has_feature(objc_arc) + void reset(NST* object = Traits::InvalidValue(), + base::scoped_policy::OwnershipPolicy policy = + base::scoped_policy::ASSUME) { + scoped_nsprotocol<NST*>::reset(object, policy); + } +#else + void reset(NST* object = Traits::InvalidValue()) { + scoped_nsprotocol<NST*>::reset(object); + } +#endif + +#if !defined(__has_feature) || !__has_feature(objc_arc) static_assert(std::is_same<NST, NSAutoreleasePool>::value == false, "Use @autoreleasepool instead"); +#endif }; // Specialization to make scoped_nsobject<id> work. template<> class scoped_nsobject<id> : public scoped_nsprotocol<id> { public: - using scoped_nsprotocol<id>::scoped_nsprotocol; + using Traits = typename scoped_nsprotocol<id>::Traits; + +#if !defined(__has_feature) || !__has_feature(objc_arc) + explicit constexpr scoped_nsobject( + id object = Traits::InvalidValue(), + base::scoped_policy::OwnershipPolicy policy = base::scoped_policy::ASSUME) + : scoped_nsprotocol<id>(object, policy) {} +#else + explicit constexpr scoped_nsobject(id object = Traits::InvalidValue()) + : scoped_nsprotocol<id>(object) {} +#endif + + scoped_nsobject(const scoped_nsobject<id>& that) = default; + + template <typename NSR> + explicit scoped_nsobject(const scoped_nsobject<NSR>& that_as_subclass) + : scoped_nsprotocol<id>(that_as_subclass) {} + + scoped_nsobject(scoped_nsobject<id>&& that) + : scoped_nsprotocol<id>(std::move(that)) {} + + scoped_nsobject& operator=(const scoped_nsobject<id>& that) = default; + + void reset(const scoped_nsobject<id>& that) { + scoped_nsprotocol<id>::reset(that); + } + +#if !defined(__has_feature) || !__has_feature(objc_arc) + void reset(id object = Traits::InvalidValue(), + base::scoped_policy::OwnershipPolicy policy = + base::scoped_policy::ASSUME) { + scoped_nsprotocol<id>::reset(object, policy); + } +#else + void reset(id object = Traits::InvalidValue()) { + scoped_nsprotocol<id>::reset(object); + } +#endif }; } // namespace base diff --git a/base/mac/scoped_nsobject_unittest_arc.mm b/base/mac/scoped_nsobject_unittest_arc.mm new file mode 100644 index 000000000..f5f0046f1 --- /dev/null +++ b/base/mac/scoped_nsobject_unittest_arc.mm @@ -0,0 +1,176 @@ +// Copyright 2012 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <CoreFoundation/CoreFoundation.h> + +#include <vector> + +#import "base/mac/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +// A note about these tests. +// +// There are a lot of "autoreleasepool" scopes. Why? `ScopedTypeRef<>::get()` is +// a function returning an unretained Obj-C object, so ARC ends the call with +// `objc_retainAutoreleaseReturnValue()` hoping to make a hand-off to the caller +// using `objc_retainAutoreleasedReturnValue()` to maintain the reference count. +// Unfortunately, a simple call to `get()` won't take the hand-off, so the +// result is an extra retain and pending autorelease. +// +// For tests that aren't doing retain count testing, this doesn't matter. For +// tests that are doing retain count testing, all calls to `get()` have to be +// made within an autorelease pool to ensure that those pending autoreleases +// don't mess up the count. +// +// For further reading: +// https://www.mikeash.com/pyblog/friday-qa-2014-05-09-when-an-autorelease-isnt.html + +namespace { + +template <typename NST> +CFIndex GetRetainCount(const base::scoped_nsobject<NST>& nst) { + @autoreleasepool { + // -1 to compensate for the `get()` call creating an extra retain and + // pending autorelease. + return CFGetRetainCount((__bridge CFTypeRef)nst.get()) - 1; + } +} + +TEST(ScopedNSObjectTestARC, DefaultPolicyIsRetain) { + id __weak o; + @autoreleasepool { + base::scoped_nsprotocol<id> p([[NSObject alloc] init]); + o = p.get(); + ASSERT_EQ(o, p.get()); + } + ASSERT_EQ(o, nil); +} + +TEST(ScopedNSObjectTestARC, ScopedNSObject) { + base::scoped_nsobject<NSObject> p1([[NSObject alloc] init]); + @autoreleasepool { + EXPECT_TRUE(p1.get()); + EXPECT_TRUE(p1.get()); + } + EXPECT_EQ(1, GetRetainCount(p1)); + EXPECT_EQ(1, GetRetainCount(p1)); + base::scoped_nsobject<NSObject> p2(p1); + @autoreleasepool { + EXPECT_EQ(p1.get(), p2.get()); + } + EXPECT_EQ(2, GetRetainCount(p1)); + p2.reset(); + EXPECT_EQ(nil, p2.get()); + EXPECT_EQ(1, GetRetainCount(p1)); + { + base::scoped_nsobject<NSObject> p3 = p1; + @autoreleasepool { + EXPECT_EQ(p1.get(), p3.get()); + } + EXPECT_EQ(2, GetRetainCount(p1)); + @autoreleasepool { + p3 = p1; + EXPECT_EQ(p1.get(), p3.get()); + } + EXPECT_EQ(2, GetRetainCount(p1)); + } + EXPECT_EQ(1, GetRetainCount(p1)); + base::scoped_nsobject<NSObject> p4; + @autoreleasepool { + p4 = base::scoped_nsobject<NSObject>(p1.get()); + } + EXPECT_EQ(2, GetRetainCount(p1)); + @autoreleasepool { + EXPECT_TRUE(p1 == p1.get()); + EXPECT_TRUE(p1 == p1); + EXPECT_FALSE(p1 != p1); + EXPECT_FALSE(p1 != p1.get()); + } + base::scoped_nsobject<NSObject> p5([[NSObject alloc] init]); + @autoreleasepool { + EXPECT_TRUE(p1 != p5); + EXPECT_TRUE(p1 != p5.get()); + EXPECT_FALSE(p1 == p5); + EXPECT_FALSE(p1 == p5.get()); + } + + base::scoped_nsobject<NSObject> p6 = p1; + EXPECT_EQ(3, GetRetainCount(p6)); + @autoreleasepool { + p6.autorelease(); + EXPECT_EQ(nil, p6.get()); + } + EXPECT_EQ(2, GetRetainCount(p1)); +} + +TEST(ScopedNSObjectTestARC, ScopedNSObjectInContainer) { + base::scoped_nsobject<id> p([[NSObject alloc] init]); + @autoreleasepool { + EXPECT_TRUE(p.get()); + } + EXPECT_EQ(1, GetRetainCount(p)); + @autoreleasepool { + std::vector<base::scoped_nsobject<id>> objects; + objects.push_back(p); + EXPECT_EQ(2, GetRetainCount(p)); + @autoreleasepool { + EXPECT_EQ(p.get(), objects[0].get()); + } + objects.push_back(base::scoped_nsobject<id>([[NSObject alloc] init])); + @autoreleasepool { + EXPECT_TRUE(objects[1].get()); + } + EXPECT_EQ(1, GetRetainCount(objects[1])); + } + EXPECT_EQ(1, GetRetainCount(p)); +} + +TEST(ScopedNSObjectTestARC, ScopedNSObjectFreeFunctions) { + base::scoped_nsobject<id> p1([[NSObject alloc] init]); + id o1 = p1.get(); + EXPECT_TRUE(o1 == p1); + EXPECT_FALSE(o1 != p1); + base::scoped_nsobject<id> p2([[NSObject alloc] init]); + EXPECT_TRUE(o1 != p2); + EXPECT_FALSE(o1 == p2); + id o2 = p2.get(); + swap(p1, p2); + EXPECT_EQ(o2, p1.get()); + EXPECT_EQ(o1, p2.get()); +} + +TEST(ScopedNSObjectTestARC, ResetWithAnotherScopedNSObject) { + // This test uses __unsafe_unretained because it holds raw pointers to do + // comparisons of them. + + base::scoped_nsobject<id> p1([[NSObject alloc] init]); + id __unsafe_unretained o1; + @autoreleasepool { + o1 = p1.get(); + } + + id __unsafe_unretained o2; + { + base::scoped_nsobject<id> p2([[NSObject alloc] init]); + @autoreleasepool { + o2 = p2.get(); + } + p1.reset(p2); + EXPECT_EQ(2u, GetRetainCount(p1)); + } + + @autoreleasepool { + EXPECT_NE(o1, p1.get()); + EXPECT_EQ(o2, p1.get()); + EXPECT_NE(p1.get(), nil); + } + + EXPECT_EQ(1u, GetRetainCount(p1)); +} + +} // namespace diff --git a/base/mac/scoped_typeref.h b/base/mac/scoped_typeref.h index ac72cfdc0..8e18d145d 100644 --- a/base/mac/scoped_typeref.h +++ b/base/mac/scoped_typeref.h @@ -50,7 +50,7 @@ struct ScopedTypeRefTraits; template<typename T, typename Traits = ScopedTypeRefTraits<T>> class ScopedTypeRef { public: - using element_type = T; + using element_type = __unsafe_unretained T; explicit constexpr ScopedTypeRef( element_type object = Traits::InvalidValue(), @@ -118,9 +118,13 @@ class ScopedTypeRef { return object_ != that.object_; } - operator element_type() const { return object_; } + operator element_type() const __attribute__((ns_returns_not_retained)) { + return object_; + } - element_type get() const { return object_; } + element_type get() const __attribute__((ns_returns_not_retained)) { + return object_; + } void swap(ScopedTypeRef& that) { element_type temp = that.object_; diff --git a/base/memory/madv_free_discardable_memory_posix.cc b/base/memory/madv_free_discardable_memory_posix.cc index a19747e04..a72aee6cf 100644 --- a/base/memory/madv_free_discardable_memory_posix.cc +++ b/base/memory/madv_free_discardable_memory_posix.cc @@ -24,6 +24,10 @@ #include "base/tracing_buildflags.h" #include "build/build_config.h" +#if BUILDFLAG(IS_ANDROID) +#include <sys/prctl.h> +#endif + #if BUILDFLAG(ENABLE_BASE_TRACING) #include "base/trace_event/memory_allocator_dump.h" // no-presubmit-check #include "base/trace_event/memory_dump_manager.h" // no-presubmit-check @@ -38,9 +42,16 @@ namespace { constexpr intptr_t kPageMagicCookie = 1; void* AllocatePages(size_t size_in_pages) { - void* data = mmap(nullptr, size_in_pages * base::GetPageSize(), - PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + const size_t length = size_in_pages * base::GetPageSize(); + void* data = mmap(nullptr, length, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); PCHECK(data != MAP_FAILED); + +#if BUILDFLAG(IS_ANDROID) + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, data, length, + "madv-free-discardable"); +#endif + return data; } diff --git a/base/memory/rust_cfg_win_unittest.cc b/base/memory/rust_cfg_win_unittest.cc index 2abfb2e82..020fde1fa 100644 --- a/base/memory/rust_cfg_win_unittest.cc +++ b/base/memory/rust_cfg_win_unittest.cc @@ -18,8 +18,9 @@ namespace { TEST(RustCfgWin, CfgCatchesInvalidIndirectCall) { base::LaunchOptions o; o.start_hidden = true; + // From //build/rust/tests/test_control_flow_guard. base::CommandLine cmd(base::FilePath( - FILE_PATH_LITERAL(RUST_CFG_WIN_UNITTEST_PATH_TO_RUST_EXE))); + FILE_PATH_LITERAL("test_control_flow_guard.exe"))); base::Process proc = base::LaunchProcess(cmd, o); int exit_code; EXPECT_TRUE(proc.WaitForExit(&exit_code)); diff --git a/base/message_loop/fd_watch_controller_posix_unittest.cc b/base/message_loop/fd_watch_controller_posix_unittest.cc index 766f39264..87fdb1652 100644 --- a/base/message_loop/fd_watch_controller_posix_unittest.cc +++ b/base/message_loop/fd_watch_controller_posix_unittest.cc @@ -131,10 +131,10 @@ class CallClosureHandler : public MessagePumpForIO::FdWatcher { TEST_F(FdWatchControllerPosixTest, FileDescriptorWatcherOutlivesMessageLoop) { // Simulate a MessageLoop that dies before an FileDescriptorWatcher. // This could happen when people use the Singleton pattern or atexit. + TestHandler handler; // Arrange for watcher to live longer than message loop. MessagePumpForIO::FdWatchController watcher(FROM_HERE); - TestHandler handler; { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); @@ -329,10 +329,10 @@ TEST_P(MessageLoopForIoPosixReadAndWriteTest, AfterWrite) { // Verify that basic readable notification works. TEST_F(FdWatchControllerPosixTest, WatchReadable) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); TestHandler handler; // Watch the pipe for readability. + MessagePumpForIO::FdWatchController watcher(FROM_HERE); ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor( read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ, &watcher, &handler)); @@ -355,10 +355,10 @@ TEST_F(FdWatchControllerPosixTest, WatchReadable) { // Verify that watching a file descriptor for writability succeeds. TEST_F(FdWatchControllerPosixTest, WatchWritable) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); TestHandler handler; // Watch the pipe for writability. + MessagePumpForIO::FdWatchController watcher(FROM_HERE); ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor( write_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_WRITE, &watcher, &handler)); @@ -378,10 +378,10 @@ TEST_F(FdWatchControllerPosixTest, WatchWritable) { // Verify that RunUntilIdle() receives IO notifications. TEST_F(FdWatchControllerPosixTest, RunUntilIdle) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); TestHandler handler; // Watch the pipe for readability. + MessagePumpForIO::FdWatchController watcher(FROM_HERE); ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor( read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ, &watcher, &handler)); @@ -426,12 +426,12 @@ TEST_F(FdWatchControllerPosixTest, StopFromHandler) { // Verify that non-persistent watcher is called only once. TEST_F(FdWatchControllerPosixTest, NonPersistentWatcher) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); RunLoop run_loop; CallClosureHandler handler(run_loop.QuitClosure(), OnceClosure()); // Create a non-persistent watcher. + MessagePumpForIO::FdWatchController watcher(FROM_HERE); ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor( read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ, &watcher, &handler)); @@ -447,12 +447,12 @@ TEST_F(FdWatchControllerPosixTest, NonPersistentWatcher) { // Verify that persistent watcher is called every time the event is triggered. TEST_F(FdWatchControllerPosixTest, PersistentWatcher) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); RunLoop run_loop1; CallClosureHandler handler(run_loop1.QuitClosure(), OnceClosure()); // Create persistent watcher. + MessagePumpForIO::FdWatchController watcher(FROM_HERE); ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor( read_fd_.get(), /*persistent=*/true, MessagePumpForIO::WATCH_READ, &watcher, &handler)); @@ -485,11 +485,12 @@ void StopWatchingAndWatchAgain(MessagePumpForIO::FdWatchController* controller, // Verify that a watcher can be stopped and reused from an event handler. TEST_F(FdWatchControllerPosixTest, StopAndRestartFromHandler) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); RunLoop run_loop1; RunLoop run_loop2; CallClosureHandler handler2(run_loop2.QuitClosure(), OnceClosure()); + MessagePumpForIO::FdWatchController watcher(FROM_HERE); + CallClosureHandler handler1(BindOnce(&StopWatchingAndWatchAgain, &watcher, read_fd_.get(), &handler2, &run_loop1), OnceClosure()); @@ -511,7 +512,6 @@ TEST_F(FdWatchControllerPosixTest, StopAndRestartFromHandler) { // Verify that the pump properly handles a delayed task after an IO event. TEST_F(FdWatchControllerPosixTest, IoEventThenTimer) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); RunLoop timer_run_loop; env.GetMainThreadTaskRunner()->PostDelayedTask( @@ -521,6 +521,7 @@ TEST_F(FdWatchControllerPosixTest, IoEventThenTimer) { CallClosureHandler handler(watcher_run_loop.QuitClosure(), OnceClosure()); // Create a non-persistent watcher. + MessagePumpForIO::FdWatchController watcher(FROM_HERE); ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor( read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ, &watcher, &handler)); @@ -540,7 +541,6 @@ TEST_F(FdWatchControllerPosixTest, IoEventThenTimer) { // Verify that the pipe can handle an IO event after a delayed task. TEST_F(FdWatchControllerPosixTest, TimerThenIoEvent) { test::TaskEnvironment env(test::TaskEnvironment::MainThreadType::IO); - MessagePumpForIO::FdWatchController watcher(FROM_HERE); // Trigger read event from a delayed task. env.GetMainThreadTaskRunner()->PostDelayedTask( @@ -552,6 +552,7 @@ TEST_F(FdWatchControllerPosixTest, TimerThenIoEvent) { CallClosureHandler handler(run_loop.QuitClosure(), OnceClosure()); // Create a non-persistent watcher. + MessagePumpForIO::FdWatchController watcher(FROM_HERE); ASSERT_TRUE(CurrentIOThread::Get()->WatchFileDescriptor( read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ, &watcher, &handler)); diff --git a/base/message_loop/message_pump_glib.cc b/base/message_loop/message_pump_glib.cc index ca2dd6b9b..dfc8a9555 100644 --- a/base/message_loop/message_pump_glib.cc +++ b/base/message_loop/message_pump_glib.cc @@ -103,6 +103,153 @@ bool RunningOnMainThread() { // we run DoWork. That part will also run in the other event pumps. // - We also run DoWork, and possibly DoIdleWork, in the main loop, // around event handling. +// +// --------------------------------------------------------------------------- +// +// An overview on the way that we track work items: +// +// ScopedDoWorkItems are used by this pump to track native work. They are +// stored by value in |state_| and are set/cleared as the pump runs. Their +// setting and clearing is done in the functions +// {Set,Clear,EnsureSet,EnsureCleared}ScopedWorkItem. Control flow in GLib is +// quite non-obvious because chrome is not notified when a nested loop is +// entered/exited. To detect nested loops, MessagePumpGlib uses +// |state_->do_work_depth| which is incremented when DoWork is entered, and a +// GLib library function, g_main_depth(), which indicates the current number of +// Dispatch() calls on the stack. To react to them, two separate +// ScopedDoWorkItems are used (a standard one used for all native work, and a +// second one used exclusively for forcing nesting when there is a native loop +// spinning). Note that `ThreadController` flags all nesting as +// `Phase::kNested` so separating native and application work while nested isn't +// supported nor a goal. +// +// It should also be noted that a second GSource has been added to GLib, +// referred to as the "observer" source. It is used because in the case where +// native work occurs on wakeup that is higher priority than Chrome (all of +// GTK), chrome won't even get notified that the pump is awake. +// +// There are several cases to consider wrt. nesting level and order. In +// order, we have: +// A. [root] -> MessagePump::Run() -> native event -> g_main_context_iteration +// B. [root] -> MessagePump::Run() -> DoWork -> g_main_context_iteration +// C. [root] -> native -> DoWork -> MessagePump -> [...] +// The second two cases are identical for our purposes, and the last one turns +// out to be handled without any extra headache. +// +// Consider nesting case A, where native work is called from +// |g_main_context_iteration()| from the pump, and that native work spins up a +// loop. For our purposes, this is a nested loop, because control is not +// returned to the pump once one iteration of the pump is complete. In this +// case, the pump needs to enter nesting without DoWork being involved at +// all. This is accomplished using |MessagePumpGlib::NestIfRequired()|, which is +// called during the Prepare() phase of GLib. As the pump records state on entry +// and exit from GLib using |OnEntryToGlib| and |OnExitFromGlib|, we can compare +// |g_main_depth| at |HandlePrepare| with the one before we entered +// |g_main_context_iteration|. If it is higher, there is a native loop being +// spun, and |RegisterNesting| is called, forcing nesting by initializing two +// work items at once. These are destroyed after the exit from +// |g_main_context_iteration| using |OnExitFromGlib|. +// +// Then, considering nesting case B, |state_->do_work_depth| is incremented +// during any Chrome work, to allow the pump to detect re-entrancy during a +// chrome work item. This is required because `g_main_depth` is not incremented +// in any `DoWork` call not occuring during `Dispatch()` (i.e. during +// `MessagePumpGlib::Run()`). In this case, a nested loop is recorded, and the +// pump sets-and-clears scoped work items during Prepare, Check, and Dispatch. A +// work item can never be active when control flow returns to GLib (i.e. on +// return) during a nested loop, because the nested loop could exit at any +// point. This is fine because TimeKeeper is only concerned with the fact that a +// nested loop is in progress, as opposed to the various phases of the nested +// loop. +// +// Finally, consider nesting case C, where a native loop is spinning +// entirely outside of Chrome, such as inside a signal handler, the pump might +// create and destroy DoWorkItems during Prepare() and Check(), but these work +// items will always get cleared during Dispatch(), before the pump enters a +// DoWork(), leading to the pump showing non-nested native work without the +// thread controller being active, the correct situation (which won't occur +// outside of startup or shutdown). Once Dispatch() is called, the pump's +// nesting tracking works correctly, as state_->do_work_depth is increased, and +// upon re-entrancy we detect the nested loop, which is correct, as this is the +// only point at which the loop actually becomes "nested". +// +// ----------------------------------------------------------------------------- +// +// As an overview of the steps taken by MessagePumpGLib to ensure that nested +// loops are detected adequately during each phase of the GLib loop: +// +// 0: Before entering GLib: +// 0.1: Record state about current state of GLib (g_main_depth()) for +// case 1.1.2. +// +// 1: Prepare. +// 1.1: Detection of nested loops + +// 1.1.1: If |state_->do_work_depth| > 0, we are in nesting case B detailed +// above. A work item must be newly created during this function to +// trigger nesting, and is destroyed to ensure proper destruction order +// in the case where GLib quits after Prepare(). +// +// 1.1.2: Otherwise, check if we are in nesting case A above. If yes, trigger +// nesting using ScopedDoWorkItems. The nesting will be cleared at exit +// from GLib. +// +// This check occurs only in |HandleObserverPrepare|, not in +// |HandlePrepare|. +// +// A third party is running a glib message loop. Since Chrome work is +// registered with GLib at |G_PRIORITY_DEFAULT_IDLE|, a relatively low +// priority, sources of default-or-higher priority will be Dispatch()ed +// first. Since only one source is Dispatched per loop iteration, +// |HandlePrepare| can get called several times in a row in the case that +// there are any other events in the queue. A ScopedDoWorkItem is created +// and destroyed to record this. That work item triggers nesting. +// +// 1.2: Other considerations +// 1.2.1: Sleep occurs between Prepare() and Check(). If Chrome will pass a +// nonzero poll time to GLib, the inner ScopedDoWorkItem is cleared and +// BeforeWait() is called. In nesting case A, the nesting work item will +// not be cleared. A nested loop will typically not block. +// +// Since Prepare() is called before Check() in all cases, the bulk of +// nesting detection is done in Prepare(). +// +// 2: Check. +// 2.1: Detection of nested loops: +// 2.1.1: In nesting case B, |ClearScopedWorkItem()| on exit. A third party is +// running a glib message loop. It is possible that at any point the +// nested message loop will quit. In this case, we don't want to leave a +// nested DoWorkItem on the stack. +// +// 2.2: Other considerations +// 2.2.1: A ScopedDoWorkItem may be created (if it was not already present) at +// the entry to Check() to record a wakeup in the case that the pump +// slept. It is important to note that this occurs both in +// |HandleObserverCheck| and |HandleCheck| to ensure that at every point +// as the pump enters the Dispatch phase it is awake. In the case it is +// already awake, this is a very cheap operation. +// +// 3: Dispatch +// 3.1 Detection of nested loops +// 3.1.1: |state_->do_work_depth| is incremented on entry and decremented on +// exit. This is used to detect nesting case B. +// +// 3.1.2: Nested loops can be quit at any point, and so ScopedDoWorkItems can't +// be left on the stack for the same reasons as in 1.1.1/2.1.1. +// +// 3.2 Other considerations +// 3.2.1: Since DoWork creates its own work items, ScopedDoWorkItems are not +// used as this would trigger nesting in all cases. +// +// 4: Post GLib +// 4.1: Detection of nested loops +// 4.1.1: |state_->do_work_depth| is also increased during the DoWork in Run() +// as nesting in that case [calling glib from third party code] needs to +// clear all work items after return to avoid improper destruction order. +// +// 4.2: Other considerations: +// 4.2.1: DoWork uses its own work item, so no ScopedDoWorkItems are active in +// this case. struct WorkSource : public GSource { raw_ptr<MessagePumpGlib> pump; @@ -130,8 +277,28 @@ gboolean WorkSourceDispatch(GSource* source, } // I wish these could be const, but g_source_new wants non-const. -GSourceFuncs WorkSourceFuncs = {WorkSourcePrepare, WorkSourceCheck, - WorkSourceDispatch, nullptr}; +GSourceFuncs g_work_source_funcs = {WorkSourcePrepare, WorkSourceCheck, + WorkSourceDispatch, nullptr}; + +struct ObserverSource : public GSource { + raw_ptr<MessagePumpGlib> pump; +}; + +gboolean ObserverPrepare(GSource* gsource, gint* timeout_ms) { + auto* source = static_cast<ObserverSource*>(gsource); + source->pump->HandleObserverPrepare(); + *timeout_ms = -1; + // We always want to poll. + return FALSE; +} + +gboolean ObserverCheck(GSource* gsource) { + auto* source = static_cast<ObserverSource*>(gsource); + return source->pump->HandleObserverCheck(); +} + +GSourceFuncs g_observer_funcs = {ObserverPrepare, ObserverCheck, nullptr, + nullptr}; struct FdWatchSource : public GSource { raw_ptr<MessagePumpGlib> pump; @@ -162,7 +329,7 @@ GSourceFuncs g_fd_watch_source_funcs = { } // namespace struct MessagePumpGlib::RunState { - explicit RunState(raw_ptr<Delegate> delegate) : delegate(delegate) { + explicit RunState(Delegate* delegate) : delegate(delegate) { CHECK(delegate); } @@ -171,6 +338,25 @@ struct MessagePumpGlib::RunState { // Used to flag that the current Run() invocation should return ASAP. bool should_quit = false; + // Keeps track of the number of calls to DoWork() on the stack for the current + // Run() invocation. Used to detect reentrancy from DoWork in order to make + // decisions about tracking nested work. + int do_work_depth = 0; + + // Value of g_main_depth() captured before the call to + // g_main_context_iteration() in Run(). nullopt if Run() is not calling + // g_main_context_iteration(). Used to track whether the pump has forced a + // nested state due to a native pump. + absl::optional<int> g_depth_on_iteration; + + // Used to keep track of the native event work items processed by the message + // pump. + Delegate::ScopedDoWorkItem scoped_do_work_item; + + // Used to force the pump into a nested state when a native runloop was + // dispatched from main. + Delegate::ScopedDoWorkItem native_loop_do_work_item; + // The information of the next task available at this run-level. Stored in // RunState because different set of tasks can be accessible at various // run-levels (e.g. non-nestable tasks). @@ -199,8 +385,13 @@ MessagePumpGlib::MessagePumpGlib() wakeup_gpollfd_->fd = wakeup_pipe_read_; wakeup_gpollfd_->events = G_IO_IN; + observer_source_ = std::unique_ptr<GSource, GSourceDeleter>( + g_source_new(&g_observer_funcs, sizeof(ObserverSource))); + static_cast<ObserverSource*>(observer_source_.get())->pump = this; + g_source_attach(observer_source_.get(), context_); + work_source_ = std::unique_ptr<GSource, GSourceDeleter>( - g_source_new(&WorkSourceFuncs, sizeof(WorkSource))); + g_source_new(&g_work_source_funcs, sizeof(WorkSource))); static_cast<WorkSource*>(work_source_.get())->pump = this; g_source_add_poll(work_source_.get(), wakeup_gpollfd_.get()); g_source_set_priority(work_source_.get(), kPriorityWork); @@ -327,19 +518,74 @@ bool MessagePumpGlib::WatchFileDescriptor(int fd, return controller->Attach(this); } +void MessagePumpGlib::HandleObserverPrepare() { + // |state_| may be null during tests. + if (!state_) { + return; + } + + if (state_->do_work_depth > 0) { + // Contingency 1.1.1 detailed above + SetScopedWorkItem(); + ClearScopedWorkItem(); + } else { + // Contingency 1.1.2 detailed above + NestIfRequired(); + } + + return; +} + +bool MessagePumpGlib::HandleObserverCheck() { + // |state_| may be null in tests. + if (!state_) { + return FALSE; + } + + // Make sure we record the fact that we're awake. Chrome won't get Check()ed + // if a higher priority work item returns TRUE from Check(). + EnsureSetScopedWorkItem(); + if (state_->do_work_depth > 0) { + // Contingency 2.1.1 + ClearScopedWorkItem(); + } + + // The observer never needs to run anything. + return FALSE; +} + // Return the timeout we want passed to poll. int MessagePumpGlib::HandlePrepare() { // |state_| may be null during tests. if (!state_) return 0; - return GetTimeIntervalMilliseconds(state_->next_work_info.delayed_run_time); + const int next_wakeup_millis = + GetTimeIntervalMilliseconds(state_->next_work_info.delayed_run_time); + if (next_wakeup_millis != 0) { + // When this is called, it is not possible to know for sure if a + // ScopedWorkItem is on the stack, because HandleObserverCheck may have set + // it during an iteration of the pump where a high priority native work item + // executed. + EnsureClearedScopedWorkItem(); + state_->delegate->BeforeWait(); + } + + return next_wakeup_millis; } bool MessagePumpGlib::HandleCheck() { if (!state_) // state_ may be null during tests. return false; + // Ensure pump is awake. + EnsureSetScopedWorkItem(); + + if (state_->do_work_depth > 0) { + // Contingency 2.1.1 + ClearScopedWorkItem(); + } + // We usually have a single message on the wakeup pipe, since we are only // signaled when the queue went from empty to non-empty, but there can be // two messages if a task posted a task, hence we read at most two bytes. @@ -371,7 +617,18 @@ bool MessagePumpGlib::HandleCheck() { } void MessagePumpGlib::HandleDispatch() { + // Contingency 3.2.1 + EnsureClearedScopedWorkItem(); + + // Contingency 3.1.1 + ++state_->do_work_depth; state_->next_work_info = state_->delegate->DoWork(); + --state_->do_work_depth; + + if (state_ && state_->do_work_depth > 0) { + // Contingency 3.1.2 + EnsureClearedScopedWorkItem(); + } } void MessagePumpGlib::Run(Delegate* delegate) { @@ -391,14 +648,28 @@ void MessagePumpGlib::Run(Delegate* delegate) { // callbacks. This is so we only quit our own loops, and we don't quit // nested loops run by others. TODO(deanm): Is this what we want? for (;;) { + // ScopedWorkItem to account for any native work until the runloop starts + // running chrome work. + SetScopedWorkItem(); + // Don't block if we think we have more work to do. bool block = !more_work_is_plausible; + OnEntryToGlib(); more_work_is_plausible = g_main_context_iteration(context_, block); + OnExitFromGlib(); + if (state_->should_quit) break; + // Contingency 4.2.1 + EnsureClearedScopedWorkItem(); + + // Contingency 4.1.1 + ++state_->do_work_depth; state_->next_work_info = state_->delegate->DoWork(); + --state_->do_work_depth; + more_work_is_plausible |= state_->next_work_info.is_immediate(); if (state_->should_quit) break; @@ -471,4 +742,128 @@ bool MessagePumpGlib::ShouldQuit() const { return state_->should_quit; } +void MessagePumpGlib::SetScopedWorkItem() { + // |state_| can be null during tests + if (!state_) { + return; + } + // If there exists a ScopedDoWorkItem in the current RunState, it cannot be + // overwritten. + CHECK(state_->scoped_do_work_item.IsNull()); + + // In the case that we're more than two work items deep, don't bother tracking + // individual native events anymore. Note that this won't cause out-of-order + // end work items, because the work item is cleared before entering the second + // DoWork(). + if (state_->do_work_depth < 2) { + state_->scoped_do_work_item = state_->delegate->BeginWorkItem(); + } +} + +void MessagePumpGlib::ClearScopedWorkItem() { + // |state_| can be null during tests + if (!state_) { + return; + } + + CHECK(!state_->scoped_do_work_item.IsNull()); + // See identical check in SetScopedWorkItem + if (state_->do_work_depth < 2) { + state_->scoped_do_work_item = Delegate::ScopedDoWorkItem(); + } +} + +void MessagePumpGlib::EnsureSetScopedWorkItem() { + // |state_| can be null during tests + if (!state_) { + return; + } + if (state_->scoped_do_work_item.IsNull()) { + SetScopedWorkItem(); + } +} + +void MessagePumpGlib::EnsureClearedScopedWorkItem() { + // |state_| can be null during tests + if (!state_) { + return; + } + if (!state_->scoped_do_work_item.IsNull()) { + ClearScopedWorkItem(); + } +} + +void MessagePumpGlib::RegisterNested() { + // |state_| can be null during tests + if (!state_) { + return; + } + CHECK(state_->native_loop_do_work_item.IsNull()); + + // Transfer `scoped_do_work_item` to `native_do_work_item`, and so the + // ephemeral `scoped_do_work_item` will be coming in and out of existence on + // top of `native_do_work_item`, whose state hasn't been deleted. + + if (state_->scoped_do_work_item.IsNull()) { + state_->native_loop_do_work_item = state_->delegate->BeginWorkItem(); + } else { + // This clears state_->scoped_do_work_item. + state_->native_loop_do_work_item = std::move(state_->scoped_do_work_item); + } + SetScopedWorkItem(); + ClearScopedWorkItem(); +} + +void MessagePumpGlib::UnregisterNested() { + // |state_| can be null during tests + if (!state_) { + return; + } + CHECK(!state_->native_loop_do_work_item.IsNull()); + + EnsureClearedScopedWorkItem(); + // Nesting exits here. + state_->native_loop_do_work_item = Delegate::ScopedDoWorkItem(); +} + +void MessagePumpGlib::NestIfRequired() { + // |state_| can be null during tests + if (!state_) { + return; + } + if (state_->native_loop_do_work_item.IsNull() && + state_->g_depth_on_iteration.has_value() && + g_main_depth() != state_->g_depth_on_iteration.value()) { + RegisterNested(); + } +} + +void MessagePumpGlib::UnnestIfRequired() { + // |state_| can be null during tests + if (!state_) { + return; + } + if (!state_->native_loop_do_work_item.IsNull()) { + UnregisterNested(); + } +} + +void MessagePumpGlib::OnEntryToGlib() { + // |state_| can be null during tests + if (!state_) { + return; + } + CHECK(!state_->g_depth_on_iteration.has_value()); + state_->g_depth_on_iteration.emplace(g_main_depth()); +} + +void MessagePumpGlib::OnExitFromGlib() { + // |state_| can be null during tests + if (!state_) { + return; + } + state_->g_depth_on_iteration.reset(); + UnnestIfRequired(); +} + } // namespace base diff --git a/base/message_loop/message_pump_glib.h b/base/message_loop/message_pump_glib.h index 1b0fcced8..647f3cc45 100644 --- a/base/message_loop/message_pump_glib.h +++ b/base/message_loop/message_pump_glib.h @@ -93,6 +93,12 @@ class BASE_EXPORT MessagePumpGlib : public MessagePump, bool HandleCheck(); void HandleDispatch(); + // Very similar to the above, with the key difference that these functions are + // only used to track work items and never indicate work is available, and + // poll indefinitely. + void HandleObserverPrepare(); + bool HandleObserverCheck(); + // Overridden from MessagePump: void Run(Delegate* delegate) override; void Quit() override; @@ -131,6 +137,38 @@ class BASE_EXPORT MessagePumpGlib : public MessagePump, raw_ptr<RunState> state_; + // Starts tracking a new work item and stores a `ScopedDoWorkItem` in + // `state_`. + void SetScopedWorkItem(); + // Gets rid of the current scoped work item. + void ClearScopedWorkItem(); + // Ensures there's a ScopedDoWorkItem at the current run-level. This can be + // useful for contexts where the caller can't tell whether they just woke up + // or are continuing from native work. + void EnsureSetScopedWorkItem(); + // Ensures there's no ScopedDoWorkItem at the current run-level. This can be + // useful in contexts where the caller knows that a sleep is imminent but + // doesn't know if the current context captures ongoing work (back from + // native). + void EnsureClearedScopedWorkItem(); + + // Called before entrance to g_main_context_iteration to record context + // related to nesting depth to track native nested loops which would otherwise + // be invisible. + void OnEntryToGlib(); + // Cleans up state set in OnEntryToGlib. + void OnExitFromGlib(); + // Forces the pump into a nested state by creating two work items back to + // back. + void RegisterNested(); + // Removes all of the pump's ScopedDoWorkItems to remove the state of nesting + // which was forced onto the pump. + void UnregisterNested(); + // Nest if pump is not already marked as nested. + void NestIfRequired(); + // Remove the nesting if the pump is nested. + void UnnestIfRequired(); + std::unique_ptr<GMainContext, GMainContextDeleter> owned_context_; // This is a GLib structure that we can add event sources to. On the main // thread, we use the default GLib context, which is the one to which all GTK @@ -141,6 +179,10 @@ class BASE_EXPORT MessagePumpGlib : public MessagePump, // the message pump is destroyed. std::unique_ptr<GSource, GSourceDeleter> work_source_; + // The observer source. It is shared by all calls to Run and destroyed when + // the message pump is destroyed. + std::unique_ptr<GSource, GSourceDeleter> observer_source_; + // We use a wakeup pipe to make sure we'll get out of the glib polling phase // when another thread has scheduled us to do some work. There is a glib // mechanism g_main_context_wakeup, but this won't guarantee that our event's diff --git a/base/message_loop/message_pump_glib_unittest.cc b/base/message_loop/message_pump_glib_unittest.cc index b202b4092..77b5ca425 100644 --- a/base/message_loop/message_pump_glib_unittest.cc +++ b/base/message_loop/message_pump_glib_unittest.cc @@ -6,6 +6,7 @@ #include <glib.h> #include <math.h> +#include "build/build_config.h" #include <algorithm> #include <vector> @@ -27,6 +28,7 @@ #include "base/task/single_thread_task_executor.h" #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h" +#include "base/test/trace_event_analyzer.h" #include "base/threading/thread.h" #include "testing/gtest/include/gtest/gtest.h" @@ -540,6 +542,67 @@ TEST_F(MessagePumpGLibTest, TestGtkLoop) { run_loop.Run(); } +namespace { + +class NestedEventAnalyzer { + public: + NestedEventAnalyzer() { + trace_analyzer::Start(TRACE_DISABLED_BY_DEFAULT("base")); + } + + size_t CountEvents() { + std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = + trace_analyzer::Stop(); + trace_analyzer::TraceEventVector events; + return analyzer->FindEvents(trace_analyzer::Query::EventName() == + trace_analyzer::Query::String("Nested"), + &events); + } +}; + +} // namespace + +TEST_F(MessagePumpGLibTest, TestNativeNestedLoopWithoutDoWork) { + // Tests that nesting is triggered correctly if a message loop is run + // from a native event (gtk event) outside of a work item (not in a posted + // task). + + RunLoop run_loop; + NestedEventAnalyzer analyzer; + + base::CurrentThread::Get()->EnableMessagePumpTimeKeeperMetrics( + "GlibMainLoopTest"); + + scoped_refptr<GLibLoopRunner> runner = base::MakeRefCounted<GLibLoopRunner>(); + injector()->AddEvent( + 0, + BindOnce( + [](EventInjector* injector, scoped_refptr<GLibLoopRunner> runner, + OnceClosure done) { + CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow; + runner->RunLoop(); + }, + Unretained(injector()), runner, run_loop.QuitClosure())); + + injector()->AddDummyEvent(0); + injector()->AddDummyEvent(0); + injector()->AddDummyEvent(0); + + SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, BindOnce(&GLibLoopRunner::Quit, runner), Milliseconds(40)); + + SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), Milliseconds(40)); + + run_loop.Run(); + + // It would be expected that there be one single event, but it seems like this + // is counting the Begin/End of the Nested trace event. Each of the two events + // found are of duration 0 with distinct timestamps. It has also been + // confirmed that nesting occurs only once. + CHECK_EQ(analyzer.CountEvents(), 2ul); +} + // Tests for WatchFileDescriptor API class MessagePumpGLibFdWatchTest : public testing::Test { protected: diff --git a/base/message_loop/message_pump_libevent.h b/base/message_loop/message_pump_libevent.h index 4017d93a1..0389de964 100644 --- a/base/message_loop/message_pump_libevent.h +++ b/base/message_loop/message_pump_libevent.h @@ -162,7 +162,10 @@ class BASE_EXPORT MessagePumpLibevent : public MessagePump, // State used only with libevent std::unique_ptr<event> event_; - raw_ptr<MessagePumpLibevent> libevent_pump_ = nullptr; + + // Tests (e.g. FdWatchControllerPosixTest) deliberately make this dangle. + raw_ptr<MessagePumpLibevent, DisableDanglingPtrDetection> libevent_pump_ = + nullptr; // State used only with epoll WeakPtr<MessagePumpEpoll> epoll_pump_; diff --git a/base/message_loop/message_pump_unittest.cc b/base/message_loop/message_pump_unittest.cc index 9e1970387..b11167b42 100644 --- a/base/message_loop/message_pump_unittest.cc +++ b/base/message_loop/message_pump_unittest.cc @@ -139,6 +139,13 @@ class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> { MessagePumpTest() : message_pump_(MessagePump::Create(GetParam())) {} protected: +#if defined(USE_GLIB) + // Because of a GLIB implementation quirk, the pump doesn't do the same things + // between each DoWork. In this case, it won't set/clear a ScopedDoWorkItem + // because we run a chrome work item in the runloop outside of GLIB's control, + // so we oscillate between setting and not setting PreDoWorkExpectations. + std::map<MessagePump::Delegate*, int> do_work_counts; +#endif void AddPreDoWorkExpectations( testing::StrictMock<MockMessagePumpDelegate>& delegate) { #if BUILDFLAG(IS_WIN) @@ -155,14 +162,35 @@ class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> { EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1)); } #endif // BUILDFLAG(IS_WIN) +#if defined(USE_GLIB) + do_work_counts.try_emplace(&delegate, 0); + if (GetParam() == MessagePumpType::UI) { + if (++do_work_counts[&delegate] % 2) { + // The GLib MessagePump will do native work before chrome work on + // startup. + EXPECT_CALL(delegate, MockOnBeginWorkItem); + EXPECT_CALL(delegate, MockOnEndWorkItem); + } + } +#endif // defined(USE_GLIB) } void AddPostDoWorkExpectations( testing::StrictMock<MockMessagePumpDelegate>& delegate) { +#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL) // MessagePumpLibEvent checks for native notifications once after processing // a DoWork() but only instantiates a ScopedDoWorkItem that triggers // MessagePumpLibevent::OnLibeventNotification() which this test does not // so there are no post-work expectations at the moment. +#endif +#if defined(USE_GLIB) + if (GetParam() == MessagePumpType::UI) { + // The GLib MessagePump can create and destroy work items between DoWorks + // depending on internal state. + EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1)); + EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1)); + } +#endif // defined(USE_GLIB) } std::unique_ptr<MessagePump> message_pump_; @@ -182,6 +210,16 @@ TEST_P(MessagePumpTest, QuitStopsWork) { message_pump_->Quit(); return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()}; })); + + // MessagePumpGlib uses a work item between a HandleDispatch() call and + // passing control back to the chrome loop, which handles the Quit() despite + // us not necessarily doing any native work during that time. +#if defined(USE_GLIB) + if (GetParam() == MessagePumpType::UI) { + AddPostDoWorkExpectations(delegate); + } +#endif + EXPECT_CALL(delegate, DoIdleWork()).Times(0); message_pump_->ScheduleWork(); @@ -215,10 +253,14 @@ TEST_P(MessagePumpTest, QuitStopsWorkWithNestedRunLoop) { return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()}; })); - // PostDoWorkExpectations for the first DoWork. + // The `nested_delegate` will quit first. + AddPostDoWorkExpectations(nested_delegate); + + // Return a delayed task with |yield_to_native| set, and exit. AddPostDoWorkExpectations(delegate); AddPreDoWorkExpectations(delegate); + EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] { message_pump_->Quit(); return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()}; @@ -245,8 +287,8 @@ TEST_P(MessagePumpTest, YieldToNativeRequestedSmokeTest) { })); AddPostDoWorkExpectations(delegate); - // Return a delayed task with |yield_to_native| set, and exit. AddPreDoWorkExpectations(delegate); + // Return a delayed task with |yield_to_native| set, and exit. EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] { message_pump_->Quit(); auto now = TimeTicks::Now(); @@ -357,9 +399,13 @@ TEST_P(MessagePumpTest, RunWithoutScheduleWorkInvokesDoWork) { message_pump_->Quit(); return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()}; })); + + AddPostDoWorkExpectations(delegate); + #if BUILDFLAG(IS_IOS) EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber()); #endif + message_pump_->Run(&delegate); } @@ -383,6 +429,11 @@ TEST_P(MessagePumpTest, NestedRunWithoutScheduleWorkInvokesDoWork) { return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()}; })); + // We quit `nested_delegate` before `delegate` + AddPostDoWorkExpectations(nested_delegate); + + AddPostDoWorkExpectations(delegate); + #if BUILDFLAG(IS_IOS) EXPECT_CALL(nested_delegate, DoIdleWork).Times(AnyNumber()); EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber()); diff --git a/base/message_loop/message_pump_win.h b/base/message_loop/message_pump_win.h index 1ea4f1d9f..68afdb236 100644 --- a/base/message_loop/message_pump_win.h +++ b/base/message_loop/message_pump_win.h @@ -14,6 +14,7 @@ #include "base/compiler_specific.h" #include "base/location.h" #include "base/memory/raw_ptr.h" +#include "base/memory/raw_ptr_exclusion.h" #include "base/message_loop/message_pump.h" #include "base/observer_list.h" #include "base/threading/thread_checker.h" @@ -76,7 +77,9 @@ class BASE_EXPORT MessagePumpWin : public MessagePump { std::atomic_bool work_scheduled_{false}; // State for the current invocation of Run(). null if not running. - RunState* run_state_ = nullptr; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #addr-of + RAW_PTR_EXCLUSION RunState* run_state_ = nullptr; THREAD_CHECKER(bound_thread_); }; diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc index 06cb7bf93..fa6a8cc6c 100644 --- a/base/metrics/field_trial.cc +++ b/base/metrics/field_trial.cc @@ -372,13 +372,14 @@ FieldTrial* FieldTrial::CreateSimulatedFieldTrial( StringPiece default_group_name, double entropy_value) { return new FieldTrial(trial_name, total_probability, default_group_name, - entropy_value); + entropy_value, /*is_low_anonymity=*/false); } FieldTrial::FieldTrial(StringPiece trial_name, const Probability total_probability, StringPiece default_group_name, - double entropy_value) + double entropy_value, + bool is_low_anonymity) : trial_name_(trial_name), divisor_(total_probability), default_group_name_(default_group_name), @@ -389,7 +390,8 @@ FieldTrial::FieldTrial(StringPiece trial_name, forced_(false), group_reported_(false), trial_registered_(false), - ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull) { + ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull), + is_low_anonymity_(is_low_anonymity) { DCHECK_GT(total_probability, 0); DCHECK(!trial_name_.empty()); DCHECK(!default_group_name_.empty()) @@ -472,7 +474,8 @@ FieldTrial* FieldTrialList::FactoryGetFieldTrial( FieldTrial::Probability total_probability, StringPiece default_group_name, const FieldTrial::EntropyProvider& entropy_provider, - uint32_t randomization_seed) { + uint32_t randomization_seed, + bool is_low_anonymity) { // Check if the field trial has already been created in some other way. FieldTrial* existing_trial = Find(trial_name); if (existing_trial) { @@ -483,8 +486,9 @@ FieldTrial* FieldTrialList::FactoryGetFieldTrial( double entropy_value = entropy_provider.GetEntropyForTrial(trial_name, randomization_seed); - FieldTrial* field_trial = new FieldTrial(trial_name, total_probability, - default_group_name, entropy_value); + FieldTrial* field_trial = + new FieldTrial(trial_name, total_probability, default_group_name, + entropy_value, is_low_anonymity); FieldTrialList::Register(field_trial, /*is_randomized_trial=*/true); return field_trial; } @@ -609,16 +613,8 @@ std::string FieldTrialList::AllParamsToString(EscapeDataFunc encode_data_func) { // static void FieldTrialList::GetActiveFieldTrialGroups( FieldTrial::ActiveGroups* active_groups) { - DCHECK(active_groups->empty()); - if (!global_) - return; - AutoLock auto_lock(global_->lock_); - - for (const auto& registered : global_->registered_) { - FieldTrial::ActiveGroup active_group; - if (registered.second->GetActiveGroup(&active_group)) - active_groups->push_back(active_group); - } + GetActiveFieldTrialGroupsInternal(active_groups, + /*include_low_anonymity=*/false); } // static @@ -805,7 +801,8 @@ FieldTrialList::DuplicateFieldTrialSharedMemoryForTesting() { // static FieldTrial* FieldTrialList::CreateFieldTrial(StringPiece name, - StringPiece group_name) { + StringPiece group_name, + bool is_low_anonymity) { DCHECK(global_); DCHECK_GE(name.size(), 0u); DCHECK_GE(group_name.size(), 0u); @@ -821,7 +818,8 @@ FieldTrial* FieldTrialList::CreateFieldTrial(StringPiece name, return field_trial; } const int kTotalProbability = 100; - field_trial = new FieldTrial(name, kTotalProbability, group_name, 0); + field_trial = + new FieldTrial(name, kTotalProbability, group_name, 0, is_low_anonymity); // The group choice will be finalized in this method. So // |is_randomized_trial| should be false. FieldTrialList::Register(field_trial, /*is_randomized_trial=*/false); @@ -832,21 +830,14 @@ FieldTrial* FieldTrialList::CreateFieldTrial(StringPiece name, // static bool FieldTrialList::AddObserver(Observer* observer) { - if (!global_) - return false; - AutoLock auto_lock(global_->lock_); - global_->observers_.push_back(observer); - return true; + return FieldTrialList::AddObserverInternal(observer, + /*include_low_anonymity=*/false); } // static void FieldTrialList::RemoveObserver(Observer* observer) { - if (!global_) - return; - AutoLock auto_lock(global_->lock_); - Erase(global_->observers_, observer); - DCHECK_EQ(global_->num_ongoing_notify_field_trial_group_selection_calls_, 0) - << "Cannot call RemoveObserver while accessing FieldTrial::group_name()."; + FieldTrialList::RemoveObserverInternal(observer, + /*include_low_anonymity=*/false); } // static @@ -855,6 +846,7 @@ void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) { return; std::vector<Observer*> local_observers; + std::vector<Observer*> local_observers_including_low_anonymity; { AutoLock auto_lock(global_->lock_); @@ -870,9 +862,18 @@ void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) { // lock. Since removing observers concurrently with this method is // disallowed, pointers should remain valid while observers are notified. local_observers = global_->observers_; + local_observers_including_low_anonymity = + global_->observers_including_low_anonymity_; + } + + if (!field_trial->is_low_anonymity_) { + for (Observer* observer : local_observers) { + observer->OnFieldTrialGroupFinalized(field_trial->trial_name(), + field_trial->group_name_internal()); + } } - for (Observer* observer : local_observers) { + for (Observer* observer : local_observers_including_low_anonymity) { observer->OnFieldTrialGroupFinalized(field_trial->trial_name(), field_trial->group_name_internal()); } @@ -1399,4 +1400,55 @@ bool FieldTrialList::CreateTrialsFromFieldTrialStatesInternal( return true; } +// static +void FieldTrialList::GetActiveFieldTrialGroupsInternal( + FieldTrial::ActiveGroups* active_groups, + bool include_low_anonymity) { + DCHECK(active_groups->empty()); + if (!global_) { + return; + } + AutoLock auto_lock(global_->lock_); + + for (const auto& registered : global_->registered_) { + const FieldTrial& trial = *registered.second; + FieldTrial::ActiveGroup active_group; + if ((include_low_anonymity || !trial.is_low_anonymity_) && + trial.GetActiveGroup(&active_group)) { + active_groups->push_back(active_group); + } + } +} + +// static +bool FieldTrialList::AddObserverInternal(Observer* observer, + bool include_low_anonymity) { + if (!global_) { + return false; + } + AutoLock auto_lock(global_->lock_); + if (include_low_anonymity) { + global_->observers_including_low_anonymity_.push_back(observer); + } else { + global_->observers_.push_back(observer); + } + return true; +} + +// static +void FieldTrialList::RemoveObserverInternal(Observer* observer, + bool include_low_anonymity) { + if (!global_) { + return; + } + AutoLock auto_lock(global_->lock_); + if (include_low_anonymity) { + Erase(global_->observers_including_low_anonymity_, observer); + } else { + Erase(global_->observers_, observer); + } + DCHECK_EQ(global_->num_ongoing_notify_field_trial_group_selection_calls_, 0) + << "Cannot call RemoveObserver while accessing FieldTrial::group_name()."; +} + } // namespace base diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h index d62b550fc..bc805254b 100644 --- a/base/metrics/field_trial.h +++ b/base/metrics/field_trial.h @@ -99,6 +99,7 @@ namespace test { class ScopedFeatureList; } // namespace test +class CompareActiveGroupToFieldTrialMatcher; class FieldTrialList; struct LaunchOptions; @@ -263,6 +264,12 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { StringPiece default_group_name, double entropy_value); + // Whether this field trial is low anonymity or not (see + // |FieldTrialListIncludingLowAnonymity|). + // TODO(crbug.com/1431156): remove this once all call sites have been properly + // migrated to use an appropriate observer. + bool is_low_anonymity() { return is_low_anonymity_; } + private: // Allow tests to access our innards for testing purposes. FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Registration); @@ -290,6 +297,10 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, ClearParamsFromSharedMemory); FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, TestGetRandomizedFieldTrialCount); + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetLowAnonymity); + + // MATCHER(CompareActiveGroupToFieldTrialMatcher, "") + friend class base::CompareActiveGroupToFieldTrialMatcher; friend class base::FieldTrialList; @@ -307,7 +318,8 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { FieldTrial(StringPiece trial_name, Probability total_probability, StringPiece default_group_name, - double entropy_value); + double entropy_value, + bool is_low_anonymity); virtual ~FieldTrial(); @@ -384,6 +396,10 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // Denotes whether benchmarking is enabled. In this case, field trials all // revert to the default group. static bool enable_benchmarking_; + + // Whether this field trial is potentially low anonymity (eg. only a small + // set of users are included). + const bool is_low_anonymity_ = false; }; //------------------------------------------------------------------------------ @@ -436,6 +452,11 @@ class BASE_EXPORT FieldTrialList { // * SHA1 and NormalizedMurmurHash providers will use a non-zero value as a // salt _instead_ of using the trial name. // + // Some field trials may be targeted in such way that a relatively small + // number of users are in a particular experiment group. Such trials should + // have |is_low_anonymity| set to true, and their visitbility is restricted + // to specific callers only, via |FieldTrialListIncludingLowAnonymity|. + // // This static method can be used to get a startup-randomized FieldTrial or a // previously created forced FieldTrial. static FieldTrial* FactoryGetFieldTrial( @@ -443,7 +464,8 @@ class BASE_EXPORT FieldTrialList { FieldTrial::Probability total_probability, StringPiece default_group_name, const FieldTrial::EntropyProvider& entropy_provider, - uint32_t randomization_seed = 0); + uint32_t randomization_seed = 0, + bool is_low_anonymity = false); // The Find() method can be used to test to see if a named trial was already // registered, or to retrieve a pointer to it from the global map. @@ -486,6 +508,10 @@ class BASE_EXPORT FieldTrialList { // called) with a snapshot of all registered FieldTrials for which the group // has been chosen and externally observed (via |group()|) and which have // not been disabled. + // + // This does not return low anonymity field trials. Callers who need access to + // low anonymity field trials should use + // |FieldTrialListIncludingLowAnonymity.GetActiveFieldTrialGroups()|. static void GetActiveFieldTrialGroups( FieldTrial::ActiveGroups* active_groups); @@ -554,18 +580,30 @@ class BASE_EXPORT FieldTrialList { // randomly selected state in a browser process into this non-browser process. // It returns NULL if there is a FieldTrial that is already registered with // the same |name| but has different finalized group string (|group_name|). - static FieldTrial* CreateFieldTrial(StringPiece name, StringPiece group_name); + // + // Visibility of field trials with |is_low_anonymity| set to true is + // restricted to specific callers only, see + // |FieldTrialListIncludingLowAnonymity|. + static FieldTrial* CreateFieldTrial(StringPiece name, + StringPiece group_name, + bool is_low_anonymity = false); // 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). Returns false and does nothing if // there is no FieldTrialList singleton. The observer can be notified on any // sequence; it must be thread-safe. + // + // Low anonymity field trials are not notified to this observer. Callers + // who need to be notified of low anonymity field trials should use + // |FieldTrialListIncludingLowAnonymity.AddObserver()|. static bool AddObserver(Observer* observer); // Remove an observer. This cannot be invoked concurrently with // FieldTrial::group() (typically, this means that no other thread should be // running when this is invoked). + // + // Removes observers added via the |AddObserver()| method of this class. static void RemoveObserver(Observer* observer); // Notify all observers that a group has been finalized for |field_trial|. @@ -646,6 +684,10 @@ class BASE_EXPORT FieldTrialList { friend int SerializeSharedMemoryRegionMetadata(); FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, CheckReadOnlySharedMemoryRegion); + // Required so that |FieldTrialListIncludingLowAnonymity| can expose APIs from + // this class to its friends. + friend class FieldTrialListIncludingLowAnonymity; + #if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_IOS) // Serialization is used to pass information about the shared memory handle // to child processes. This is achieved by passing a stringified reference to @@ -730,9 +772,32 @@ class BASE_EXPORT FieldTrialList { static bool CreateTrialsFromFieldTrialStatesInternal( const std::vector<FieldTrial::State>& entries); + // The same as |GetActiveFieldTrialGroups| but also gives access to low + // anonymity field trials. + // Restricted to specifically allowed friends - access via + // |FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups|. + static void GetActiveFieldTrialGroupsInternal( + FieldTrial::ActiveGroups* active_groups, + bool include_low_anonymity); + + // The same as |AddObserver| but is notified for low anonymity field trials + // too. + // Restricted to specifically allowed friends - access via + // |FieldTrialListIncludingLowAnonymity::AddObserver|. + static bool AddObserverInternal(Observer* observer, + bool include_low_anonymity); + + // The same as |RemoveObserver| but is notified for low anonymity field trials + // too. + // Restricted to specifically allowed friends - access via + // |FieldTrialListIncludingLowAnonymity::RemoveObserver|. + static void RemoveObserverInternal(Observer* observer, + bool include_low_anonymity); + static FieldTrialList* global_; // The singleton of this class. // Lock for access to |registered_|, |observers_|, + // |observers_including_low_anonymity_|, // |count_of_manually_created_field_trials_|. Lock lock_; RegistrationMap registered_ GUARDED_BY(lock_); @@ -741,8 +806,13 @@ class BASE_EXPORT FieldTrialList { size_t num_registered_randomized_trials_ GUARDED_BY(lock_) = 0; // List of observers to be notified when a group is selected for a FieldTrial. + // Excludes low anonymity field trials. std::vector<Observer*> observers_ GUARDED_BY(lock_); + // List of observers to be notified when a group is selected for a FieldTrial. + // Includes low anonymity field trials. + std::vector<Observer*> observers_including_low_anonymity_ GUARDED_BY(lock_); + // Counts the ongoing calls to // FieldTrialList::NotifyFieldTrialGroupSelection(). Used to ensure that // RemoveObserver() isn't called while notifying observers. diff --git a/base/metrics/field_trial_list_including_low_anonymity.cc b/base/metrics/field_trial_list_including_low_anonymity.cc new file mode 100644 index 000000000..d672b865d --- /dev/null +++ b/base/metrics/field_trial_list_including_low_anonymity.cc @@ -0,0 +1,31 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/field_trial_list_including_low_anonymity.h" +#include "base/metrics/field_trial.h" + +namespace base { + +// static +void FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups( + FieldTrial::ActiveGroups* active_groups) { + return FieldTrialList::GetActiveFieldTrialGroupsInternal( + active_groups, /*include_low_anonymity=*/true); +} + +// static +bool FieldTrialListIncludingLowAnonymity::AddObserver( + FieldTrialList::Observer* observer) { + return FieldTrialList::AddObserverInternal(observer, + /*include_low_anonymity=*/true); +} + +// static +void FieldTrialListIncludingLowAnonymity::RemoveObserver( + FieldTrialList::Observer* observer) { + FieldTrialList::RemoveObserverInternal(observer, + /*include_low_anonymity=*/true); +} + +} // namespace base diff --git a/base/metrics/field_trial_list_including_low_anonymity.h b/base/metrics/field_trial_list_including_low_anonymity.h new file mode 100644 index 000000000..a70af448d --- /dev/null +++ b/base/metrics/field_trial_list_including_low_anonymity.h @@ -0,0 +1,98 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_FIELD_TRIAL_LIST_INCLUDING_LOW_ANONYMITY_H_ +#define BASE_METRICS_FIELD_TRIAL_LIST_INCLUDING_LOW_ANONYMITY_H_ + +#include "base/gtest_prod_util.h" +#include "base/metrics/field_trial.h" +#include "base/values.h" + +class AndroidFieldTrialListLogActiveTrialsFriendHelper; + +namespace content { +class FieldTrialSynchronizer; +} + +namespace variations { +class ChildProcessFieldTrialSyncer; +class EntropyProviders; +class ProcessedStudy; +struct SeedSimulationResult; +class VariationsCrashKeys; +class VariationsLayers; +SeedSimulationResult ComputeDifferences( + const std::vector<ProcessedStudy>& processed_studies, + const VariationsLayers& layers, + const EntropyProviders& entropy_providers); +} // namespace variations + +namespace version_ui { +base::Value::List GetVariationsList(); +} + +namespace base { + +// Provides a way to restrict access to the full set of field trials, including +// trials with low anonymity, to explicitly allowed callers. +// +// See |FieldTrialList::FactoryGetFieldTrial()| for background. +class BASE_EXPORT FieldTrialListIncludingLowAnonymity { + public: + // Exposed publicly, to avoid test code needing to be explicitly friended. + static void GetActiveFieldTrialGroupsForTesting( + FieldTrial::ActiveGroups* active_groups) { + return GetActiveFieldTrialGroups(active_groups); + } + + // Classes / functions which are allowed full access to all field trials + // should be listed as friends here, with a comment explaining why this does + // not risk revealing identifiable information externally. + + // This is used only for local logging on Android. + friend class ::AndroidFieldTrialListLogActiveTrialsFriendHelper; + + // Used to synchronize field trial status between the browser and child + // processes. + // Access to these trials within each of these is then allowed only to the + // other friend classes / methods listed here. + friend class content::FieldTrialSynchronizer; + friend class variations::ChildProcessFieldTrialSyncer; + + // This is only used to simulate seed changes, not sent to Google servers. + friend variations::SeedSimulationResult variations::ComputeDifferences( + const std::vector<variations::ProcessedStudy>& processed_studies, + const variations::VariationsLayers& layers, + const variations::EntropyProviders& entropy_providers); + + // Include all active field trials in crash reports, so that crashes are + // reproducible: https://www.google.com/intl/en/chrome/privacy/. + friend class variations::VariationsCrashKeys; + + // This usage is to display field trials in chrome://version and other local + // internal UIs. + friend base::Value::List version_ui::GetVariationsList(); + + // Required for tests. + friend class TestFieldTrialObserverIncludingLowAnonymity; + FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ObserveIncludingLowAnonymity); + + private: + // The same as |FieldTrialList::GetActiveFieldTrialGroups| but gives access to + // low anonymity field trials too. + static void GetActiveFieldTrialGroups( + FieldTrial::ActiveGroups* active_groups); + + // Identical to |FieldTrialList::AddObserver| but also notifies of low + // anonymity trials. + static bool AddObserver(FieldTrialList::Observer* observer); + + // Identical to |FieldTrialList::RemoveObserver| but for observers registered + // through the AddObserver() function of this class. + static void RemoveObserver(FieldTrialList::Observer* observer); +}; + +} // namespace base + +#endif // BASE_METRICS_FIELD_TRIAL_LIST_INCLUDING_LOW_ANONYMITY_H_ diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc index 269980605..a2a8ef31b 100644 --- a/base/metrics/field_trial_unittest.cc +++ b/base/metrics/field_trial_unittest.cc @@ -12,6 +12,7 @@ #include "base/command_line.h" #include "base/feature_list.h" #include "base/memory/ptr_util.h" +#include "base/metrics/field_trial_list_including_low_anonymity.h" #include "base/metrics/field_trial_param_associator.h" #include "base/rand_util.h" #include "base/run_loop.h" @@ -25,6 +26,7 @@ #include "base/test/test_shared_memory_util.h" #include "base/test/test_timeouts.h" #include "build/build_config.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" @@ -51,10 +53,12 @@ const char kDefaultGroupName[] = "DefaultGroup"; scoped_refptr<FieldTrial> CreateFieldTrial( const std::string& trial_name, int total_probability, - const std::string& default_group_name) { + const std::string& default_group_name, + bool is_low_anonymity = false) { MockEntropyProvider entropy_provider(0.9); return FieldTrialList::FactoryGetFieldTrial( - trial_name, total_probability, default_group_name, entropy_provider); + trial_name, total_probability, default_group_name, entropy_provider, 0, + is_low_anonymity); } // A FieldTrialList::Observer implementation which stores the trial name and @@ -116,6 +120,37 @@ std::string MockEscapeQueryParamValue(const std::string& input) { } // namespace +// Same as |TestFieldTrialObserver|, but registers for low anonymity field +// trials too. +class TestFieldTrialObserverIncludingLowAnonymity + : public FieldTrialList::Observer { + public: + TestFieldTrialObserverIncludingLowAnonymity() { + FieldTrialListIncludingLowAnonymity::AddObserver(this); + } + TestFieldTrialObserverIncludingLowAnonymity( + const TestFieldTrialObserverIncludingLowAnonymity&) = delete; + TestFieldTrialObserverIncludingLowAnonymity& operator=( + const TestFieldTrialObserverIncludingLowAnonymity&) = delete; + + ~TestFieldTrialObserverIncludingLowAnonymity() override { + FieldTrialListIncludingLowAnonymity::RemoveObserver(this); + } + + void OnFieldTrialGroupFinalized(const std::string& trial, + const std::string& group) override { + trial_name_ = trial; + group_name_ = group; + } + + const std::string& trial_name() const { return trial_name_; } + const std::string& group_name() const { return group_name_; } + + private: + std::string trial_name_; + std::string group_name_; +}; + class FieldTrialTest : public ::testing::Test { public: FieldTrialTest() { @@ -131,6 +166,13 @@ class FieldTrialTest : public ::testing::Test { test::ScopedFeatureList scoped_feature_list_; }; +MATCHER(CompareActiveGroupToFieldTrial, "") { + const base::FieldTrial::ActiveGroup& lhs = ::testing::get<0>(arg); + const base::FieldTrial* rhs = ::testing::get<1>(arg).get(); + return lhs.trial_name == rhs->trial_name() && + lhs.group_name == rhs->group_name_internal(); +} + // Test registration, and also check that destructors are called for trials. TEST_F(FieldTrialTest, Registration) { const char name1[] = "name 1 test"; @@ -762,8 +804,8 @@ TEST_F(FieldTrialTest, FloatBoundariesGiveEqualGroupSizes) { for (int i = 0; i < kBucketCount; ++i) { const double entropy = i / static_cast<double>(kBucketCount); - scoped_refptr<FieldTrial> trial( - new FieldTrial("test", kBucketCount, "default", entropy)); + scoped_refptr<FieldTrial> trial(new FieldTrial( + "test", kBucketCount, "default", entropy, /*is_low_anonymity=*/false)); for (int j = 0; j < kBucketCount; ++j) trial->AppendGroup(NumberToString(j), 1); @@ -775,8 +817,8 @@ TEST_F(FieldTrialTest, DoesNotSurpassTotalProbability) { const double kEntropyValue = 1.0 - 1e-9; ASSERT_LT(kEntropyValue, 1.0); - scoped_refptr<FieldTrial> trial( - new FieldTrial("test", 2, "default", kEntropyValue)); + scoped_refptr<FieldTrial> trial(new FieldTrial( + "test", 2, "default", kEntropyValue, /*is_low_anonymity=*/false)); trial->AppendGroup("1", 1); trial->AppendGroup("2", 1); @@ -1279,4 +1321,53 @@ TEST_F(FieldTrialTest, TestAllParamsToString) { FieldTrialList::AllParamsToString(&MockEscapeQueryParamValue)); } +TEST_F(FieldTrialTest, GetActiveFieldTrialGroups_LowAnonymity) { + // Create a field trial with a single winning group. + scoped_refptr<FieldTrial> trial_1 = CreateFieldTrial("Normal", 10, "Default"); + trial_1->AppendGroup("Winner 1", 10); + trial_1->Activate(); + + // Create a second field trial with a single winning group, marked as + // low-anonymity. + scoped_refptr<FieldTrial> trial_2 = CreateFieldTrial( + "Low anonymity", 10, "Default", /*is_low_anonymity=*/true); + trial_2->AppendGroup("Winner 2", 10); + trial_2->Activate(); + + // Check that |FieldTrialList::GetActiveFieldTrialGroups()| does not include + // the low-anonymity trial. + FieldTrial::ActiveGroups active_groups_for_metrics; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups_for_metrics); + EXPECT_THAT( + active_groups_for_metrics, + testing::UnorderedPointwise(CompareActiveGroupToFieldTrial(), {trial_1})); + + // Check that + // |FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroups()| includes + // both trials. + FieldTrial::ActiveGroups active_groups; + FieldTrialListIncludingLowAnonymity::GetActiveFieldTrialGroupsForTesting( + &active_groups); + EXPECT_THAT(active_groups, + testing::UnorderedPointwise(CompareActiveGroupToFieldTrial(), + {trial_1, trial_2})); +} + +TEST_F(FieldTrialTest, ObserveIncludingLowAnonymity) { + TestFieldTrialObserver observer; + TestFieldTrialObserverIncludingLowAnonymity low_anonymity_observer; + + // Create a low-anonymity trial with one active group. + const char kTrialName[] = "TrialToObserve1"; + scoped_refptr<FieldTrial> trial = CreateFieldTrial( + kTrialName, 100, kDefaultGroupName, /*is_low_anonymity=*/true); + trial->Activate(); + + // Only the low_anonymity_observer should be notified. + EXPECT_EQ("", observer.trial_name()); + EXPECT_EQ("", observer.group_name()); + EXPECT_EQ(kTrialName, low_anonymity_observer.trial_name()); + EXPECT_EQ(kDefaultGroupName, low_anonymity_observer.group_name()); +} + } // namespace base diff --git a/base/metrics/histogram_threadsafe_unittest.cc b/base/metrics/histogram_threadsafe_unittest.cc index 753e9c835..aeb0e8556 100644 --- a/base/metrics/histogram_threadsafe_unittest.cc +++ b/base/metrics/histogram_threadsafe_unittest.cc @@ -181,6 +181,8 @@ class HistogramThreadsafeTest : public testing::Test { } void TearDown() override { + histograms_.clear(); + allocator_view_.reset(); GlobalHistogramAllocator::ReleaseForTesting(); ASSERT_FALSE(GlobalHistogramAllocator::Get()); } @@ -297,7 +299,7 @@ class HistogramThreadsafeTest : public testing::Test { // use of ASSERT_* instead EXPECT_* because the test is repeated multiple times, // and the use of EXPECT_* produces spammy outputs as it does not end the test // immediately. -TEST_F(HistogramThreadsafeTest, SnapshotDeltaThreadsafe) { +TEST_F(HistogramThreadsafeTest, DISABLED_SnapshotDeltaThreadsafe) { // We try this test |kNumIterations| times to have a coverage of different // scenarios. For example, for a numeric histogram, if it has only samples // within the same bucket, the samples will be stored in a different way than @@ -332,11 +334,11 @@ TEST_F(HistogramThreadsafeTest, SnapshotDeltaThreadsafe) { // later on. constexpr size_t kNumThreads = 2; constexpr size_t kNumEmissions = 2000; - std::unique_ptr<SnapshotDeltaThread> threads[kNumThreads]; subtle::Atomic32 real_total_samples_count = 0; std::vector<subtle::Atomic32> real_bucket_counts(kHistogramMax, 0); subtle::Atomic32 snapshots_total_samples_count = 0; std::vector<subtle::Atomic32> snapshots_bucket_counts(kHistogramMax, 0); + std::unique_ptr<SnapshotDeltaThread> threads[kNumThreads]; for (size_t i = 0; i < kNumThreads; ++i) { threads[i] = std::make_unique<SnapshotDeltaThread>( StringPrintf("SnapshotDeltaThread.%zu.%zu", iteration, i), diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc index d29e21710..ec0e85287 100644 --- a/base/metrics/persistent_memory_allocator.cc +++ b/base/metrics/persistent_memory_allocator.cc @@ -10,6 +10,7 @@ #include "base/bits.h" #include "base/debug/alias.h" +#include "base/debug/crash_logging.h" #include "base/files/memory_mapped_file.h" #include "base/logging.h" #include "base/metrics/histogram_functions.h" @@ -17,6 +18,7 @@ #include "base/notreached.h" #include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" +#include "base/strings/strcat.h" #include "base/strings/string_piece.h" #include "base/system/sys_info.h" #include "base/threading/scoped_blocking_call.h" @@ -29,6 +31,9 @@ #include <winbase.h> #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) #include <sys/mman.h> +#if BUILDFLAG(IS_ANDROID) +#include <sys/prctl.h> +#endif #endif namespace { @@ -959,8 +964,12 @@ LocalPersistentMemoryAllocator::LocalPersistentMemoryAllocator( size_t size, uint64_t id, base::StringPiece name) - : PersistentMemoryAllocator(AllocateLocalMemory(size), - size, 0, id, name, false) {} + : PersistentMemoryAllocator(AllocateLocalMemory(size, name), + size, + 0, + id, + name, + false) {} LocalPersistentMemoryAllocator::~LocalPersistentMemoryAllocator() { DeallocateLocalMemory(const_cast<char*>(mem_base_), mem_size_, mem_type_); @@ -968,7 +977,8 @@ LocalPersistentMemoryAllocator::~LocalPersistentMemoryAllocator() { // static PersistentMemoryAllocator::Memory -LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size) { +LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size, + base::StringPiece name) { void* address; #if BUILDFLAG(IS_WIN) @@ -981,8 +991,16 @@ LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size) { // 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) + if (address != MAP_FAILED) { +#if BUILDFLAG(IS_ANDROID) + // Allow the anonymous memory region allocated by mmap(MAP_ANON) to be + // identified in /proc/$PID/smaps. This helps improve visibility into + // Chrome's memory usage on Android. + const std::string arena_name = base::StrCat({"persistent:", name}); + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, address, size, arena_name.c_str()); +#endif return Memory(address, MEM_VIRTUAL); + } #else #error This architecture is not (yet) supported. #endif @@ -1177,6 +1195,14 @@ 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 !BUILDFLAG(IS_NACL) + // TODO(crbug/1432981): Remove these. They are used to investigate unexpected + // failures. + bool ref_found = (ref != 0); + bool raced = false; +#endif // !BUILDFLAG(IS_NACL) + if (!ref) { ref = allocator_->Allocate(size_, type_); if (!ref) @@ -1196,11 +1222,27 @@ void* DelayedPersistentAllocation::Get() const { DCHECK_LE(size_, allocator_->GetAllocSize(existing)); allocator_->ChangeType(ref, 0, type_, /*clear=*/false); ref = existing; +#if !BUILDFLAG(IS_NACL) + raced = true; +#endif // !BUILDFLAG(IS_NACL) } } char* mem = allocator_->GetAsArray<char>(ref, type_, size_); if (!mem) { +#if !BUILDFLAG(IS_NACL) + // TODO(crbug/1432981): Remove these. They are used to investigate + // unexpected failures. + SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "full", + allocator_->IsFull()); + SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "corrupted", + allocator_->IsCorrupt()); + SCOPED_CRASH_KEY_NUMBER("PersistentMemoryAllocator", "ref", ref); + SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "ref_found", ref_found); + SCOPED_CRASH_KEY_BOOL("PersistentMemoryAllocator", "raced", raced); + SCOPED_CRASH_KEY_NUMBER("PersistentMemoryAllocator", "type_", type_); + SCOPED_CRASH_KEY_NUMBER("PersistentMemoryAllocator", "size_", size_); +#endif // !BUILDFLAG(IS_NACL) // This should never happen but be tolerant if it does as corruption from // the outside is something to guard against. NOTREACHED(); diff --git a/base/metrics/persistent_memory_allocator.h b/base/metrics/persistent_memory_allocator.h index e5ef0ffa3..806df91c3 100644 --- a/base/metrics/persistent_memory_allocator.h +++ b/base/metrics/persistent_memory_allocator.h @@ -737,7 +737,7 @@ class BASE_EXPORT LocalPersistentMemoryAllocator // Allocates a block of local memory of the specified |size|, ensuring that // the memory will not be physically allocated until accessed and will read // as zero when that happens. - static Memory AllocateLocalMemory(size_t size); + static Memory AllocateLocalMemory(size_t size, base::StringPiece name); // Deallocates a block of local |memory| of the specified |size|. static void DeallocateLocalMemory(void* memory, size_t size, MemoryType type); diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc index 52bab7cec..de7512207 100644 --- a/base/metrics/persistent_sample_map.cc +++ b/base/metrics/persistent_sample_map.cc @@ -7,6 +7,7 @@ #include "base/atomicops.h" #include "base/check_op.h" #include "base/containers/contains.h" +#include "base/debug/crash_logging.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/persistent_histogram_allocator.h" #include "base/notreached.h" @@ -193,6 +194,13 @@ PersistentSampleMap::CreatePersistentRecord( Sample value) { SampleRecord* record = allocator->New<SampleRecord>(); if (!record) { +#if !BUILDFLAG(IS_NACL) + // TODO(crbug/1432981): Remove these. They are used to investigate + // unexpected failures. + SCOPED_CRASH_KEY_BOOL("PersistentSampleMap", "full", allocator->IsFull()); + SCOPED_CRASH_KEY_BOOL("PersistentSampleMap", "corrupted", + allocator->IsCorrupt()); +#endif // !BUILDFLAG(IS_NACL) NOTREACHED() << "full=" << allocator->IsFull() << ", corrupt=" << allocator->IsCorrupt(); return 0; diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc index e992edec0..0192748a5 100644 --- a/base/metrics/sample_vector.cc +++ b/base/metrics/sample_vector.cc @@ -7,6 +7,7 @@ #include <ostream> #include "base/check_op.h" +#include "base/debug/crash_logging.h" #include "base/lazy_instance.h" #include "base/memory/ptr_util.h" #include "base/metrics/persistent_memory_allocator.h" @@ -324,6 +325,16 @@ bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter, // 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)) { +#if !BUILDFLAG(IS_NACL) + // TODO(crbug/1432981): Remove these. They are used to investigate + // unexpected failures. + SCOPED_CRASH_KEY_NUMBER("SampleVector", "min", min); + SCOPED_CRASH_KEY_NUMBER("SampleVector", "max", max); + SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_min", + bucket_ranges_->range(dest_index)); + SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_max", + bucket_ranges_->range(dest_index + 1)); +#endif // !BUILDFLAG(IS_NACL) NOTREACHED() << "sample=" << min << "," << max << "; range=" << bucket_ranges_->range(dest_index) << "," << bucket_ranges_->range(dest_index + 1); diff --git a/base/nix/xdg_util.cc b/base/nix/xdg_util.cc index fd4076d06..06ebf54b6 100644 --- a/base/nix/xdg_util.cc +++ b/base/nix/xdg_util.cc @@ -86,6 +86,9 @@ DesktopEnvironment GetDesktopEnvironment(Environment* env) { if (kde_session == "5") { return DESKTOP_ENVIRONMENT_KDE5; } + if (kde_session == "6") { + return DESKTOP_ENVIRONMENT_KDE6; + } } return DESKTOP_ENVIRONMENT_KDE4; } @@ -152,6 +155,8 @@ const char* GetDesktopEnvironmentName(DesktopEnvironment env) { return "KDE4"; case DESKTOP_ENVIRONMENT_KDE5: return "KDE5"; + case DESKTOP_ENVIRONMENT_KDE6: + return "KDE6"; case DESKTOP_ENVIRONMENT_PANTHEON: return "PANTHEON"; case DESKTOP_ENVIRONMENT_UNITY: diff --git a/base/nix/xdg_util.h b/base/nix/xdg_util.h index 4d26721d7..7085e152e 100644 --- a/base/nix/xdg_util.h +++ b/base/nix/xdg_util.h @@ -22,11 +22,12 @@ enum DesktopEnvironment { DESKTOP_ENVIRONMENT_CINNAMON = 1, DESKTOP_ENVIRONMENT_DEEPIN = 2, DESKTOP_ENVIRONMENT_GNOME = 3, - // KDE3, KDE4 and KDE5 are sufficiently different that we count + // KDE{3,4,5,6} are sufficiently different that we count // them as different desktop environments here. DESKTOP_ENVIRONMENT_KDE3 = 4, DESKTOP_ENVIRONMENT_KDE4 = 5, DESKTOP_ENVIRONMENT_KDE5 = 6, + DESKTOP_ENVIRONMENT_KDE6 = 12, DESKTOP_ENVIRONMENT_PANTHEON = 7, DESKTOP_ENVIRONMENT_UKUI = 8, DESKTOP_ENVIRONMENT_UNITY = 9, diff --git a/base/nix/xdg_util_unittest.cc b/base/nix/xdg_util_unittest.cc index bd028681f..19f8aad46 100644 --- a/base/nix/xdg_util_unittest.cc +++ b/base/nix/xdg_util_unittest.cc @@ -44,6 +44,7 @@ const char* const kXdgDesktopUnity = "Unity"; const char* const kXdgDesktopUnity7 = "Unity:Unity7"; const char* const kXdgDesktopUnity8 = "Unity:Unity8"; const char* const kKDESessionKDE5 = "5"; +const char* const kKDESessionKDE6 = "6"; const char kDesktopSession[] = "DESKTOP_SESSION"; const char kKDESession[] = "KDE_SESSION_VERSION"; @@ -162,6 +163,17 @@ TEST(XDGUtilTest, GetXdgDesktopKDE5) { EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE5, GetDesktopEnvironment(&getter)); } +TEST(XDGUtilTest, GetXdgDesktopKDE6) { + MockEnvironment getter; + EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(getter, GetVar(Eq(kXdgCurrentDesktopEnvVar), _)) + .WillOnce(DoAll(SetArgPointee<1>(kXdgDesktopKDE), Return(true))); + EXPECT_CALL(getter, GetVar(Eq(kKDESession), _)) + .WillOnce(DoAll(SetArgPointee<1>(kKDESessionKDE6), Return(true))); + + EXPECT_EQ(DESKTOP_ENVIRONMENT_KDE6, GetDesktopEnvironment(&getter)); +} + TEST(XDGUtilTest, GetXdgDesktopKDE4) { MockEnvironment getter; EXPECT_CALL(getter, GetVar(_, _)).WillRepeatedly(Return(false)); diff --git a/base/process/process_metrics.cc b/base/process/process_metrics.cc index 1b88164d3..a3b441d79 100644 --- a/base/process/process_metrics.cc +++ b/base/process/process_metrics.cc @@ -90,6 +90,8 @@ Value::Dict SystemMetrics::ToDict() const { return res; } +ProcessMetrics::~ProcessMetrics() = default; + std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateCurrentProcessMetrics() { #if !BUILDFLAG(IS_MAC) return CreateProcessMetrics(base::GetCurrentProcessHandle()); diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h index 79d1238d5..4e2541958 100644 --- a/base/process/process_metrics.h +++ b/base/process/process_metrics.h @@ -605,7 +605,7 @@ class BASE_EXPORT SystemMetrics { #endif }; -#if BUILDFLAG(IS_MAC) +#if BUILDFLAG(IS_APPLE) enum class MachVMRegionResult { // There were no more memory regions between |address| and the end of the // virtual address space. @@ -619,17 +619,6 @@ enum class MachVMRegionResult { }; // Returns info on the first memory region at or after |address|, including -// resident memory and share mode. On Success, |size| reflects the size of the -// memory region. -// |size| and |info| are output parameters, only valid on Success. -// |address| is an in-out parameter, than represents both the address to start -// looking, and the start address of the memory region. -BASE_EXPORT MachVMRegionResult GetTopInfo(mach_port_t task, - mach_vm_size_t* size, - mach_vm_address_t* address, - vm_region_top_info_data_t* info); - -// Returns info on the first memory region at or after |address|, including // protection values. On Success, |size| reflects the size of the // memory region. // Returns info on the first memory region at or after |address|, including @@ -639,6 +628,19 @@ BASE_EXPORT MachVMRegionResult GetBasicInfo(mach_port_t task, mach_vm_size_t* size, mach_vm_address_t* address, vm_region_basic_info_64* info); +#endif // BUILDFLAG(IS_APPLE) + +#if BUILDFLAG(IS_MAC) +// Returns info on the first memory region at or after |address|, including +// resident memory and share mode. On Success, |size| reflects the size of the +// memory region. +// |size| and |info| are output parameters, only valid on Success. +// |address| is an in-out parameter, than represents both the address to start +// looking, and the start address of the memory region. +BASE_EXPORT MachVMRegionResult GetTopInfo(mach_port_t task, + mach_vm_size_t* size, + mach_vm_address_t* address, + vm_region_top_info_data_t* info); #endif // BUILDFLAG(IS_MAC) } // namespace base diff --git a/base/process/process_metrics_ios.cc b/base/process/process_metrics_ios.cc index 685cc2658..a5571e899 100644 --- a/base/process/process_metrics_ios.cc +++ b/base/process/process_metrics_ios.cc @@ -6,6 +6,7 @@ #include <limits.h> #include <mach/task.h> +#include <mach/vm_region.h> #include <malloc/malloc.h> #include <stddef.h> @@ -14,13 +15,12 @@ #include "base/memory/ptr_util.h" #include "base/notreached.h" #include "base/numerics/safe_conversions.h" +#include "build/blink_buildflags.h" namespace base { ProcessMetrics::ProcessMetrics(ProcessHandle process) {} -ProcessMetrics::~ProcessMetrics() {} - // static std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics( ProcessHandle process) { @@ -32,6 +32,10 @@ TimeDelta ProcessMetrics::GetCumulativeCPUUsage() { return TimeDelta(); } +// The blink code path pulls in process_metrics_posix.cc which +// is used for the following implementations. +#if !BUILDFLAG(USE_BLINK) + size_t GetMaxFds() { static const rlim_t kSystemDefaultMaxFds = 256; rlim_t max_fds; @@ -53,6 +57,13 @@ void IncreaseFdLimitTo(unsigned int max_descriptors) { // Unimplemented. } +size_t ProcessMetrics::GetMallocUsage() { + malloc_statistics_t stats; + malloc_zone_statistics(nullptr, &stats); + return stats.size_in_use; +} +#endif // !BUILDFLAG(USE_BLINK) + // Bytes committed by the system. size_t GetSystemCommitCharge() { NOTIMPLEMENTED(); @@ -95,10 +106,28 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { return true; } -size_t ProcessMetrics::GetMallocUsage() { - malloc_statistics_t stats; - malloc_zone_statistics(nullptr, &stats); - return stats.size_in_use; +MachVMRegionResult ParseOutputFromVMRegion(kern_return_t kr) { + if (kr == KERN_INVALID_ADDRESS) { + // We're at the end of the address space. + return MachVMRegionResult::Finished; + } else if (kr != KERN_SUCCESS) { + return MachVMRegionResult::Error; + } + return MachVMRegionResult::Success; +} + +MachVMRegionResult GetBasicInfo(mach_port_t task, + mach_vm_size_t* size, + mach_vm_address_t* address, + vm_region_basic_info_64* info) { + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + mac::ScopedMachSendRight object_name; + kern_return_t kr = + vm_region_64(task, reinterpret_cast<vm_address_t*>(address), + reinterpret_cast<vm_size_t*>(size), VM_REGION_BASIC_INFO_64, + reinterpret_cast<vm_region_info_t>(info), &info_count, + mac::ScopedMachSendRight::Receiver(object_name).get()); + return ParseOutputFromVMRegion(kr); } } // namespace base diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc index 873a328aa..9cb9d92bb 100644 --- a/base/process/process_metrics_posix.cc +++ b/base/process/process_metrics_posix.cc @@ -39,8 +39,6 @@ int64_t TimeValToMicroseconds(const struct timeval& tv) { return ret; } -ProcessMetrics::~ProcessMetrics() = default; - #if !BUILDFLAG(IS_FUCHSIA) #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) diff --git a/base/process/process_metrics_win.cc b/base/process/process_metrics_win.cc index 41bd51e39..e8987bcee 100644 --- a/base/process/process_metrics_win.cc +++ b/base/process/process_metrics_win.cc @@ -122,8 +122,6 @@ struct SYSTEM_PERFORMANCE_INFORMATION { } // namespace -ProcessMetrics::~ProcessMetrics() { } - size_t GetMaxFds() { // Windows is only limited by the amount of physical memory. return std::numeric_limits<size_t>::max(); diff --git a/base/profiler/native_unwinder_android_unittest.cc b/base/profiler/native_unwinder_android_unittest.cc index 3fe4adf26..29124ae14 100644 --- a/base/profiler/native_unwinder_android_unittest.cc +++ b/base/profiler/native_unwinder_android_unittest.cc @@ -16,6 +16,7 @@ #include "base/android/build_info.h" #include "base/android/jni_android.h" #include "base/functional/bind.h" +#include "base/memory/raw_ptr.h" #include "base/profiler/native_unwinder_android_map_delegate.h" #include "base/profiler/native_unwinder_android_memory_regions_map.h" #include "base/profiler/register_context.h" @@ -65,8 +66,8 @@ class NativeUnwinderAndroidMemoryRegionsMapForTesting std::unique_ptr<unwindstack::Memory> TakeMemory() override { return nullptr; } private: - unwindstack::Maps* maps_; - unwindstack::Memory* memory_; + raw_ptr<unwindstack::Maps> maps_; + raw_ptr<unwindstack::Memory> memory_; }; class NativeUnwinderAndroidMapDelegateForTesting diff --git a/base/profiler/stack_sampler.cc b/base/profiler/stack_sampler.cc index 65d3eb9d1..b1659b1f8 100644 --- a/base/profiler/stack_sampler.cc +++ b/base/profiler/stack_sampler.cc @@ -150,8 +150,15 @@ void StackSampler::RecordStackFrames(StackBuffer* stack_buffer, if ((++stack_size_histogram_sampling_counter_ % kUMAHistogramDownsampleAmount) == 0) { // Record the size of the stack to tune kLargeStackSize. - UmaHistogramMemoryKB("Memory.StackSamplingProfiler.StackSampleSize", - saturated_cast<int>(stack_size / kBytesPerKilobyte)); + // UmaHistogramMemoryKB has a min of 1000, which isn't useful for our + // purposes, so call UmaHistogramCustomCounts directly. + // Min is 4KB, since that's the normal pagesize and setting kLargeStackSize + // smaller than that would be pointless. Max is 8MB since that's the + // current ChromeOS stack size; we shouldn't be able to get a number + // larger than that. + UmaHistogramCustomCounts( + "Memory.StackSamplingProfiler.StackSampleSize2", + saturated_cast<int>(stack_size / kBytesPerKilobyte), 4, 8 * 1024, 50); } // We expect to very rarely see stacks larger than kLargeStackSize. If we see diff --git a/base/profiler/stack_sampler.h b/base/profiler/stack_sampler.h index 962245392..d6154ce16 100644 --- a/base/profiler/stack_sampler.h +++ b/base/profiler/stack_sampler.h @@ -94,7 +94,7 @@ class BASE_EXPORT StackSampler { StackSamplerTestDelegate* test_delegate = nullptr); #if BUILDFLAG(IS_CHROMEOS) - // How often to record the "Memory.StackSamplingProfiler.StackSampleSize" UMA + // How often to record the "Memory.StackSamplingProfiler.StackSampleSize2" UMA // histogram. Specifically, only 1 in kUMAHistogramDownsampleAmount calls to // RecordStackFrames will add a sample to the histogram. RecordStackFrames is // called many times a second. We don't need multiple samples per second to @@ -130,7 +130,7 @@ class BASE_EXPORT StackSampler { const raw_ptr<StackSamplerTestDelegate> test_delegate_; #if BUILDFLAG(IS_CHROMEOS) - // Counter for "Memory.StackSamplingProfiler.StackSampleSize" UMA histogram. + // Counter for "Memory.StackSamplingProfiler.StackSampleSize2" UMA histogram. // See comments above kUMAHistogramDownsampleAmount. Unsigned so that overflow // isn't undefined behavior. uint32_t stack_size_histogram_sampling_counter_ = 0; diff --git a/base/profiler/stack_sampler_unittest.cc b/base/profiler/stack_sampler_unittest.cc index 41d593f3c..c5b9b5fc7 100644 --- a/base/profiler/stack_sampler_unittest.cc +++ b/base/profiler/stack_sampler_unittest.cc @@ -319,16 +319,16 @@ TEST(StackSamplerTest, RecordStackFramesUMAMetric) { PlatformThread::CurrentId()); // Should have no new samples in the - // Memory.StackSamplingProfiler.StackSampleSize histogram. + // Memory.StackSamplingProfiler.StackSampleSize2 histogram. histogram_tester.ExpectUniqueSample( - "Memory.StackSamplingProfiler.StackSampleSize", kExpectedSizeKB, 0); + "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 0); } stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder, PlatformThread::CurrentId()); histogram_tester.ExpectUniqueSample( - "Memory.StackSamplingProfiler.StackSampleSize", kExpectedSizeKB, 1); + "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 1); } #endif // #if BUILDFLAG(IS_CHROMEOS) diff --git a/base/strings/DEPS b/base/strings/DEPS deleted file mode 100644 index db5863570..000000000 --- a/base/strings/DEPS +++ /dev/null @@ -1,8 +0,0 @@ -specific_include_rules = { - # absl::string_view is generally banned in Chromium. Including it is only - # allowed from abseil_string_conversions* to provide appropriate conversion - # functions from and to StringPiece at third party API boundaries. - "abseil_string_conversions(\.h|\.cc|_unittest.cc)": [ - "+third_party/abseil-cpp/absl/strings/string_view.h", - ], -} diff --git a/base/strings/abseil_string_conversions.cc b/base/strings/abseil_string_conversions.cc deleted file mode 100644 index 9606ee2f6..000000000 --- a/base/strings/abseil_string_conversions.cc +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/strings/abseil_string_conversions.h" - -#include <vector> - -#include "base/containers/span.h" -#include "base/ranges/algorithm.h" -#include "base/strings/string_piece.h" -#include "third_party/abseil-cpp/absl/strings/string_view.h" - -namespace base { - -std::vector<absl::string_view> StringPiecesToStringViews( - span<const StringPiece> pieces) { - std::vector<absl::string_view> views(pieces.size()); - ranges::transform(pieces, views.begin(), &StringPieceToStringView); - return views; -} - -std::vector<StringPiece> StringViewsToStringPieces( - span<const absl::string_view> views) { - std::vector<StringPiece> pieces(views.size()); - ranges::transform(views, pieces.begin(), &StringViewToStringPiece); - return pieces; -} - -} // namespace base diff --git a/base/strings/abseil_string_conversions.h b/base/strings/abseil_string_conversions.h deleted file mode 100644 index 5b8e045d5..000000000 --- a/base/strings/abseil_string_conversions.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef BASE_STRINGS_ABSEIL_STRING_CONVERSIONS_H_ -#define BASE_STRINGS_ABSEIL_STRING_CONVERSIONS_H_ - -#include <vector> - -#include "base/base_export.h" -#include "base/containers/span.h" -#include "base/strings/string_piece.h" -#include "third_party/abseil-cpp/absl/strings/string_view.h" - -namespace base { - -// Converts `piece` to a string view, pointing to the same piece of memory. -constexpr absl::string_view StringPieceToStringView(StringPiece piece) { - return {piece.data(), piece.size()}; -} - -// Converts `view` to a string piece, pointing to the same piece of memory. -constexpr StringPiece StringViewToStringPiece(absl::string_view view) { - return {view.data(), view.size()}; -} - -// Converts `pieces` to string views, pointing to the same piece of memory. -BASE_EXPORT std::vector<absl::string_view> StringPiecesToStringViews( - span<const StringPiece> pieces); - -// Converts `views` to string pieces, pointing to the same piece of memory. -BASE_EXPORT std::vector<StringPiece> StringViewsToStringPieces( - span<const absl::string_view> views); - -} // namespace base - -#endif // BASE_STRINGS_ABSEIL_STRING_CONVERSIONS_H_ diff --git a/base/strings/abseil_string_conversions_unittest.cc b/base/strings/abseil_string_conversions_unittest.cc deleted file mode 100644 index 106d1a62b..000000000 --- a/base/strings/abseil_string_conversions_unittest.cc +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/strings/abseil_string_conversions.h" - -#include <vector> - -#include "base/containers/span.h" -#include "base/strings/string_piece.h" -#include "base/strings/string_piece_forward.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "third_party/abseil-cpp/absl/strings/string_view.h" - -namespace base { - -namespace { -using string_view = absl::string_view; -} // namespace - -TEST(AbseilStringConversionsTest, StringPieceToStringView) { - static constexpr StringPiece kPiece = "foo"; - static constexpr string_view kView = StringPieceToStringView(kPiece); - static_assert(kPiece.data() == kView.data(), ""); - static_assert(kPiece.size() == kView.size(), ""); -} - -TEST(AbseilStringConversionsTest, StringViewToStringPiece) { - static constexpr string_view kView = "bar"; - static constexpr StringPiece kPiece = StringViewToStringPiece(kView); - static_assert(kView.data() == kPiece.data(), ""); - static_assert(kView.size() == kPiece.size(), ""); -} - -TEST(AbseilStringConversionsTest, StringPiecesToStringViews) { - static constexpr StringPiece kFoo = "foo"; - static constexpr StringPiece kBar = "bar"; - static constexpr StringPiece kBaz = "baz"; - - const std::vector<StringPiece> kPieces = {kFoo, kBar, kBaz}; - const std::vector<string_view> kViews = StringPiecesToStringViews(kPieces); - - ASSERT_EQ(kViews.size(), 3u); - EXPECT_EQ(kViews[0].data(), kFoo); - EXPECT_EQ(kViews[0].size(), 3u); - EXPECT_EQ(kViews[1].data(), kBar); - EXPECT_EQ(kViews[1].size(), 3u); - EXPECT_EQ(kViews[2].data(), kBaz); - EXPECT_EQ(kViews[2].size(), 3u); -} - -TEST(AbseilStringConversionsTest, StringViewsToStringPieces) { - static constexpr string_view kFoo = "foo"; - static constexpr string_view kBar = "bar"; - static constexpr string_view kBaz = "baz"; - - const std::vector<string_view> kViews = {kFoo, kBar, kBaz}; - const std::vector<StringPiece> kPieces = StringViewsToStringPieces(kViews); - - ASSERT_EQ(kPieces.size(), 3u); - EXPECT_EQ(kPieces[0].data(), kFoo); - EXPECT_EQ(kPieces[0].size(), 3u); - EXPECT_EQ(kPieces[1].data(), kBar); - EXPECT_EQ(kPieces[1].size(), 3u); - EXPECT_EQ(kPieces[2].data(), kBaz); - EXPECT_EQ(kPieces[2].size(), 3u); -} - -} // namespace base diff --git a/base/strings/escape.cc b/base/strings/escape.cc index d76bb6e2a..d303ead90 100644 --- a/base/strings/escape.cc +++ b/base/strings/escape.cc @@ -543,9 +543,20 @@ std::string UnescapeBinaryURLComponent(StringPiece escaped_text, DCHECK(!(rules & ~(UnescapeRule::NORMAL | UnescapeRule::REPLACE_PLUS_WITH_SPACE))); + // It is not possible to read the feature state when this function is invoked + // before FeatureList initialization. In that case, fallback to the feature's + // default state. + // + // TODO(crbug.com/1321924): Cleanup this feature. + const bool optimize_data_urls_feature_is_enabled = + base::FeatureList::GetInstance() + ? base::FeatureList::IsEnabled(features::kOptimizeDataUrls) + : features::kOptimizeDataUrls.default_state == + base::FEATURE_ENABLED_BY_DEFAULT; + // If there are no '%' characters in the string, there will be nothing to // unescape, so we can take the fast path. - if (base::FeatureList::IsEnabled(features::kOptimizeDataUrls) && + if (optimize_data_urls_feature_is_enabled && escaped_text.find('%') == StringPiece::npos) { std::string unescaped_text(escaped_text); if (rules & UnescapeRule::REPLACE_PLUS_WITH_SPACE) diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h index 9dde6ec29..61273898b 100644 --- a/base/strings/string_piece.h +++ b/base/strings/string_piece.h @@ -28,6 +28,7 @@ #include <iosfwd> #include <limits> #include <string> +#include <string_view> #include <type_traits> #include "base/base_export.h" @@ -138,6 +139,7 @@ class GSL_POINTER BasicStringPiece { // `BasicStringPiece(nullptr_t) = delete`, but unfortunately the terse form is // not supported by the PNaCl toolchain. template <class T, class = std::enable_if_t<std::is_null_pointer<T>::value>> + // NOLINTNEXTLINE(google-explicit-constructor) BasicStringPiece(T) { static_assert(sizeof(T) == 0, // Always false. "StringPiece does not support construction from nullptr, use " @@ -148,12 +150,25 @@ class GSL_POINTER BasicStringPiece { // (an object convertible to) a std::basic_string_view, as well as an explicit // cast operator to a std::basic_string_view, but (obviously) not from/to a // BasicStringPiece. + // NOLINTNEXTLINE(google-explicit-constructor) BasicStringPiece(const std::basic_string<CharT>& str) : ptr_(str.data()), length_(str.size()) {} explicit operator std::basic_string<CharT>() const { return std::basic_string<CharT>(data(), size()); } + // Provide implicit conversions from/to the STL version, for interoperability + // with non-Chromium code. + // TODO(crbug.com/691162): These will be moot when BasicStringPiece is + // replaced with std::basic_string_view. + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr BasicStringPiece(std::basic_string_view<CharT> str) + : ptr_(str.data()), length_(str.size()) {} + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator std::basic_string_view<CharT>() const { + return std::basic_string_view<CharT>(data(), size()); + } + constexpr const_iterator begin() const noexcept { return ptr_; } constexpr const_iterator cbegin() const noexcept { return ptr_; } constexpr const_iterator end() const noexcept { return ptr_ + length_; } diff --git a/base/strings/string_piece_unittest.cc b/base/strings/string_piece_unittest.cc index cebb31962..9baf7ad30 100644 --- a/base/strings/string_piece_unittest.cc +++ b/base/strings/string_piece_unittest.cc @@ -5,6 +5,7 @@ #include <stddef.h> #include <string> +#include <string_view> #include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" @@ -893,4 +894,19 @@ TEST(StringPieceTest, Find) { static_assert(foobar.find_last_not_of("ox", 2) == 0, ""); } +// Test that `base::StringPiece` and `std::string_view` are interoperable. +TEST(StringPieceTest, StringPieceToStringView) { + constexpr StringPiece kPiece = "foo"; + constexpr std::string_view kView = kPiece; + static_assert(kPiece.data() == kView.data()); + static_assert(kPiece.size() == kView.size()); +} + +TEST(StringPieceTest, StringViewToStringPiece) { + constexpr std::string_view kView = "bar"; + constexpr StringPiece kPiece = kView; + static_assert(kView.data() == kPiece.data()); + static_assert(kView.size() == kPiece.size()); +} + } // namespace base diff --git a/base/strings/string_util.h b/base/strings/string_util.h index 120ffb20b..0367af552 100644 --- a/base/strings/string_util.h +++ b/base/strings/string_util.h @@ -13,8 +13,6 @@ #include <stdint.h> #include <initializer_list> -#include <memory> -#include <sstream> #include <string> #include <type_traits> #include <vector> @@ -26,7 +24,6 @@ #include "base/cxx20_to_address.h" #include "base/strings/string_piece.h" // For implicit conversions. #include "base/strings/string_util_internal.h" -#include "base/template_util.h" #include "build/build_config.h" namespace base { @@ -116,93 +113,6 @@ constexpr WStringPiece MakeWStringPiece(Iter begin, Iter end) { return MakeBasicStringPiece<wchar_t>(begin, end); } -// Convert a type with defined `operator<<` or `.ToString()` method into a -// string. - -// I/O manipulators are function pointers, but should be sent directly to the -// `ostream` instead of being cast to `const void*` like other function -// pointers. -template <typename T, typename = void> -constexpr bool IsIomanip = false; -template <typename T> -constexpr bool - IsIomanip<T&(T&), std::enable_if_t<std::is_base_of_v<std::ios_base, T>>> = - true; - -// Function pointers implicitly convert to `bool`, so use this to avoid printing -// function pointers as 1 or 0. -template <typename T, typename = void> -constexpr bool WillBeIncorrectlyStreamedAsBool = false; -template <typename T> -constexpr bool WillBeIncorrectlyStreamedAsBool< - T, - std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<T>> && - !IsIomanip<typename std::remove_pointer_t<T>>>> = true; - -// Fallback case when there is no better representation. -template <typename T, typename = void> -struct ToStringHelper { - static void Stringify(const T& v, std::ostringstream& ss) { - ss << "[" << sizeof(v) << "-byte object at 0x" << std::addressof(v) << "]"; - } -}; - -// Most streamables. -template <typename T> -struct ToStringHelper< - T, - std::enable_if_t<base::internal::SupportsOstreamOperator<const T&>::value && - !WillBeIncorrectlyStreamedAsBool<T>>> { - static void Stringify(const T& v, std::ostringstream& ss) { ss << v; } -}; - -// Functions and function pointers. -template <typename T> -struct ToStringHelper< - T, - std::enable_if_t<base::internal::SupportsOstreamOperator<const T&>::value && - WillBeIncorrectlyStreamedAsBool<T>>> { - static void Stringify(const T& v, std::ostringstream& ss) { - ToStringHelper<const void*>::Stringify(reinterpret_cast<const void*>(v), - ss); - } -}; - -// Non-streamables that have a `ToString` member. -template <typename T> -struct ToStringHelper< - T, - std::enable_if_t< - !base::internal::SupportsOstreamOperator<const T&>::value && - base::internal::SupportsToString<const T&>::value>> { - static void Stringify(const T& v, std::ostringstream& ss) { - // .ToString() may not return a std::string, e.g. blink::WTF::String. - ToStringHelper<decltype(v.ToString())>::Stringify(v.ToString(), ss); - } -}; - -// Non-streamable enums (i.e. scoped enums where no `operator<<` overload was -// declared). -template <typename T> -struct ToStringHelper<T, - std::enable_if_t<!base::internal::SupportsOstreamOperator< - const T&>::value && - std::is_enum_v<T>>> { - static void Stringify(const T& v, std::ostringstream& ss) { - using UT = typename std::underlying_type_t<T>; - ToStringHelper<UT>::Stringify(static_cast<UT>(v), ss); - } -}; - -template <typename... Ts> -std::string ToString(const Ts&... values) { - std::ostringstream ss; - (ToStringHelper<typename std::remove_cvref_t<decltype(values)>>::Stringify( - values, ss), - ...); - return ss.str(); -} - // ASCII-specific tolower. The standard library's tolower is locale sensitive, // so we don't want to use it here. template <typename CharT, diff --git a/base/strings/string_util_unittest.cc b/base/strings/string_util_unittest.cc index aba0e3143..4bb22e54c 100644 --- a/base/strings/string_util_unittest.cc +++ b/base/strings/string_util_unittest.cc @@ -1351,80 +1351,6 @@ TEST(StringUtilTest, MakeBasicStringPieceTest) { EXPECT_TRUE(MakeWStringPiece(baz.end(), baz.end()).empty()); } -enum class StreamableTestEnum { kGreeting, kLocation }; -enum class NonStreamableTestEnum { kGreeting = 0, kLocation }; - -std::ostream& operator<<(std::ostream& os, const StreamableTestEnum& value) { - switch (value) { - case StreamableTestEnum::kGreeting: - return os << "hello"; - case StreamableTestEnum::kLocation: - return os << "world"; - } -} - -class HasToString { - public: - std::string ToString() const { return "yay!"; } -}; - -class UnusualToString { - public: - HasToString ToString() const { return HasToString(); } -}; - -void Func() {} - -class NotStringifiable {}; - -class OverloadsAddressOp { - public: - OverloadsAddressOp* operator&() { return nullptr; } - const OverloadsAddressOp* operator&() const { return nullptr; } -}; - -TEST(StringUtilTest, ToString) { - // Types with built-in <<. - EXPECT_EQ(ToString("foo"), "foo"); - EXPECT_EQ(ToString(123), "123"); - - // Type with user-defined <<. - EXPECT_EQ(ToString(StreamableTestEnum::kGreeting), "hello"); - EXPECT_EQ(ToString(StreamableTestEnum::kGreeting, " ", - StreamableTestEnum::kLocation), - "hello world"); - - // Type with user-defined ToString(). - EXPECT_EQ(ToString(HasToString()), "yay!"); - - // Types with a ToString() that does not directly return a std::string should - // still work. - EXPECT_EQ(ToString(UnusualToString()), "yay!"); - - // Scoped enums without a defined << should print as their underlying type. - EXPECT_EQ(ToString(NonStreamableTestEnum::kLocation), "1"); - - // I/O manipulators should have their expected effect, not be printed as - // function pointers. - EXPECT_EQ(ToString("42 in hex is ", std::hex, 42), "42 in hex is 2a"); - - // We don't care about the actual address, but a function pointer should not - // be implicitly converted to bool. - EXPECT_NE(ToString(&Func), ToString(true)); - - // Functions should be treated like function pointers. - EXPECT_EQ(ToString(Func), ToString(&Func)); - - // Non-stringifiable types should be printed using a fallback. - EXPECT_NE(ToString(NotStringifiable()).find("-byte object at 0x"), - std::string::npos); - - // Non-stringifiable types which overload operator& should print their real - // address. - EXPECT_NE(ToString(OverloadsAddressOp()), - ToString(static_cast<OverloadsAddressOp*>(nullptr))); -} - TEST(StringUtilTest, RemoveChars) { const char kRemoveChars[] = "-/+*"; std::string input = "A-+bc/d!*"; diff --git a/base/strings/to_string.h b/base/strings/to_string.h new file mode 100644 index 000000000..216963efe --- /dev/null +++ b/base/strings/to_string.h @@ -0,0 +1,110 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_STRINGS_TO_STRING_H_ +#define BASE_STRINGS_TO_STRING_H_ + +#include <ios> +#include <memory> +#include <sstream> +#include <string> +#include <type_traits> + +#include "base/template_util.h" +#include "base/types/supports_ostream_operator.h" + +namespace base { + +namespace internal { + +// I/O manipulators are function pointers, but should be sent directly to the +// `ostream` instead of being cast to `const void*` like other function +// pointers. +template <typename T, typename = void> +constexpr bool IsIomanip = false; +template <typename T> +constexpr bool + IsIomanip<T&(T&), std::enable_if_t<std::is_base_of_v<std::ios_base, T>>> = + true; + +// Function pointers implicitly convert to `bool`, so use this to avoid printing +// function pointers as 1 or 0. +template <typename T, typename = void> +constexpr bool WillBeIncorrectlyStreamedAsBool = false; +template <typename T> +constexpr bool WillBeIncorrectlyStreamedAsBool< + T, + std::enable_if_t<std::is_function_v<std::remove_pointer_t<T>> && + !IsIomanip<std::remove_pointer_t<T>>>> = true; + +// Fallback case when there is no better representation. +template <typename T, typename = void> +struct ToStringHelper { + static void Stringify(const T& v, std::ostringstream& ss) { + ss << "[" << sizeof(v) << "-byte object at 0x" << std::addressof(v) << "]"; + } +}; + +// Most streamables. +template <typename T> +struct ToStringHelper< + T, + std::enable_if_t<SupportsOstreamOperator<const T&>::value && + !WillBeIncorrectlyStreamedAsBool<T>>> { + static void Stringify(const T& v, std::ostringstream& ss) { ss << v; } +}; + +// Functions and function pointers. +template <typename T> +struct ToStringHelper< + T, + std::enable_if_t<SupportsOstreamOperator<const T&>::value && + WillBeIncorrectlyStreamedAsBool<T>>> { + static void Stringify(const T& v, std::ostringstream& ss) { + ToStringHelper<const void*>::Stringify(reinterpret_cast<const void*>(v), + ss); + } +}; + +// Non-streamables that have a `ToString` member. +template <typename T> +struct ToStringHelper< + T, + std::enable_if_t<!SupportsOstreamOperator<const T&>::value && + SupportsToString<const T&>::value>> { + static void Stringify(const T& v, std::ostringstream& ss) { + // .ToString() may not return a std::string, e.g. blink::WTF::String. + ToStringHelper<decltype(v.ToString())>::Stringify(v.ToString(), ss); + } +}; + +// Non-streamable enums (i.e. scoped enums where no `operator<<` overload was +// declared). +template <typename T> +struct ToStringHelper< + T, + std::enable_if_t<!SupportsOstreamOperator<const T&>::value && + std::is_enum_v<T>>> { + static void Stringify(const T& v, std::ostringstream& ss) { + using UT = typename std::underlying_type_t<T>; + ToStringHelper<UT>::Stringify(static_cast<UT>(v), ss); + } +}; + +} // namespace internal + +// Converts any type to a string, preferring defined operator<<() or ToString() +// methods if they exist. +template <typename... Ts> +std::string ToString(const Ts&... values) { + std::ostringstream ss; + (internal::ToStringHelper<remove_cvref_t<decltype(values)>>::Stringify(values, + ss), + ...); + return ss.str(); +} + +} // namespace base + +#endif // BASE_STRINGS_TO_STRING_H_ diff --git a/base/strings/to_string_test.cc b/base/strings/to_string_test.cc new file mode 100644 index 000000000..793820521 --- /dev/null +++ b/base/strings/to_string_test.cc @@ -0,0 +1,106 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/to_string.h" + +#include <ios> +#include <ostream> +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(ToStringTest, Streamable) { + // Types with built-in <<. + EXPECT_EQ(ToString("foo"), "foo"); + EXPECT_EQ(ToString(123), "123"); +} + +enum class StreamableTestEnum { kGreeting, kLocation }; + +std::ostream& operator<<(std::ostream& os, const StreamableTestEnum& value) { + switch (value) { + case StreamableTestEnum::kGreeting: + return os << "hello"; + case StreamableTestEnum::kLocation: + return os << "world"; + } +} + +TEST(ToStringTest, UserDefinedStreamable) { + // Type with user-defined <<. + EXPECT_EQ(ToString(StreamableTestEnum::kGreeting), "hello"); + EXPECT_EQ(ToString(StreamableTestEnum::kGreeting, " ", + StreamableTestEnum::kLocation), + "hello world"); +} + +class HasToString { + public: + std::string ToString() const { return "yay!"; } +}; + +TEST(ToStringTest, UserDefinedToString) { + // Type with user-defined ToString(). + EXPECT_EQ(ToString(HasToString()), "yay!"); +} + +class UnusualToString { + public: + HasToString ToString() const { return HasToString(); } +}; + +TEST(ToStringTest, ToStringReturnsNonStdString) { + // Types with a ToString() that does not directly return a std::string should + // still work. + EXPECT_EQ(ToString(UnusualToString()), "yay!"); +} + +enum class NonStreamableTestEnum { kGreeting = 0, kLocation }; + +TEST(ToStringTest, ScopedEnum) { + // Scoped enums without a defined << should print as their underlying type. + EXPECT_EQ(ToString(NonStreamableTestEnum::kLocation), "1"); +} + +TEST(ToStringTest, IoManip) { + // I/O manipulators should have their expected effect, not be printed as + // function pointers. + EXPECT_EQ(ToString("42 in hex is ", std::hex, 42), "42 in hex is 2a"); +} + +void Func() {} + +TEST(ToStringTest, FunctionPointer) { + // We don't care about the actual address, but a function pointer should not + // be implicitly converted to bool. + EXPECT_NE(ToString(&Func), ToString(true)); + + // Functions should be treated like function pointers. + EXPECT_EQ(ToString(Func), ToString(&Func)); +} + +class NotStringifiable {}; + +class OverloadsAddressOp { + public: + OverloadsAddressOp* operator&() { return nullptr; } + const OverloadsAddressOp* operator&() const { return nullptr; } +}; + +TEST(ToStringTest, NonStringifiable) { + // Non-stringifiable types should be printed using a fallback. + EXPECT_NE(ToString(NotStringifiable()).find("-byte object at 0x"), + std::string::npos); + + // Non-stringifiable types which overload operator& should print their real + // address. + EXPECT_NE(ToString(OverloadsAddressOp()), + ToString(static_cast<OverloadsAddressOp*>(nullptr))); +} + +} // namespace +} // namespace base diff --git a/base/system/sys_info.cc b/base/system/sys_info.cc index 2c5bfc9bb..9d52ed326 100644 --- a/base/system/sys_info.cc +++ b/base/system/sys_info.cc @@ -8,6 +8,7 @@ #include "base/base_switches.h" #include "base/command_line.h" +#include "base/features.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/location.h" @@ -76,6 +77,47 @@ bool SysInfo::IsLowEndDevice() { return IsLowEndDeviceImpl(); } +#if BUILDFLAG(IS_ANDROID) + +namespace { + +bool IsAndroid4GbOr6GbDevice() { + // Because of Android carveouts, AmountOfPhysicalMemory() returns smaller + // than the actual memory size, So we will use a small lowerbound than 4GB + // to discriminate real 4GB devices from lower memory ones. + constexpr int kLowerBoundMB = 3.2 * 1024; + constexpr int kUpperBoundMB = 6 * 1024; + static bool is_4gb_or_6g_device = + kLowerBoundMB <= base::SysInfo::AmountOfPhysicalMemoryMB() && + base::SysInfo::AmountOfPhysicalMemoryMB() <= kUpperBoundMB; + return is_4gb_or_6g_device; +} + +bool IsPartialLowEndModeOnMidRangeDevicesEnabled() { + // TODO(crbug.com/1434873): make the feature not enable on 32-bit devices + // before launching or going to high Stable %. + return IsAndroid4GbOr6GbDevice() && + base::FeatureList::IsEnabled( + features::kPartialLowEndModeOnMidRangeDevices); +} + +} // namespace + +#endif // BUILDFLAG(IS_ANDROID) + +// TODO(crbug.com/1434873): This method is for chromium native code. +// We need to update the java-side code, i.e. +// base/android/java/src/org/chromium/base/SysUtils.java, +// and to make the selected components in java to see this feature. +bool SysInfo::IsLowEndDeviceOrPartialLowEndModeEnabled() { +#if BUILDFLAG(IS_ANDROID) + return base::SysInfo::IsLowEndDevice() || + IsPartialLowEndModeOnMidRangeDevicesEnabled(); +#else + return base::SysInfo::IsLowEndDevice(); +#endif +} + #if !BUILDFLAG(IS_ANDROID) // The Android equivalent of this lives in `detectLowEndDevice()` at: // base/android/java/src/org/chromium/base/SysUtils.java diff --git a/base/system/sys_info.h b/base/system/sys_info.h index 94389b0ff..02f3006ee 100644 --- a/base/system/sys_info.h +++ b/base/system/sys_info.h @@ -238,6 +238,14 @@ class BASE_EXPORT SysInfo { // On Desktop this returns true when memory <= 2GB. static bool IsLowEndDevice(); + // The same as IsLowEndDevice() except on Android. + // + // On Android this returns: + // true when IsLowEndDevice() returns true. + // true when the physical memory of the device is 4gb or 6gb and + // the feature: kPartialLowEndModeOnMidEndDevices() is enabled. + static bool IsLowEndDeviceOrPartialLowEndModeEnabled(); + #if BUILDFLAG(IS_MAC) // Sets whether CPU security mitigations are enabled for the current process. // This is used to control the behavior of NumberOfProcessors(), see comment diff --git a/base/task/cancelable_task_tracker.cc b/base/task/cancelable_task_tracker.cc index 0acac4179..79e0d99e2 100644 --- a/base/task/cancelable_task_tracker.cc +++ b/base/task/cancelable_task_tracker.cc @@ -8,13 +8,11 @@ #include <utility> -#include "base/feature_list.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/location.h" #include "base/memory/ref_counted.h" #include "base/metrics/histogram_macros.h" -#include "base/task/scoped_set_task_priority_for_current_thread.h" #include "base/task/sequenced_task_runner.h" #include "base/task/task_runner.h" @@ -30,52 +28,6 @@ void RunOrPostToTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner, task_runner->PostTask(FROM_HERE, std::move(closure)); } -// TODO(https://crbug.com/1009795): Remove these once we have established -// whether off-sequence cancelation is worthwhile. - -// These values are persisted to logs. Entries should not be renumbered and -// numeric values should never be reused. -enum class TaskStatus { - kSameSequenceLive = 0, - kOffSequenceLive = 1, - kSameSequenceCanceled = 2, - kOffSequenceCanceled = 3, - kMaxValue = kOffSequenceCanceled, -}; - -void UmaRecordTaskDuration(bool same_sequence, - bool background, - bool canceled, - TimeDelta duration) { -#define DECLARE_HISTOGRAM(suffix) \ - Histogram::FactoryTimeGet( \ - "Scheduler.CancelableTaskTracker.TaskDuration2_" suffix, \ - Milliseconds(1), Seconds(10), 50, Histogram::kUmaTargetedHistogramFlag) - - static HistogramBase* histograms[] = { - DECLARE_HISTOGRAM("LiveForegroundOffSequence"), - DECLARE_HISTOGRAM("LiveForegroundSameSequence"), - DECLARE_HISTOGRAM("LiveBackgroundOffSequence"), - DECLARE_HISTOGRAM("LiveBackgroundSameSequence"), - DECLARE_HISTOGRAM("CanceledForegroundOffSequence"), - DECLARE_HISTOGRAM("CanceledForegroundSameSequence"), - DECLARE_HISTOGRAM("CanceledBackgroundOffSequence"), - DECLARE_HISTOGRAM("CanceledBackgroundSameSequence")}; - - int i = (same_sequence ? 1 : 0) + (background ? 2 : 0) + (canceled ? 4 : 0); - histograms[i]->AddTimeMillisecondsGranularity(duration); -} - -BASE_FEATURE(kAllowOffSequenceTaskCancelation, - "AllowOffSequenceTaskCancelation", - base::FEATURE_ENABLED_BY_DEFAULT); - -bool AllowOffSequenceTaskCancelation() { - if (!base::FeatureList::GetInstance()) - return true; - return base::FeatureList::IsEnabled(kAllowOffSequenceTaskCancelation); -} - } // namespace // static @@ -122,11 +74,8 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply( OnceClosure untrack_closure = BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id); bool success = task_runner->PostTaskAndReply( - from_here, - BindOnce(&RunIfNotCanceled, SequencedTaskRunner::GetCurrentDefault(), - flag, std::move(task)), - BindOnce(&RunThenUntrackIfNotCanceled, - SequencedTaskRunner::GetCurrentDefault(), flag, std::move(reply), + from_here, BindOnce(&RunIfNotCanceled, flag, std::move(task)), + BindOnce(&RunThenUntrackIfNotCanceled, flag, std::move(reply), std::move(untrack_closure))); if (!success) @@ -152,14 +101,11 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId( BindOnce(&CancelableTaskTracker::Untrack, Unretained(this), id); // Will always run |untrack_closure| on current sequence. - ScopedClosureRunner untrack_runner(BindOnce( - &RunOrPostToTaskRunner, SequencedTaskRunner::GetCurrentDefault(), - BindOnce(&RunIfNotCanceled, SequencedTaskRunner::GetCurrentDefault(), - flag, std::move(untrack_closure)))); + ScopedClosureRunner untrack_runner( + BindOnce(&RunOrPostToTaskRunner, SequencedTaskRunner::GetCurrentDefault(), + BindOnce(&RunIfNotCanceled, flag, std::move(untrack_closure)))); - *is_canceled_cb = - BindRepeating(&IsCanceled, SequencedTaskRunner::GetCurrentDefault(), flag, - std::move(untrack_runner)); + *is_canceled_cb = BindRepeating(&IsCanceled, flag, std::move(untrack_runner)); Track(id, std::move(flag)); return id; @@ -201,60 +147,27 @@ bool CancelableTaskTracker::HasTrackedTasks() const { // static void CancelableTaskTracker::RunIfNotCanceled( - const scoped_refptr<SequencedTaskRunner>& origin_task_runner, const scoped_refptr<TaskCancellationFlag>& flag, OnceClosure task) { - // TODO(https://crbug.com/1009795): Record durations for executed tasks, - // correlated with whether the task runs on a background or foreground - // sequence, and whether it is the same sequence as the CancelableTaskTracker. - // Also correlate with whether the task was run despite being canceled, to - // allow an experiment to assess the value of off-sequence cancelation. - - // Record canceled & off-sequence status for all tasks. - const bool was_canceled = flag->data.IsSet(); - const bool same_sequence = origin_task_runner->RunsTasksInCurrentSequence(); - const TaskStatus task_status = - was_canceled ? (same_sequence ? TaskStatus::kSameSequenceCanceled - : TaskStatus::kOffSequenceCanceled) - : (same_sequence ? TaskStatus::kSameSequenceLive - : TaskStatus::kOffSequenceLive); - UMA_HISTOGRAM_ENUMERATION("Scheduler.CancelableTaskTracker.TaskStatus", - task_status); - - // Skip tasks if they are canceled, taking into account the off-sequence - // cancelation experiment. - const bool skip_task = - was_canceled && (AllowOffSequenceTaskCancelation() || same_sequence); - if (skip_task) - return; - - // Run the task and record its duration. - const TimeTicks before_task_ticks = TimeTicks::Now(); - std::move(task).Run(); - const TimeDelta duration = TimeTicks::Now() - before_task_ticks; - const bool is_background = - internal::GetTaskPriorityForCurrentThread() < TaskPriority::USER_VISIBLE; - UmaRecordTaskDuration(same_sequence, is_background, was_canceled, duration); + if (!flag->data.IsSet()) { + std::move(task).Run(); + } } // static void CancelableTaskTracker::RunThenUntrackIfNotCanceled( - const scoped_refptr<SequencedTaskRunner>& origin_task_runner, const scoped_refptr<TaskCancellationFlag>& flag, OnceClosure task, OnceClosure untrack) { - RunIfNotCanceled(origin_task_runner, flag, std::move(task)); - RunIfNotCanceled(origin_task_runner, flag, std::move(untrack)); + RunIfNotCanceled(flag, std::move(task)); + RunIfNotCanceled(flag, std::move(untrack)); } // static bool CancelableTaskTracker::IsCanceled( - const scoped_refptr<SequencedTaskRunner>& origin_task_runner, const scoped_refptr<TaskCancellationFlag>& flag, const ScopedClosureRunner& cleanup_runner) { - return flag->data.IsSet() && - (AllowOffSequenceTaskCancelation() || - origin_task_runner->RunsTasksInCurrentSequence()); + return flag->data.IsSet(); } void CancelableTaskTracker::Track(TaskId id, diff --git a/base/task/cancelable_task_tracker.h b/base/task/cancelable_task_tracker.h index 250c27450..e6325d9a1 100644 --- a/base/task/cancelable_task_tracker.h +++ b/base/task/cancelable_task_tracker.h @@ -132,19 +132,14 @@ class BASE_EXPORT CancelableTaskTracker { // See https://crbug.com/918948. using TaskCancellationFlag = RefCountedData<AtomicFlag>; - static void RunIfNotCanceled( - const scoped_refptr<SequencedTaskRunner>& origin_task_runner, - const scoped_refptr<TaskCancellationFlag>& flag, - OnceClosure task); + static void RunIfNotCanceled(const scoped_refptr<TaskCancellationFlag>& flag, + OnceClosure task); static void RunThenUntrackIfNotCanceled( - const scoped_refptr<SequencedTaskRunner>& origin_task_runner, const scoped_refptr<TaskCancellationFlag>& flag, OnceClosure task, OnceClosure untrack); - static bool IsCanceled( - const scoped_refptr<SequencedTaskRunner>& origin_task_runner, - const scoped_refptr<TaskCancellationFlag>& flag, - const ScopedClosureRunner& cleanup_runner); + static bool IsCanceled(const scoped_refptr<TaskCancellationFlag>& flag, + const ScopedClosureRunner& cleanup_runner); void Track(TaskId id, scoped_refptr<TaskCancellationFlag> flag); void Untrack(TaskId id); diff --git a/base/task/common/task_annotator.cc b/base/task/common/task_annotator.cc index 96416c886..367eda578 100644 --- a/base/task/common/task_annotator.cc +++ b/base/task/common/task_annotator.cc @@ -16,6 +16,7 @@ #include "base/logging.h" #include "base/ranges/algorithm.h" #include "base/sys_byteorder.h" +#include "base/time/time.h" #include "base/trace_event/base_tracing.h" #include "base/tracing_buildflags.h" #include "build/build_config.h" @@ -168,6 +169,13 @@ void TaskAnnotator::RunTaskImpl(PendingTask& pending_task) { reinterpret_cast<void*>(pending_task.ipc_hash); debug::Alias(&task_backtrace); + // Record the task time in convenient units. This can be compared to times + // stored in places like ReportThreadHang() and BrowserMain() when analyzing + // hangs. + const int64_t task_time = + pending_task.GetDesiredExecutionTime().since_origin().InSeconds(); + base::debug::Alias(&task_time); + { const AutoReset<PendingTask*> resetter(¤t_pending_task, &pending_task); diff --git a/base/task/sequence_manager/thread_controller.h b/base/task/sequence_manager/thread_controller.h index 692056dc5..138bdcdda 100644 --- a/base/task/sequence_manager/thread_controller.h +++ b/base/task/sequence_manager/thread_controller.h @@ -362,7 +362,7 @@ class BASE_EXPORT ThreadController { absl::optional<perfetto::Track> perfetto_track_; // True if tracing was enabled during the last pass of RecordTimeInPhase. - bool was_tracing_enabled_; + bool was_tracing_enabled_ = false; #endif const raw_ref<const RunLevelTracker> outer_; } time_keeper_{*this}; diff --git a/base/template_util.h b/base/template_util.h index 415b3acf1..e192110d0 100644 --- a/base/template_util.h +++ b/base/template_util.h @@ -18,15 +18,6 @@ namespace base { namespace internal { -// Uses expression SFINAE to detect whether using operator<< would work. -template <typename T, typename = void> -struct SupportsOstreamOperator : std::false_type {}; -template <typename T> -struct SupportsOstreamOperator<T, - decltype(void(std::declval<std::ostream&>() - << std::declval<T>()))> - : std::true_type {}; - template <typename T, typename = void> struct SupportsToString : std::false_type {}; template <typename T> diff --git a/base/template_util_unittest.cc b/base/template_util_unittest.cc index 2774b5cac..efcea3d6d 100644 --- a/base/template_util_unittest.cc +++ b/base/template_util_unittest.cc @@ -4,6 +4,8 @@ #include "base/template_util.h" +#include <stdint.h> + #include <string> #include <type_traits> @@ -17,58 +19,11 @@ namespace { enum SimpleEnum { SIMPLE_ENUM }; enum EnumWithExplicitType : uint64_t { ENUM_WITH_EXPLICIT_TYPE }; enum class ScopedEnum { SCOPED_ENUM }; -enum class ScopedEnumWithOperator { SCOPED_ENUM_WITH_OPERATOR }; -std::ostream& operator<<(std::ostream& os, ScopedEnumWithOperator v) { - return os; -} struct SimpleStruct {}; -struct StructWithOperator {}; -std::ostream& operator<<(std::ostream& os, const StructWithOperator& v) { - return os; -} struct StructWithToString { std::string ToString() const { return ""; } }; -// A few standard types that definitely support printing. -static_assert(internal::SupportsOstreamOperator<int>::value, - "ints should be printable"); -static_assert(internal::SupportsOstreamOperator<const char*>::value, - "C strings should be printable"); -static_assert(internal::SupportsOstreamOperator<std::string>::value, - "std::string should be printable"); - -// Various kinds of enums operator<< support. -static_assert(internal::SupportsOstreamOperator<SimpleEnum>::value, - "simple enum should be printable by value"); -static_assert(internal::SupportsOstreamOperator<const SimpleEnum&>::value, - "simple enum should be printable by const ref"); -static_assert(internal::SupportsOstreamOperator<EnumWithExplicitType>::value, - "enum with explicit type should be printable by value"); -static_assert( - internal::SupportsOstreamOperator<const EnumWithExplicitType&>::value, - "enum with explicit type should be printable by const ref"); -static_assert(!internal::SupportsOstreamOperator<ScopedEnum>::value, - "scoped enum should not be printable by value"); -static_assert(!internal::SupportsOstreamOperator<const ScopedEnum&>::value, - "simple enum should not be printable by const ref"); -static_assert(internal::SupportsOstreamOperator<ScopedEnumWithOperator>::value, - "scoped enum with operator<< should be printable by value"); -static_assert( - internal::SupportsOstreamOperator<const ScopedEnumWithOperator&>::value, - "scoped enum with operator<< should be printable by const ref"); - -// operator<< support on structs. -static_assert(!internal::SupportsOstreamOperator<SimpleStruct>::value, - "simple struct should not be printable by value"); -static_assert(!internal::SupportsOstreamOperator<const SimpleStruct&>::value, - "simple struct should not be printable by const ref"); -static_assert(internal::SupportsOstreamOperator<StructWithOperator>::value, - "struct with operator<< should be printable by value"); -static_assert( - internal::SupportsOstreamOperator<const StructWithOperator&>::value, - "struct with operator<< should be printable by const ref"); - // .ToString() support on structs. static_assert(!internal::SupportsToString<SimpleStruct>::value, "simple struct value doesn't support .ToString()"); diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn index c4c6b2cd2..cc75b7235 100644 --- a/base/test/BUILD.gn +++ b/base/test/BUILD.gn @@ -13,6 +13,10 @@ if (is_android) { import("//build/config/android/rules.gni") } +if (is_ios) { + import("//build/config/ios/rules.gni") +} + static_library("test_config") { testonly = true sources = [ @@ -168,10 +172,16 @@ static_library("test_support") { if (enable_base_tracing) { public_deps += [ "//third_party/perfetto:perfetto_test_support" ] - if (!is_chromeos && !is_ios) { + if (!is_chromeos) { # TODO(rasikan): Add to ios and chromeos when unblocked by the chromiumos # change to add the shared lib to the chrome-binary-tests directory. public_deps += [ ":test_trace_processor" ] + if (is_ios) { + deps += [ + ":test_trace_processor+bundle", + ":test_trace_processor+link", + ] + } } } @@ -243,7 +253,10 @@ static_library("test_support") { "test_support_ios.h", "test_support_ios.mm", ] - deps += [ ":google_test_runner_shared_headers" ] + deps += [ + ":google_test_runner_shared_headers", + "//build:blink_buildflags", + ] # With blink, we use the standard unit_test_launcher.cc. if (!use_blink) { @@ -477,7 +490,12 @@ if (enable_base_tracing) { # processor depends on dev_sqlite. The two share the same symbols but have # different implementations, so we need to hide dev_sqlite in this shared # library even in non-component builds to prevent duplicate symbols. - shared_library("test_trace_processor") { + _target_type = "shared_library" + if (is_ios) { + _target_type = "ios_framework_bundle" + } + + target(_target_type, "test_trace_processor") { defines = [ "TEST_TRACE_PROCESSOR_IMPL" ] testonly = true sources = [ @@ -493,6 +511,12 @@ if (enable_base_tracing) { configs -= [ "//build/config/android:hide_all_but_jni_onload" ] } + if (is_ios) { + info_plist = "test_trace_processor.plist" + output_name = "TestTraceProcessor" + bundle_deps_filter = [ "//third_party/icu:icudata" ] + } + # Set rpath on dependent tests so that they can find the shared library # in a non-component build. if (!is_component_build) { diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java index 7f4d192ef..208b7b663 100644 --- a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java +++ b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java @@ -237,7 +237,7 @@ public class BaseJUnit4ClassRunner extends AndroidJUnit4ClassRunner { @CallSuper protected List<TestRule> getDefaultTestRules() { return Arrays.asList(new BaseJUnit4TestRule(), new MockitoErrorHandler(), - new UnitTestLifetimeAssertRule()); + new UnitTestLifetimeAssertRule(), new ResettersForTestingTestRule()); } /** diff --git a/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java b/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java new file mode 100644 index 000000000..779730603 --- /dev/null +++ b/base/test/android/javatests/src/org/chromium/base/test/ResettersForTestingTestRule.java @@ -0,0 +1,32 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.test; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import org.chromium.base.ResettersForTesting; + +/** + * Ensures that all resetters are cleaned up after a test. The resetters are registered through + * {@link ResettersForTesting#register(Runnable)} and are typically used whenever we have code that + * has <code>public static void setFooForTesting(...)</code> constructs. + */ +class ResettersForTestingTestRule implements TestRule { + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } finally { + ResettersForTesting.executeResetters(); + } + } + }; + } +} diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java index 93c600725..9fccb2c84 100644 --- a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java +++ b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java @@ -170,16 +170,30 @@ public final class CommandLineFlags { } private static void restoreFlags(Set<String> flagsToRemove, Map<String, String> flagsToAdd) { - for (Entry<String, String> flag : flagsToAdd.entrySet()) { - CommandLine.getInstance().appendSwitchWithValue(flag.getKey(), flag.getValue()); - } for (String flag : flagsToRemove) { CommandLine.getInstance().removeSwitch(flag); } + for (Entry<String, String> flag : flagsToAdd.entrySet()) { + if (flag.getValue() == null) { + CommandLine.getInstance().appendSwitch(flag.getKey()); + } else { + CommandLine.getInstance().appendSwitchWithValue(flag.getKey(), flag.getValue()); + } + } } private static void applyFlags(Set<String> flagsToAdd, Set<String> flagsToRemove, Set<String> flagsToRemoveForRestore, Map<String, String> flagsToAddForRestore) { + if (flagsToRemove != null) { + for (String flag : flagsToRemove) { + if (CommandLine.getInstance().hasSwitch(flag)) { + String existingValue = CommandLine.getInstance().getSwitchValue(flag); + CommandLine.getInstance().removeSwitch(flag); + flagsToAddForRestore.put(flag, existingValue); + } + } + } + Set<String> enableFeatures = new HashSet<String>(getFeatureValues(ENABLE_FEATURES)); Set<String> disableFeatures = new HashSet<String>(getFeatureValues(DISABLE_FEATURES)); for (String flag : flagsToAdd) { @@ -210,25 +224,24 @@ public final class CommandLineFlags { if (enableFeatures.size() > 0) { String existingValue = CommandLine.getInstance().getSwitchValue(ENABLE_FEATURES); - flagsToAddForRestore.put(ENABLE_FEATURES, existingValue); + if (existingValue != null) { + flagsToAddForRestore.put(ENABLE_FEATURES, existingValue); + CommandLine.getInstance().removeSwitch(ENABLE_FEATURES); + } CommandLine.getInstance().appendSwitchWithValue( ENABLE_FEATURES, TextUtils.join(",", enableFeatures)); flagsToRemoveForRestore.add(ENABLE_FEATURES); } if (disableFeatures.size() > 0) { String existingValue = CommandLine.getInstance().getSwitchValue(DISABLE_FEATURES); - flagsToAddForRestore.put(DISABLE_FEATURES, existingValue); + if (existingValue != null) { + flagsToAddForRestore.put(DISABLE_FEATURES, existingValue); + CommandLine.getInstance().removeSwitch(DISABLE_FEATURES); + } CommandLine.getInstance().appendSwitchWithValue( DISABLE_FEATURES, TextUtils.join(",", disableFeatures)); flagsToRemoveForRestore.add(DISABLE_FEATURES); } - if (flagsToRemove == null) return; - for (String flag : flagsToRemove) { - if (CommandLine.getInstance().hasSwitch(flag)) { - CommandLine.getInstance().removeSwitch(flag); - flagsToAddForRestore.put(flag, null); - } - } } private static void updateFlagsForClass(Class<?> clazz, Set<String> flags) { diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java index e17119188..1039f736e 100644 --- a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java +++ b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java @@ -5,21 +5,35 @@ package org.chromium.base.test.util; import android.os.Debug; -import android.os.SystemClock; /** * Encapsulates timeout logic, and disables timeouts when debugger is attached. */ public class TimeoutTimer { - private final long mEndTimeMs; + private static final boolean IS_REAL_ANDROID = + System.getProperty("java.class.path").endsWith(".apk"); + private static final long MS_TO_NANO = 1000000; + private final long mEndTimeNano; private final long mTimeoutMs; + static { + if (!IS_REAL_ANDROID) { + try { + // BaseRobolectricTestRunner marks this class as "DoNotAcquire" so that + // System.nanoTime() will not return fake time. + Class.forName("android.os.Debug"); + assert false : "Cannot use TimeoutTimer without using BaseRobolectricTestRunner"; + } catch (Throwable e) { + } + } + } + /** * @param timeoutMs Relative time for the timeout (unscaled). */ public TimeoutTimer(long timeoutMs) { mTimeoutMs = ScalableTimeout.scaleTimeout(timeoutMs); - mEndTimeMs = SystemClock.uptimeMillis() + mTimeoutMs; + mEndTimeNano = System.nanoTime() + mTimeoutMs * MS_TO_NANO; } /** Whether this timer has expired. */ @@ -27,14 +41,24 @@ public class TimeoutTimer { return getRemainingMs() == 0; } + private static boolean shouldPauseTimeouts() { + if (IS_REAL_ANDROID) { + return Debug.isDebuggerConnected(); + } + // Our test runner sets this when --wait-for-java-debugger is passed. + // This will cause tests to never time out since the value is not updated when debugger + // detaches (oh well). + return "true".equals(System.getProperty("chromium.jdwp_active")); + } + /** Returns how much time is left in milliseconds. */ public long getRemainingMs() { - if (Debug.isDebuggerConnected()) { + if (shouldPauseTimeouts()) { // Never decreases, but still short enough that it's safe to wait() on and have a // timeout happen once the debugger detaches. return mTimeoutMs; } - long ret = mEndTimeMs - SystemClock.uptimeMillis(); - return ret < 0 ? 0 : ret; + long ret = mEndTimeNano - System.nanoTime(); + return ret < 0 ? 0 : ret / MS_TO_NANO; } } diff --git a/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java index 2d63c583b..6b4eef24f 100644 --- a/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java +++ b/base/test/android/junit/src/org/chromium/base/test/BaseRobolectricTestRunner.java @@ -9,7 +9,9 @@ import androidx.test.core.app.ApplicationProvider; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.robolectric.DefaultTestLifecycle; +import org.robolectric.RobolectricTestRunner; import org.robolectric.TestLifecycle; +import org.robolectric.internal.bytecode.InstrumentationConfiguration; import org.chromium.base.ApplicationStatus; import org.chromium.base.BundleUtils; @@ -17,6 +19,7 @@ import org.chromium.base.ContextUtils; import org.chromium.base.Flag; import org.chromium.base.LifetimeAssert; import org.chromium.base.PathUtils; +import org.chromium.base.ResettersForTesting; import org.chromium.base.ThreadUtils; import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.library_loader.LibraryProcessType; @@ -24,16 +27,16 @@ import org.chromium.base.metrics.UmaRecorderHolder; import org.chromium.base.task.PostTask; import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.DisabledTest; -import org.chromium.testing.local.LocalRobolectricTestRunner; +import org.chromium.base.test.util.TimeoutTimer; import java.lang.reflect.Method; /** * A Robolectric Test Runner that configures Chromium-specific settings and initializes base * globals. If initializing base globals is not desired, then {@link - * org.chromium.testing.local.LocalRobolectricTestRunner} could be used. + * org.robolectric.RobolectricTestRunner} could be used directly. */ -public class BaseRobolectricTestRunner extends LocalRobolectricTestRunner { +public class BaseRobolectricTestRunner extends RobolectricTestRunner { /** * Enables a per-test setUp / tearDown hook. */ @@ -66,6 +69,7 @@ public class BaseRobolectricTestRunner extends LocalRobolectricTestRunner { } finally { CommandLineFlags.tearDownMethod(); CommandLineFlags.tearDownClass(); + ResettersForTesting.executeResetters(); ApplicationStatus.destroyForJUnitTests(); ContextUtils.clearApplicationContextForTests(); PathUtils.resetForTesting(); @@ -94,4 +98,11 @@ public class BaseRobolectricTestRunner extends LocalRobolectricTestRunner { Class<?> testSuiteClass = method.getDeclaringClass(); return testSuiteClass.getAnnotation(DisabledTest.class) != null; } + + @Override + protected InstrumentationConfiguration createClassLoaderConfig(final FrameworkMethod method) { + return new InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method)) + .doNotAcquireClass(TimeoutTimer.class) // Requires access to non-fake SystemClock. + .build(); + } } diff --git a/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java b/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java index c8550ae14..d591ec8f2 100644 --- a/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java +++ b/base/test/android/junit/src/org/chromium/base/test/util/CommandLineFlagsNoClassAnnotationCheckTest.java @@ -33,22 +33,20 @@ public class CommandLineFlagsNoClassAnnotationCheckTest { } @Test - @CommandLineFlags.Add("some-switch") + @CommandLineFlags.Add("some-switch=method_value") @CommandLineFlags.Remove("some-switch") public void testAddThenRemoveSwitch_method() throws Throwable { - Assert.assertTrue( - "CommandLine switches should be empty after adding and removing the same switch", - CommandLine.getInstance().getSwitches().isEmpty()); + Assert.assertEquals( + "some-switch should be removed from the class level and added back, not ignored", + "method_value", CommandLine.getInstance().getSwitchValue("some-switch")); } @Test @CommandLineFlags.Remove("some-switch") - @CommandLineFlags.Add("some-switch") + @CommandLineFlags.Add("some-switch=method_value") public void testRemoveThenAddSwitch_method() throws Throwable { - // ".Add" rules apply before ".Remove" rules when annotating the same method/class, - // regardless of the order the annotations are written. - Assert.assertTrue( - "CommandLine switches should be empty after removing and adding the same switch", - CommandLine.getInstance().getSwitches().isEmpty()); + Assert.assertEquals( + "some-switch should be removed from the class level and added back, not ignored", + "method_value", CommandLine.getInstance().getSwitchValue("some-switch")); } } diff --git a/base/test/test_support_ios.mm b/base/test/test_support_ios.mm index df7e8a9eb..f298c4e31 100644 --- a/base/test/test_support_ios.mm +++ b/base/test/test_support_ios.mm @@ -15,6 +15,7 @@ #import "base/test/ios/google_test_runner_delegate.h" #include "base/test/test_suite.h" #include "base/test/test_switches.h" +#include "build/blink_buildflags.h" #include "testing/coverage_util_ios.h" // Springboard will kill any iOS app that fails to check in after launch within @@ -226,9 +227,13 @@ bool IsSceneStartupEnabled() { int exitStatus = [self runGoogleTests]; + // The blink code path uses a spawning test launcher and this wait isn't + // really necessary for that code path. +#if !BUILDFLAG(USE_BLINK) // If a test app is too fast, it will exit before Instruments has has a // a chance to initialize and no test results will be seen. [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; +#endif _window.reset(); // Use the hidden selector to try and cleanly take down the app (otherwise diff --git a/base/test/test_trace_processor.plist b/base/test/test_trace_processor.plist new file mode 100644 index 000000000..449c5cc40 --- /dev/null +++ b/base/test/test_trace_processor.plist @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleIdentifier</key> + <string>${BUNDLE_IDENTIFIER}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> +</dict> +</plist> diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto index 8d2218ae9..b7e866466 100644 --- a/base/tracing/protos/chrome_track_event.proto +++ b/base/tracing/protos/chrome_track_event.proto @@ -1093,10 +1093,28 @@ message BlinkHighEntropyAPI { // similar to "Navigator.languages.get". optional string identifier = 1; repeated JSFunctionArgument func_arguments = 2; + + // Deprecated in favour of outer source_location. optional BlinkSourceLocation source_location = 3; } optional BlinkExecutionContext execution_context = 1; optional CalledJsApi called_api = 2; + optional BlinkSourceLocation source_location = 3; + + // Describes lookup of a font. + message FontLookup { + enum FontLookupType { + FONT_LOOKUP_UNKNOWN_TYPE = 0; + FONT_LOOKUP_UNIQUE_OR_FAMILY_NAME = 1; + FONT_LOOKUP_UNIQUE_NAME_ONLY = 2; + } + optional FontLookupType type = 1; + optional string name = 2; + optional uint64 weight = 3; + optional uint64 width = 4; + optional uint64 slope = 5; + } + optional FontLookup font_lookup = 4; } // Contains information about a tab switch measurement. diff --git a/base/types/strong_alias.h b/base/types/strong_alias.h index 8a757be57..7a4872a4a 100644 --- a/base/types/strong_alias.h +++ b/base/types/strong_alias.h @@ -9,8 +9,8 @@ #include <type_traits> #include <utility> -#include "base/template_util.h" #include "base/trace_event/base_tracing_forward.h" +#include "base/types/supports_ostream_operator.h" namespace base { @@ -154,7 +154,7 @@ class StrongAlias { template <typename TagType, typename UnderlyingType, typename = std::enable_if_t< - base::internal::SupportsOstreamOperator<UnderlyingType>::value>> + internal::SupportsOstreamOperator<UnderlyingType>::value>> std::ostream& operator<<(std::ostream& stream, const StrongAlias<TagType, UnderlyingType>& alias) { return stream << alias.value(); diff --git a/base/types/strong_alias_unittest.cc b/base/types/strong_alias_unittest.cc index 6cc211adc..7ab1444d1 100644 --- a/base/types/strong_alias_unittest.cc +++ b/base/types/strong_alias_unittest.cc @@ -14,7 +14,7 @@ #include <utility> #include "base/strings/string_piece.h" -#include "base/template_util.h" +#include "base/types/supports_ostream_operator.h" #include "testing/gtest/include/gtest/gtest.h" #if BUILDFLAG(ENABLE_BASE_TRACING) @@ -371,8 +371,7 @@ TEST(StrongAliasTest, EnsureConstexpr) { void StreamOperatorExists() { // Aliases of ints should be streamable because ints are streamable. using StreamableAlias = StrongAlias<class IntTag, int>; - static_assert(base::internal::SupportsOstreamOperator<StreamableAlias>::value, - ""); + static_assert(internal::SupportsOstreamOperator<StreamableAlias>::value); // Aliases of a class which does not expose a stream operator should // themselves not be streamable. @@ -381,8 +380,7 @@ void StreamOperatorExists() { Scope() = default; }; using NonStreamableAlias = StrongAlias<class ScopeTag, Scope>; - static_assert( - !base::internal::SupportsOstreamOperator<NonStreamableAlias>::value, ""); + static_assert(!internal::SupportsOstreamOperator<NonStreamableAlias>::value); } #if BUILDFLAG(ENABLE_BASE_TRACING) diff --git a/base/types/supports_ostream_operator.h b/base/types/supports_ostream_operator.h new file mode 100644 index 000000000..0803db486 --- /dev/null +++ b/base/types/supports_ostream_operator.h @@ -0,0 +1,28 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_ +#define BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_ + +#include <ostream> +#include <type_traits> +#include <utility> + +namespace base::internal { + +// Uses expression SFINAE to detect whether using operator<< would work. +// +// Note that the above #include of <ostream> is necessary to guarantee +// consistent results here for basic types. +template <typename T, typename = void> +struct SupportsOstreamOperator : std::false_type {}; +template <typename T> +struct SupportsOstreamOperator<T, + decltype(void(std::declval<std::ostream&>() + << std::declval<T>()))> + : std::true_type {}; + +} // namespace base::internal + +#endif // BASE_TYPES_SUPPORTS_OSTREAM_OPERATOR_H_ diff --git a/base/types/supports_ostream_operator_test.cc b/base/types/supports_ostream_operator_test.cc new file mode 100644 index 000000000..0f8c3a09d --- /dev/null +++ b/base/types/supports_ostream_operator_test.cc @@ -0,0 +1,68 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/types/supports_ostream_operator.h" + +#include <stdint.h> + +#include <ostream> +#include <string> + +namespace base { +namespace { + +enum SimpleEnum { SIMPLE_ENUM }; +enum EnumWithExplicitType : uint64_t { ENUM_WITH_EXPLICIT_TYPE }; +enum class ScopedEnum { SCOPED_ENUM }; +enum class ScopedEnumWithOperator { SCOPED_ENUM_WITH_OPERATOR }; +std::ostream& operator<<(std::ostream& os, ScopedEnumWithOperator v) { + return os; +} +struct SimpleStruct {}; +struct StructWithOperator {}; +std::ostream& operator<<(std::ostream& os, const StructWithOperator& v) { + return os; +} + +// A few standard types that definitely support printing. +static_assert(internal::SupportsOstreamOperator<int>::value, + "ints should be printable"); +static_assert(internal::SupportsOstreamOperator<const char*>::value, + "C strings should be printable"); +static_assert(internal::SupportsOstreamOperator<std::string>::value, + "std::string should be printable"); + +// Various kinds of enums operator<< support. +static_assert(internal::SupportsOstreamOperator<SimpleEnum>::value, + "simple enum should be printable by value"); +static_assert(internal::SupportsOstreamOperator<const SimpleEnum&>::value, + "simple enum should be printable by const ref"); +static_assert(internal::SupportsOstreamOperator<EnumWithExplicitType>::value, + "enum with explicit type should be printable by value"); +static_assert( + internal::SupportsOstreamOperator<const EnumWithExplicitType&>::value, + "enum with explicit type should be printable by const ref"); +static_assert(!internal::SupportsOstreamOperator<ScopedEnum>::value, + "scoped enum should not be printable by value"); +static_assert(!internal::SupportsOstreamOperator<const ScopedEnum&>::value, + "simple enum should not be printable by const ref"); +static_assert(internal::SupportsOstreamOperator<ScopedEnumWithOperator>::value, + "scoped enum with operator<< should be printable by value"); +static_assert( + internal::SupportsOstreamOperator<const ScopedEnumWithOperator&>::value, + "scoped enum with operator<< should be printable by const ref"); + +// operator<< support on structs. +static_assert(!internal::SupportsOstreamOperator<SimpleStruct>::value, + "simple struct should not be printable by value"); +static_assert(!internal::SupportsOstreamOperator<const SimpleStruct&>::value, + "simple struct should not be printable by const ref"); +static_assert(internal::SupportsOstreamOperator<StructWithOperator>::value, + "struct with operator<< should be printable by value"); +static_assert( + internal::SupportsOstreamOperator<const StructWithOperator&>::value, + "struct with operator<< should be printable by const ref"); + +} // namespace +} // namespace base diff --git a/base/values.cc b/base/values.cc index 2e51b987d..ac4cc65f3 100644 --- a/base/values.cc +++ b/base/values.cc @@ -1169,18 +1169,6 @@ std::string* Value::FindStringKey(StringPiece key) { return GetDict().FindString(key); } -const Value* Value::FindDictKey(StringPiece key) const { - const Value* result = GetDict().Find(key); - if (!result || result->type() != Type::DICT) { - return nullptr; - } - return result; -} - -Value* Value::FindDictKey(StringPiece key) { - return const_cast<Value*>(std::as_const(*this).FindDictKey(key)); -} - const Value* Value::FindListKey(StringPiece key) const { const Value* result = GetDict().Find(key); if (!result || result->type() != Type::LIST) { diff --git a/base/values.h b/base/values.h index 45dfea06a..d9e9e2df6 100644 --- a/base/values.h +++ b/base/values.h @@ -769,9 +769,6 @@ class BASE_EXPORT GSL_OWNER Value { // DEPRECATED: prefer `Value::Dict::FindString()`. const std::string* FindStringKey(StringPiece key) const; std::string* FindStringKey(StringPiece key); - // DEPRECATED: prefer `Value::Dict::FindDict()`. - const Value* FindDictKey(StringPiece key) const; - Value* FindDictKey(StringPiece key); // DEPRECATED: prefer `Value::Dict::FindList()`. const Value* FindListKey(StringPiece key) const; Value* FindListKey(StringPiece key); diff --git a/base/win/com_init_util.cc b/base/win/com_init_util.cc index 0816fee42..b7b973c95 100644 --- a/base/win/com_init_util.cc +++ b/base/win/com_init_util.cc @@ -8,6 +8,7 @@ #include <winternl.h> #include "base/logging.h" +#include "base/memory/raw_ptr_exclusion.h" #include "base/notreached.h" namespace base { @@ -27,8 +28,12 @@ struct OleTlsData { MTA = 0x140, }; - void* thread_base; - void* sm_allocator; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #reinterpret-cast-trivial-type + RAW_PTR_EXCLUSION void* thread_base; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #reinterpret-cast-trivial-type + RAW_PTR_EXCLUSION void* sm_allocator; DWORD apartment_id; DWORD apartment_flags; // There are many more fields than this, but for our purposes, we only care diff --git a/base/win/iat_patch_function.cc b/base/win/iat_patch_function.cc index 914cdd5b5..f9de69804 100644 --- a/base/win/iat_patch_function.cc +++ b/base/win/iat_patch_function.cc @@ -5,6 +5,7 @@ #include "base/win/iat_patch_function.h" #include "base/check_op.h" +#include "base/memory/raw_ptr_exclusion.h" #include "base/notreached.h" #include "base/win/patch_util.h" #include "base/win/pe_image.h" @@ -18,9 +19,15 @@ struct InterceptFunctionInformation { bool finished_operation; const char* imported_from_module; const char* function_name; - void* new_function; - void** old_function; - IMAGE_THUNK_DATA** iat_thunk; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #reinterpret-cast-trivial-type + RAW_PTR_EXCLUSION void* new_function; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #reinterpret-cast-trivial-type + RAW_PTR_EXCLUSION void** old_function; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #reinterpret-cast-trivial-type + RAW_PTR_EXCLUSION IMAGE_THUNK_DATA** iat_thunk; DWORD return_code; }; @@ -36,7 +43,9 @@ void* GetIATFunction(IMAGE_THUNK_DATA* iat_thunk) { // or IMAGE_THUNK_DATA64 for correct pointer size. union FunctionThunk { IMAGE_THUNK_DATA thunk; - void* pointer; + // This field is not a raw_ptr<> because it was filtered by the rewriter + // for: #union + RAW_PTR_EXCLUSION void* pointer; } iat_function; iat_function.thunk = *iat_thunk; diff --git a/base/win/iat_patch_function.h b/base/win/iat_patch_function.h index 7b49baea7..3f3502d55 100644 --- a/base/win/iat_patch_function.h +++ b/base/win/iat_patch_function.h @@ -9,6 +9,7 @@ #include "base/base_export.h" #include "base/memory/raw_ptr.h" +#include "base/memory/raw_ptr_exclusion.h" namespace base { namespace win { @@ -70,8 +71,12 @@ class BASE_EXPORT IATPatchFunction { private: HMODULE module_handle_ = nullptr; raw_ptr<void> intercept_function_ = nullptr; - void* original_function_ = nullptr; - IMAGE_THUNK_DATA* iat_thunk_ = nullptr; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #addr-of + RAW_PTR_EXCLUSION void* original_function_ = nullptr; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #addr-of + RAW_PTR_EXCLUSION IMAGE_THUNK_DATA* iat_thunk_ = nullptr; }; } // namespace win diff --git a/base/win/registry.cc b/base/win/registry.cc index 1ea20faeb..473f5fdc8 100644 --- a/base/win/registry.cc +++ b/base/win/registry.cc @@ -14,12 +14,16 @@ #include <vector> #include "base/check_op.h" +#include "base/containers/fixed_flat_map.h" #include "base/functional/callback.h" +#include "base/native_library.h" #include "base/notreached.h" +#include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/string_util_win.h" #include "base/threading/thread_restrictions.h" #include "base/win/object_watcher.h" +#include "base/win/pe_image.h" #include "base/win/scoped_handle.h" #include "base/win/shlwapi.h" @@ -46,8 +50,326 @@ constexpr DWORD kInvalidIterValue = static_cast<DWORD>(-1); } // namespace +namespace internal { + +// A forwarder to the normal delayloaded Windows Registry API. +class Standard { + public: + static inline LSTATUS CreateKey(HKEY hKey, + LPCWSTR lpSubKey, + DWORD Reserved, + LPWSTR lpClass, + DWORD dwOptions, + REGSAM samDesired, + CONST LPSECURITY_ATTRIBUTES + lpSecurityAttributes, + PHKEY phkResult, + LPDWORD lpdwDisposition) { + return ::RegCreateKeyExW(hKey, lpSubKey, Reserved, lpClass, dwOptions, + samDesired, lpSecurityAttributes, phkResult, + lpdwDisposition); + } + + static inline LSTATUS OpenKey(HKEY hKey, + LPCWSTR lpSubKey, + DWORD ulOptions, + REGSAM samDesired, + PHKEY phkResult) { + return ::RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, phkResult); + } + + static inline LSTATUS DeleteKey(HKEY hKey, + LPCWSTR lpSubKey, + REGSAM samDesired, + DWORD Reserved) { + return ::RegDeleteKeyExW(hKey, lpSubKey, samDesired, Reserved); + } + + static inline LSTATUS QueryInfoKey(HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime) { + return ::RegQueryInfoKeyW(hKey, lpClass, lpcchClass, lpReserved, lpcSubKeys, + lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, + lpcbMaxValueNameLen, lpcbMaxValueLen, + lpcbSecurityDescriptor, lpftLastWriteTime); + } + + static inline LSTATUS EnumKey(HKEY hKey, + DWORD dwIndex, + LPWSTR lpName, + LPDWORD lpcchName, + LPDWORD lpReserved, + LPWSTR lpClass, + LPDWORD lpcchClass, + PFILETIME lpftLastWriteTime) { + return ::RegEnumKeyExW(hKey, dwIndex, lpName, lpcchName, lpReserved, + lpClass, lpcchClass, lpftLastWriteTime); + } + + static inline LSTATUS CloseKey(HKEY hKey) { return ::RegCloseKey(hKey); } + + static inline LSTATUS QueryValue(HKEY hKey, + LPCWSTR lpValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) { + return ::RegQueryValueExW(hKey, lpValueName, lpReserved, lpType, lpData, + lpcbData); + } + + static inline LSTATUS SetValue(HKEY hKey, + LPCWSTR lpValueName, + DWORD Reserved, + DWORD dwType, + CONST BYTE* lpData, + DWORD cbData) { + return ::RegSetValueExW(hKey, lpValueName, Reserved, dwType, lpData, + cbData); + } + + static inline LSTATUS DeleteValue(HKEY hKey, LPCWSTR lpValueName) { + return ::RegDeleteValueW(hKey, lpValueName); + } + + static inline LSTATUS EnumValue(HKEY hKey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) { + return ::RegEnumValueW(hKey, dwIndex, lpValueName, lpcchValueName, + lpReserved, lpType, lpData, lpcbData); + } +}; + +// An implementation derived from the export table of advapi32. +class ExportDerived { + public: + static LSTATUS CreateKey(HKEY hKey, + LPCWSTR lpSubKey, + DWORD Reserved, + LPWSTR lpClass, + DWORD dwOptions, + REGSAM samDesired, + CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes, + PHKEY phkResult, + LPDWORD lpdwDisposition) { + if (!ResolveRegistryFunctions() || !reg_create_key_ex_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_create_key_ex_(hKey, lpSubKey, Reserved, lpClass, dwOptions, + samDesired, lpSecurityAttributes, phkResult, + lpdwDisposition); + } + + static LSTATUS OpenKey(HKEY hKey, + LPCWSTR lpSubKey, + DWORD ulOptions, + REGSAM samDesired, + PHKEY phkResult) { + if (!ResolveRegistryFunctions() || !reg_open_key_ex_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_open_key_ex_(hKey, lpSubKey, ulOptions, samDesired, phkResult); + } + + static LSTATUS DeleteKey(HKEY hKey, + LPCWSTR lpSubKey, + REGSAM samDesired, + DWORD Reserved) { + if (!ResolveRegistryFunctions() || !reg_delete_key_ex_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_delete_key_ex_(hKey, lpSubKey, samDesired, Reserved); + } + + static LSTATUS QueryInfoKey(HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime) { + if (!ResolveRegistryFunctions() || !reg_query_info_key_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_query_info_key_(hKey, lpClass, lpcchClass, lpReserved, + lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, + lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, + lpcbSecurityDescriptor, lpftLastWriteTime); + } + + static LSTATUS EnumKey(HKEY hKey, + DWORD dwIndex, + LPWSTR lpName, + LPDWORD lpcchName, + LPDWORD lpReserved, + LPWSTR lpClass, + LPDWORD lpcchClass, + PFILETIME lpftLastWriteTime) { + if (!ResolveRegistryFunctions() || !reg_enum_key_ex_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_enum_key_ex_(hKey, dwIndex, lpName, lpcchName, lpReserved, + lpClass, lpcchClass, lpftLastWriteTime); + } + + static LSTATUS CloseKey(HKEY hKey) { + if (!ResolveRegistryFunctions() || !reg_close_key_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_close_key_(hKey); + } + + static LSTATUS QueryValue(HKEY hKey, + LPCWSTR lpValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) { + if (!ResolveRegistryFunctions() || !reg_query_value_ex_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_query_value_ex_(hKey, lpValueName, lpReserved, lpType, lpData, + lpcbData); + } + + static LSTATUS SetValue(HKEY hKey, + LPCWSTR lpValueName, + DWORD Reserved, + DWORD dwType, + CONST BYTE* lpData, + DWORD cbData) { + if (!ResolveRegistryFunctions() || !reg_set_value_ex_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_set_value_ex_(hKey, lpValueName, Reserved, dwType, lpData, + cbData); + } + + static LSTATUS DeleteValue(HKEY hKey, LPCWSTR lpValueName) { + if (!ResolveRegistryFunctions() || !reg_delete_value_) { + return ERROR_ERRORS_ENCOUNTERED; + } + return reg_delete_value_(hKey, lpValueName); + } + static LSTATUS EnumValue(HKEY hKey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) { + if (!ResolveRegistryFunctions() || !reg_enum_value_) { + return ERROR_ERRORS_ENCOUNTERED; + } + + return reg_enum_value_(hKey, dwIndex, lpValueName, lpcchValueName, + lpReserved, lpType, lpData, lpcbData); + } + + private: + static bool ProcessOneExport(const base::win::PEImage& image, + DWORD ordinal, + DWORD hint, + LPCSTR name, + PVOID function_addr, + LPCSTR forward, + PVOID cookie) { + if (!name || !function_addr) { + return true; + } + + static const auto kMap = + base::MakeFixedFlatMapSorted<base::StringPiece, void**>({ + {"RegCloseKey", reinterpret_cast<void**>(®_close_key_)}, + {"RegCreateKeyExW", reinterpret_cast<void**>(®_create_key_ex_)}, + {"RegDeleteKeyExW", reinterpret_cast<void**>(®_delete_key_ex_)}, + {"RegDeleteValueW", reinterpret_cast<void**>(®_delete_value_)}, + {"RegEnumKeyExW", reinterpret_cast<void**>(®_enum_key_ex_)}, + {"RegEnumValueW", reinterpret_cast<void**>(®_enum_value_)}, + {"RegOpenKeyExW", reinterpret_cast<void**>(®_open_key_ex_)}, + {"RegQueryInfoKeyW", + reinterpret_cast<void**>(®_query_info_key_)}, + {"RegQueryValueExW", + reinterpret_cast<void**>(®_query_value_ex_)}, + {"RegSetValueExW", reinterpret_cast<void**>(®_set_value_ex_)}, + }); + + auto* entry = kMap.find(name); + if (entry == kMap.end()) { + return true; + } + + static size_t num_init_functions = 0; + if (!std::exchange(*(entry->second), function_addr)) { + ++num_init_functions; + } + + bool& fully_resolved = *static_cast<bool*>(cookie); + fully_resolved = num_init_functions == kMap.size(); + return !fully_resolved; + } + + static bool ResolveRegistryFunctions() { + static bool initialized = []() { + base::NativeLibraryLoadError error; + HMODULE advapi32 = base::PinSystemLibrary(L"advapi32.dll", &error); + if (!advapi32 || error.code) { + return false; + } + bool fully_resolved = false; + base::win::PEImage(advapi32).EnumExports(&ProcessOneExport, + &fully_resolved); + return fully_resolved; + }(); + return initialized; + } + + static decltype(::RegCreateKeyExW)* reg_create_key_ex_; + static decltype(::RegOpenKeyExW)* reg_open_key_ex_; + static decltype(::RegDeleteKeyExW)* reg_delete_key_ex_; + static decltype(::RegQueryInfoKeyW)* reg_query_info_key_; + static decltype(::RegEnumKeyExW)* reg_enum_key_ex_; + static decltype(::RegCloseKey)* reg_close_key_; + static decltype(::RegQueryValueExW)* reg_query_value_ex_; + static decltype(::RegSetValueExW)* reg_set_value_ex_; + static decltype(::RegDeleteValueW)* reg_delete_value_; + static decltype(::RegEnumValueW)* reg_enum_value_; +}; + +decltype(::RegCreateKeyEx)* ExportDerived::reg_create_key_ex_ = nullptr; +decltype(::RegOpenKeyExW)* ExportDerived::reg_open_key_ex_ = nullptr; +decltype(::RegDeleteKeyExW)* ExportDerived::reg_delete_key_ex_ = nullptr; +decltype(::RegQueryInfoKeyW)* ExportDerived::reg_query_info_key_ = nullptr; +decltype(::RegEnumKeyExW)* ExportDerived::reg_enum_key_ex_ = nullptr; +decltype(::RegCloseKey)* ExportDerived::reg_close_key_ = nullptr; +decltype(::RegQueryValueEx)* ExportDerived::reg_query_value_ex_ = nullptr; +decltype(::RegSetValueExW)* ExportDerived::reg_set_value_ex_ = nullptr; +decltype(::RegDeleteValueW)* ExportDerived::reg_delete_value_ = nullptr; +decltype(::RegEnumValueW)* ExportDerived::reg_enum_value_ = nullptr; + // Watches for modifications to a key. -class RegKey::Watcher : public ObjectWatcher::Delegate { +template <typename Reg> +class GenericRegKey<Reg>::Watcher : public ObjectWatcher::Delegate { public: Watcher() = default; @@ -71,15 +393,19 @@ class RegKey::Watcher : public ObjectWatcher::Delegate { ChangeCallback callback_; }; -bool RegKey::Watcher::StartWatching(HKEY key, ChangeCallback callback) { +template <typename Reg> +bool GenericRegKey<Reg>::Watcher::StartWatching(HKEY key, + ChangeCallback callback) { DCHECK(key); DCHECK(callback_.is_null()); - if (!watch_event_.is_valid()) + if (!watch_event_.is_valid()) { watch_event_.Set(CreateEvent(nullptr, TRUE, FALSE, nullptr)); + } - if (!watch_event_.is_valid()) + if (!watch_event_.is_valid()) { return false; + } DWORD filter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES | REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY | @@ -97,25 +423,33 @@ bool RegKey::Watcher::StartWatching(HKEY key, ChangeCallback callback) { return object_watcher_.StartWatchingOnce(watch_event_.get(), this); } -// RegKey ---------------------------------------------------------------------- +// GenericRegKey<Reg> +// ---------------------------------------------------------------------- -RegKey::RegKey() = default; +template <typename Reg> +GenericRegKey<Reg>::GenericRegKey() = default; -RegKey::RegKey(HKEY key) : key_(key) {} +template <typename Reg> +GenericRegKey<Reg>::GenericRegKey(HKEY key) : key_(key) {} -RegKey::RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access) { +template <typename Reg> +GenericRegKey<Reg>::GenericRegKey(HKEY rootkey, + const wchar_t* subkey, + REGSAM access) { if (rootkey) { - if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) + if (access & (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_CREATE_LINK)) { Create(rootkey, subkey, access); - else + } else { Open(rootkey, subkey, access); + } } else { DCHECK(!subkey); wow64access_ = access & kWow64AccessMask; } } -RegKey::RegKey(RegKey&& other) noexcept +template <typename Reg> +GenericRegKey<Reg>::GenericRegKey(GenericRegKey<Reg>&& other) noexcept : key_(other.key_), wow64access_(other.wow64access_), key_watcher_(std::move(other.key_watcher_)) { @@ -123,7 +457,8 @@ RegKey::RegKey(RegKey&& other) noexcept other.wow64access_ = 0; } -RegKey& RegKey::operator=(RegKey&& other) { +template <typename Reg> +GenericRegKey<Reg>& GenericRegKey<Reg>::operator=(GenericRegKey<Reg>&& other) { Close(); std::swap(key_, other.key_); std::swap(wow64access_, other.wow64access_); @@ -131,23 +466,28 @@ RegKey& RegKey::operator=(RegKey&& other) { return *this; } -RegKey::~RegKey() { +template <typename Reg> +GenericRegKey<Reg>::~GenericRegKey() { Close(); } -LONG RegKey::Create(HKEY rootkey, const wchar_t* subkey, REGSAM access) { +template <typename Reg> +LONG GenericRegKey<Reg>::Create(HKEY rootkey, + const wchar_t* subkey, + REGSAM access) { DWORD disposition_value; return CreateWithDisposition(rootkey, subkey, &disposition_value, access); } -LONG RegKey::CreateWithDisposition(HKEY rootkey, - const wchar_t* subkey, - DWORD* disposition, - REGSAM access) { +template <typename Reg> +LONG GenericRegKey<Reg>::CreateWithDisposition(HKEY rootkey, + const wchar_t* subkey, + DWORD* disposition, + REGSAM access) { DCHECK(rootkey && subkey && access && disposition); HKEY subhkey = nullptr; LONG result = - RegCreateKeyEx(rootkey, subkey, 0, nullptr, REG_OPTION_NON_VOLATILE, + Reg::CreateKey(rootkey, subkey, 0, nullptr, REG_OPTION_NON_VOLATILE, access, nullptr, &subhkey, disposition); if (result == ERROR_SUCCESS) { Close(); @@ -158,19 +498,21 @@ LONG RegKey::CreateWithDisposition(HKEY rootkey, return result; } -LONG RegKey::CreateKey(const wchar_t* name, REGSAM access) { +template <typename Reg> +LONG GenericRegKey<Reg>::CreateKey(const wchar_t* name, REGSAM access) { DCHECK(name && access); - // After the application has accessed an alternate registry view using one of - // the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent operations - // (create, delete, or open) on child registry keys must explicitly use the - // same flag. Otherwise, there can be unexpected behavior. + // After the application has accessed an alternate registry view using one + // of the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent + // operations (create, delete, or open) on child registry keys must + // explicitly use the same flag. Otherwise, there can be unexpected + // behavior. // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx. if ((access & kWow64AccessMask) != wow64access_) { NOTREACHED(); return ERROR_INVALID_PARAMETER; } HKEY subkey = nullptr; - LONG result = RegCreateKeyEx(key_, name, 0, nullptr, REG_OPTION_NON_VOLATILE, + LONG result = Reg::CreateKey(key_, name, 0, nullptr, REG_OPTION_NON_VOLATILE, access, nullptr, &subkey, nullptr); if (result == ERROR_SUCCESS) { Close(); @@ -181,11 +523,14 @@ LONG RegKey::CreateKey(const wchar_t* name, REGSAM access) { return result; } -LONG RegKey::Open(HKEY rootkey, const wchar_t* subkey, REGSAM access) { +template <typename Reg> +LONG GenericRegKey<Reg>::Open(HKEY rootkey, + const wchar_t* subkey, + REGSAM access) { DCHECK(rootkey && subkey && access); HKEY subhkey = nullptr; - LONG result = RegOpenKeyEx(rootkey, subkey, 0, access, &subhkey); + LONG result = Reg::OpenKey(rootkey, subkey, 0, access, &subhkey); if (result == ERROR_SUCCESS) { Close(); key_ = subhkey; @@ -195,19 +540,22 @@ LONG RegKey::Open(HKEY rootkey, const wchar_t* subkey, REGSAM access) { return result; } -LONG RegKey::OpenKey(const wchar_t* relative_key_name, REGSAM access) { +template <typename Reg> +LONG GenericRegKey<Reg>::OpenKey(const wchar_t* relative_key_name, + REGSAM access) { DCHECK(relative_key_name && access); - // After the application has accessed an alternate registry view using one of - // the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent operations - // (create, delete, or open) on child registry keys must explicitly use the - // same flag. Otherwise, there can be unexpected behavior. + // After the application has accessed an alternate registry view using one + // of the [KEY_WOW64_32KEY / KEY_WOW64_64KEY] flags, all subsequent + // operations (create, delete, or open) on child registry keys must + // explicitly use the same flag. Otherwise, there can be unexpected + // behavior. // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129.aspx. if ((access & kWow64AccessMask) != wow64access_) { NOTREACHED(); return ERROR_INVALID_PARAMETER; } HKEY subkey = nullptr; - LONG result = RegOpenKeyEx(key_, relative_key_name, 0, access, &subkey); + LONG result = Reg::OpenKey(key_, relative_key_name, 0, access, &subkey); // We have to close the current opened key before replacing it with the new // one. @@ -219,62 +567,72 @@ LONG RegKey::OpenKey(const wchar_t* relative_key_name, REGSAM access) { return result; } -void RegKey::Close() { +template <typename Reg> +void GenericRegKey<Reg>::Close() { if (key_) { - ::RegCloseKey(key_); + Reg::CloseKey(key_); key_ = nullptr; wow64access_ = 0; } } -// TODO(wfh): Remove this and other unsafe methods. See http://crbug.com/375400 -void RegKey::Set(HKEY key) { +// TODO(wfh): Remove this and other unsafe methods. See +// http://crbug.com/375400 +template <typename Reg> +void GenericRegKey<Reg>::Set(HKEY key) { if (key_ != key) { Close(); key_ = key; } } -HKEY RegKey::Take() { +template <typename Reg> +HKEY GenericRegKey<Reg>::Take() { DCHECK_EQ(wow64access_, 0u); HKEY key = key_; key_ = nullptr; return key; } -bool RegKey::HasValue(const wchar_t* name) const { - return RegQueryValueEx(key_, name, nullptr, nullptr, nullptr, nullptr) == +template <typename Reg> +bool GenericRegKey<Reg>::HasValue(const wchar_t* name) const { + return Reg::QueryValue(key_, name, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS; } -DWORD RegKey::GetValueCount() const { +template <typename Reg> +DWORD GenericRegKey<Reg>::GetValueCount() const { DWORD count = 0; LONG result = - RegQueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, &count, nullptr, nullptr, nullptr, nullptr); + Reg::QueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, &count, nullptr, nullptr, nullptr, nullptr); return (result == ERROR_SUCCESS) ? count : 0; } -FILETIME RegKey::GetLastWriteTime() const { +template <typename Reg> +FILETIME GenericRegKey<Reg>::GetLastWriteTime() const { FILETIME last_write_time; - LONG result = RegQueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, &last_write_time); + LONG result = Reg::QueryInfoKey(key_, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, &last_write_time); return (result == ERROR_SUCCESS) ? last_write_time : FILETIME{}; } -LONG RegKey::GetValueNameAt(DWORD index, std::wstring* name) const { +template <typename Reg> +LONG GenericRegKey<Reg>::GetValueNameAt(DWORD index, std::wstring* name) const { wchar_t buf[256]; DWORD bufsize = std::size(buf); - LONG r = ::RegEnumValue(key_, index, buf, &bufsize, nullptr, nullptr, nullptr, + LONG r = Reg::EnumValue(key_, index, buf, &bufsize, nullptr, nullptr, nullptr, nullptr); - if (r == ERROR_SUCCESS) + if (r == ERROR_SUCCESS) { name->assign(buf, bufsize); + } return r; } -LONG RegKey::DeleteKey(const wchar_t* name) { +template <typename Reg> +LONG GenericRegKey<Reg>::DeleteKey(const wchar_t* name) { DCHECK(name); // Verify the key exists before attempting delete to replicate previous @@ -282,64 +640,75 @@ LONG RegKey::DeleteKey(const wchar_t* name) { // `RegOpenKeyEx()` will return an error if `key_` is invalid. HKEY subkey = nullptr; LONG result = - RegOpenKeyEx(key_, name, 0, READ_CONTROL | wow64access_, &subkey); - if (result != ERROR_SUCCESS) + Reg::OpenKey(key_, name, 0, READ_CONTROL | wow64access_, &subkey); + if (result != ERROR_SUCCESS) { return result; - RegCloseKey(subkey); + } + Reg::CloseKey(subkey); return RegDelRecurse(key_, name, wow64access_); } -LONG RegKey::DeleteEmptyKey(const wchar_t* name) { +template <typename Reg> +LONG GenericRegKey<Reg>::DeleteEmptyKey(const wchar_t* name) { DCHECK(name); // `RegOpenKeyEx()` will return an error if `key_` is invalid. HKEY target_key = nullptr; LONG result = - RegOpenKeyEx(key_, name, 0, KEY_READ | wow64access_, &target_key); + Reg::OpenKey(key_, name, 0, KEY_READ | wow64access_, &target_key); - if (result != ERROR_SUCCESS) + if (result != ERROR_SUCCESS) { return result; + } DWORD count = 0; result = - RegQueryInfoKey(target_key, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, &count, nullptr, nullptr, nullptr, nullptr); + Reg::QueryInfoKey(target_key, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, &count, nullptr, nullptr, nullptr, nullptr); - RegCloseKey(target_key); + Reg::CloseKey(target_key); - if (result != ERROR_SUCCESS) + if (result != ERROR_SUCCESS) { return result; + } - if (count == 0) + if (count == 0) { return RegDeleteKeyEx(key_, name, wow64access_, 0); + } return ERROR_DIR_NOT_EMPTY; } -LONG RegKey::DeleteValue(const wchar_t* value_name) { +template <typename Reg> +LONG GenericRegKey<Reg>::DeleteValue(const wchar_t* value_name) { // `RegDeleteValue()` will return an error if `key_` is invalid. - LONG result = RegDeleteValue(key_, value_name); + LONG result = Reg::DeleteValue(key_, value_name); return result; } -LONG RegKey::ReadValueDW(const wchar_t* name, DWORD* out_value) const { +template <typename Reg> +LONG GenericRegKey<Reg>::ReadValueDW(const wchar_t* name, + DWORD* out_value) const { DCHECK(out_value); DWORD type = REG_DWORD; DWORD size = sizeof(DWORD); DWORD local_value = 0; LONG result = ReadValue(name, &local_value, &size, &type); if (result == ERROR_SUCCESS) { - if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD)) + if ((type == REG_DWORD || type == REG_BINARY) && size == sizeof(DWORD)) { *out_value = local_value; - else + } else { result = ERROR_CANTREAD; + } } return result; } -LONG RegKey::ReadInt64(const wchar_t* name, int64_t* out_value) const { +template <typename Reg> +LONG GenericRegKey<Reg>::ReadInt64(const wchar_t* name, + int64_t* out_value) const { DCHECK(out_value); DWORD type = REG_QWORD; int64_t local_value = 0; @@ -347,16 +716,19 @@ LONG RegKey::ReadInt64(const wchar_t* name, int64_t* out_value) const { LONG result = ReadValue(name, &local_value, &size, &type); if (result == ERROR_SUCCESS) { if ((type == REG_QWORD || type == REG_BINARY) && - size == sizeof(local_value)) + size == sizeof(local_value)) { *out_value = local_value; - else + } else { result = ERROR_CANTREAD; + } } return result; } -LONG RegKey::ReadValue(const wchar_t* name, std::wstring* out_value) const { +template <typename Reg> +LONG GenericRegKey<Reg>::ReadValue(const wchar_t* name, + std::wstring* out_value) const { DCHECK(out_value); const size_t kMaxStringLength = 1024; // This is after expansion. // Use the one of the other forms of ReadValue if 1024 is too small for you. @@ -386,36 +758,41 @@ LONG RegKey::ReadValue(const wchar_t* name, std::wstring* out_value) const { return result; } -LONG RegKey::ReadValue(const wchar_t* name, - void* data, - DWORD* dsize, - DWORD* dtype) const { - LONG result = RegQueryValueEx(key_, name, nullptr, dtype, +template <typename Reg> +LONG GenericRegKey<Reg>::ReadValue(const wchar_t* name, + void* data, + DWORD* dsize, + DWORD* dtype) const { + LONG result = Reg::QueryValue(key_, name, nullptr, dtype, reinterpret_cast<LPBYTE>(data), dsize); return result; } -LONG RegKey::ReadValues(const wchar_t* name, - std::vector<std::wstring>* values) { +template <typename Reg> +LONG GenericRegKey<Reg>::ReadValues(const wchar_t* name, + std::vector<std::wstring>* values) { values->clear(); DWORD type = REG_MULTI_SZ; DWORD size = 0; LONG result = ReadValue(name, nullptr, &size, &type); - if (result != ERROR_SUCCESS || size == 0) + if (result != ERROR_SUCCESS || size == 0) { return result; + } - if (type != REG_MULTI_SZ) + if (type != REG_MULTI_SZ) { return ERROR_CANTREAD; + } std::vector<wchar_t> buffer(size / sizeof(wchar_t)); result = ReadValue(name, buffer.data(), &size, nullptr); - if (result != ERROR_SUCCESS || size == 0) + if (result != ERROR_SUCCESS || size == 0) { return result; + } // Parse the double-null-terminated list of strings. - // Note: This code is paranoid to not read outside of |buf|, in the case where - // it may not be properly terminated. + // Note: This code is paranoid to not read outside of |buf|, in the case + // where it may not be properly terminated. auto entry = buffer.cbegin(); auto buffer_end = buffer.cend(); while (entry < buffer_end && *entry != '\0') { @@ -426,12 +803,15 @@ LONG RegKey::ReadValues(const wchar_t* name, return 0; } -LONG RegKey::WriteValue(const wchar_t* name, DWORD in_value) { +template <typename Reg> +LONG GenericRegKey<Reg>::WriteValue(const wchar_t* name, DWORD in_value) { return WriteValue(name, &in_value, static_cast<DWORD>(sizeof(in_value)), REG_DWORD); } -LONG RegKey::WriteValue(const wchar_t* name, const wchar_t* in_value) { +template <typename Reg> +LONG GenericRegKey<Reg>::WriteValue(const wchar_t* name, + const wchar_t* in_value) { return WriteValue( name, in_value, static_cast<DWORD>(sizeof(*in_value) * @@ -439,49 +819,59 @@ LONG RegKey::WriteValue(const wchar_t* name, const wchar_t* in_value) { REG_SZ); } -LONG RegKey::WriteValue(const wchar_t* name, - const void* data, - DWORD dsize, - DWORD dtype) { +template <typename Reg> +LONG GenericRegKey<Reg>::WriteValue(const wchar_t* name, + const void* data, + DWORD dsize, + DWORD dtype) { DCHECK(data || !dsize); LONG result = - RegSetValueEx(key_, name, 0, dtype, + Reg::SetValue(key_, name, 0, dtype, reinterpret_cast<LPBYTE>(const_cast<void*>(data)), dsize); return result; } -bool RegKey::StartWatching(ChangeCallback callback) { - if (!key_watcher_) +template <typename Reg> +bool GenericRegKey<Reg>::StartWatching(ChangeCallback callback) { + if (!key_watcher_) { key_watcher_ = std::make_unique<Watcher>(); + } - if (!key_watcher_->StartWatching(key_, std::move(callback))) + if (!key_watcher_->StartWatching(key_, std::move(callback))) { return false; + } return true; } // static -LONG RegKey::RegDelRecurse(HKEY root_key, const wchar_t* name, REGSAM access) { +template <typename Reg> +LONG GenericRegKey<Reg>::RegDelRecurse(HKEY root_key, + const wchar_t* name, + REGSAM access) { // First, see if the key can be deleted without having to recurse. - LONG result = RegDeleteKeyEx(root_key, name, access, 0); - if (result == ERROR_SUCCESS) + LONG result = Reg::DeleteKey(root_key, name, access, 0); + if (result == ERROR_SUCCESS) { return result; + } HKEY target_key = nullptr; - result = RegOpenKeyEx(root_key, name, 0, KEY_ENUMERATE_SUB_KEYS | access, + result = Reg::OpenKey(root_key, name, 0, KEY_ENUMERATE_SUB_KEYS | access, &target_key); - if (result == ERROR_FILE_NOT_FOUND) + if (result == ERROR_FILE_NOT_FOUND) { return ERROR_SUCCESS; + } if (result != ERROR_SUCCESS) return result; std::wstring subkey_name(name); // Check for an ending slash and add one if it is missing. - if (!subkey_name.empty() && subkey_name.back() != '\\') + if (!subkey_name.empty() && subkey_name.back() != '\\') { subkey_name.push_back('\\'); + } // Enumerate the keys result = ERROR_SUCCESS; @@ -491,28 +881,70 @@ LONG RegKey::RegDelRecurse(HKEY root_key, const wchar_t* name, REGSAM access) { while (result == ERROR_SUCCESS) { DWORD key_size = kMaxKeyNameLength; result = - RegEnumKeyEx(target_key, 0, WriteInto(&key_name, kMaxKeyNameLength), + Reg::EnumKey(target_key, 0, WriteInto(&key_name, kMaxKeyNameLength), &key_size, nullptr, nullptr, nullptr, nullptr); - if (result != ERROR_SUCCESS) + if (result != ERROR_SUCCESS) { break; + } key_name.resize(key_size); subkey_name.resize(base_key_length); subkey_name += key_name; - if (RegDelRecurse(root_key, subkey_name.c_str(), access) != ERROR_SUCCESS) + if (RegDelRecurse(root_key, subkey_name.c_str(), access) != ERROR_SUCCESS) { break; + } } - RegCloseKey(target_key); + Reg::CloseKey(target_key); // Try again to delete the key. - result = RegDeleteKeyEx(root_key, name, access, 0); + result = Reg::DeleteKey(root_key, name, access, 0); return result; } +// Instantiate the only two allowed versions of GenericRegKey for use by the +// public base::win::RegKey and base::win::ExportDerivedRegKey. +template class GenericRegKey<internal::Standard>; +template class GenericRegKey<internal::ExportDerived>; + +} // namespace internal + +RegKey::RegKey() : GenericRegKey<internal::Standard>() {} +RegKey::RegKey(HKEY key) : GenericRegKey<internal::Standard>(key) {} +RegKey::RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access) + : GenericRegKey<internal::Standard>(rootkey, subkey, access) {} + +RegKey::RegKey(RegKey&& other) noexcept + : GenericRegKey<internal::Standard>(std::move(other)) {} +RegKey& RegKey::operator=(RegKey&& other) { + GenericRegKey<internal::Standard>::operator=(std::move(other)); + return *this; +} + +RegKey::~RegKey() = default; + +ExportDerivedRegKey::ExportDerivedRegKey() + : GenericRegKey<internal::ExportDerived>() {} +ExportDerivedRegKey::ExportDerivedRegKey(HKEY key) + : GenericRegKey<internal::ExportDerived>(key) {} +ExportDerivedRegKey::ExportDerivedRegKey(HKEY rootkey, + const wchar_t* subkey, + REGSAM access) + : GenericRegKey<internal::ExportDerived>(rootkey, subkey, access) {} + +ExportDerivedRegKey::ExportDerivedRegKey(ExportDerivedRegKey&& other) noexcept + : GenericRegKey<internal::ExportDerived>(std::move(other)) {} +ExportDerivedRegKey& ExportDerivedRegKey::operator=( + ExportDerivedRegKey&& other) { + GenericRegKey<internal::ExportDerived>::operator=(std::move(other)); + return *this; +} + +ExportDerivedRegKey::~ExportDerivedRegKey() = default; + // RegistryValueIterator ------------------------------------------------------ RegistryValueIterator::RegistryValueIterator(HKEY root_key, diff --git a/base/win/registry.h b/base/win/registry.h index 8037cedfc..8e6372a40 100644 --- a/base/win/registry.h +++ b/base/win/registry.h @@ -13,8 +13,11 @@ #include "base/base_export.h" #include "base/functional/callback_forward.h" +#include "base/gtest_prod_util.h" #include "base/win/windows_types.h" +class ShellUtil; + namespace base { namespace win { @@ -27,21 +30,31 @@ namespace win { // is not touched in case of failure. // * Functions returning LONG indicate success as ERROR_SUCCESS or an // error as a (non-zero) win32 error code. -class BASE_EXPORT RegKey { +// +// Most developers should use base::win::RegKey subclass below. +namespace internal { + +class Standard; +class ExportDerived; +template <typename T> +class RegTestTraits; + +template <typename Reg> +class GenericRegKey { public: // Called from the MessageLoop when the key changes. using ChangeCallback = OnceCallback<void()>; - RegKey(); - explicit RegKey(HKEY key); - RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access); - RegKey(RegKey&& other) noexcept; - RegKey& operator=(RegKey&& other); + GenericRegKey(); + explicit GenericRegKey(HKEY key); + GenericRegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access); + GenericRegKey(GenericRegKey&& other) noexcept; + GenericRegKey& operator=(GenericRegKey&& other); - RegKey(const RegKey&) = delete; - RegKey& operator=(const RegKey&) = delete; + GenericRegKey(const GenericRegKey&) = delete; + GenericRegKey& operator=(const GenericRegKey&) = delete; - ~RegKey(); + virtual ~GenericRegKey(); LONG Create(HKEY rootkey, const wchar_t* subkey, REGSAM access); @@ -139,8 +152,8 @@ class BASE_EXPORT RegKey { // Starts watching the key to see if any of its values have changed. // The key must have been opened with the KEY_NOTIFY access privilege. // Returns true on success. - // To stop watching, delete this RegKey object. To continue watching the - // object after the callback is invoked, call StartWatching again. + // To stop watching, delete this GenericRegKey object. To continue watching + // the object after the callback is invoked, call StartWatching again. bool StartWatching(ChangeCallback callback); HKEY Handle() const { return key_; } @@ -156,6 +169,47 @@ class BASE_EXPORT RegKey { std::unique_ptr<Watcher> key_watcher_; }; +} // namespace internal + +// The Windows registry utility class most developers should use. +class BASE_EXPORT RegKey : public internal::GenericRegKey<internal::Standard> { + public: + RegKey(); + explicit RegKey(HKEY key); + RegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access); + RegKey(RegKey&& other) noexcept; + RegKey& operator=(RegKey&& other); + + RegKey(const RegKey&) = delete; + RegKey& operator=(const RegKey&) = delete; + + ~RegKey() override; +}; + +// A Windows registry class that derives its calls directly from advapi32.dll. +// Generally, you should use RegKey above. Note that use of this API will pin +// advapi32.dll. If you need to use this class, please reach out to the +// base/win/OWNERS first. +class BASE_EXPORT ExportDerivedRegKey + : public internal::GenericRegKey<internal::ExportDerived> { + public: + ExportDerivedRegKey(ExportDerivedRegKey&& other) noexcept; + ExportDerivedRegKey& operator=(ExportDerivedRegKey&& other); + + ExportDerivedRegKey(const ExportDerivedRegKey&) = delete; + ExportDerivedRegKey& operator=(const ExportDerivedRegKey&) = delete; + + ~ExportDerivedRegKey() override; + + private: + friend class ::ShellUtil; + friend class internal::RegTestTraits<ExportDerivedRegKey>; + + ExportDerivedRegKey(); + explicit ExportDerivedRegKey(HKEY key); + ExportDerivedRegKey(HKEY rootkey, const wchar_t* subkey, REGSAM access); +}; + // Iterates the entries found in a particular folder on the registry. class BASE_EXPORT RegistryValueIterator { public: diff --git a/base/win/registry_unittest.cc b/base/win/registry_unittest.cc index 77bb741d1..030d01e02 100644 --- a/base/win/registry_unittest.cc +++ b/base/win/registry_unittest.cc @@ -11,6 +11,7 @@ #include <cstring> #include <iterator> +#include <type_traits> #include <utility> #include "base/compiler_specific.h" @@ -37,6 +38,7 @@ constexpr wchar_t kRootKey[] = L"Base_Registry_Unittest"; // A test harness for registry tests that operate in HKCU. Each test is given // a valid key distinct from that used by other tests. +template <typename Traits> class RegistryTest : public testing::Test { protected: RegistryTest() : root_key_(std::wstring(L"Software\\") + kRootKey) {} @@ -46,7 +48,8 @@ class RegistryTest : public testing::Test { registry_override_.OverrideRegistry(HKEY_CURRENT_USER)); // Create the test's root key. - RegKey key(HKEY_CURRENT_USER, L"", KEY_CREATE_SUB_KEY); + typename Traits::RegType key( + Traits::Create(HKEY_CURRENT_USER, L"", KEY_CREATE_SUB_KEY)); ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ)); ASSERT_EQ(ERROR_SUCCESS, @@ -62,10 +65,50 @@ class RegistryTest : public testing::Test { const std::wstring root_key_; }; -TEST_F(RegistryTest, ValueTest) { - RegKey key; +} // namespace + +namespace internal { + +template <typename T> +class RegTestTraits { + public: + using RegType = T; + + static T Create() { return T(); } + + static T Create(HKEY rootkey, const wchar_t* subkey, REGSAM access) { + return T(rootkey, subkey, access); + } +}; + +} // namespace internal + +namespace { + +class RegistryTypeNames { + public: + template <typename T> + static std::string GetName(int index) { + if (std::is_same<typename T::RegType, RegKey>()) { + return "RegKey"; + } + if (std::is_same<typename T::RegType, ExportDerivedRegKey>()) { + return "ExportDerivedRegKey"; + } + } +}; + +} // namespace + +using RegistryTypes = + ::testing::Types<internal::RegTestTraits<RegKey>, + internal::RegTestTraits<ExportDerivedRegKey>>; +TYPED_TEST_SUITE(RegistryTest, RegistryTypes, RegistryTypeNames); + +TYPED_TEST(RegistryTest, ValueTest) { + typename TypeParam::RegType key(TypeParam::Create()); - ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(), + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ | KEY_SET_VALUE)); ASSERT_TRUE(key.Valid()); @@ -116,9 +159,9 @@ TEST_F(RegistryTest, ValueTest) { EXPECT_FALSE(key.HasValue(kInt64ValueName)); } -TEST_F(RegistryTest, BigValueIteratorTest) { - RegKey key; - ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(), +TYPED_TEST(RegistryTest, BigValueIteratorTest) { + typename TypeParam::RegType key(TypeParam::Create()); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ | KEY_SET_VALUE)); ASSERT_TRUE(key.Valid()); @@ -127,7 +170,7 @@ TEST_F(RegistryTest, BigValueIteratorTest) { ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(data.c_str(), data.c_str())); - RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str()); + RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str()); ASSERT_TRUE(iterator.Valid()); EXPECT_EQ(data, iterator.Name()); EXPECT_EQ(data, iterator.Value()); @@ -137,9 +180,9 @@ TEST_F(RegistryTest, BigValueIteratorTest) { EXPECT_FALSE(iterator.Valid()); } -TEST_F(RegistryTest, TruncatedCharTest) { - RegKey key; - ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(), +TYPED_TEST(RegistryTest, TruncatedCharTest) { + typename TypeParam::RegType key(TypeParam::Create()); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ | KEY_SET_VALUE)); ASSERT_TRUE(key.Valid()); @@ -150,7 +193,7 @@ TEST_F(RegistryTest, TruncatedCharTest) { ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kName, kData, std::size(kData), REG_BINARY)); - RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str()); + RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str()); ASSERT_TRUE(iterator.Valid()); // Avoid having to use EXPECT_STREQ here by leveraging StringPiece's // operator== to perform a deep comparison. @@ -167,19 +210,20 @@ TEST_F(RegistryTest, TruncatedCharTest) { } // Tests that the value iterator is okay with an empty key. -TEST_F(RegistryTest, ValueIteratorEmptyKey) { - RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str()); +TYPED_TEST(RegistryTest, ValueIteratorEmptyKey) { + RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str()); EXPECT_EQ(iterator.ValueCount(), 0U); EXPECT_FALSE(iterator.Valid()); } // Tests that the default value is seen by a value iterator. -TEST_F(RegistryTest, ValueIteratorDefaultValue) { +TYPED_TEST(RegistryTest, ValueIteratorDefaultValue) { const WStringPiece kTestString(L"i miss you"); - ASSERT_EQ(RegKey(HKEY_CURRENT_USER, root_key().c_str(), KEY_SET_VALUE) + ASSERT_EQ(TypeParam::Create(HKEY_CURRENT_USER, this->root_key().c_str(), + KEY_SET_VALUE) .WriteValue(nullptr, kTestString.data()), ERROR_SUCCESS); - RegistryValueIterator iterator(HKEY_CURRENT_USER, root_key().c_str()); + RegistryValueIterator iterator(HKEY_CURRENT_USER, this->root_key().c_str()); EXPECT_EQ(iterator.ValueCount(), 1U); ASSERT_TRUE(iterator.Valid()); EXPECT_EQ(WStringPiece(iterator.Name()), WStringPiece()); @@ -190,8 +234,8 @@ TEST_F(RegistryTest, ValueIteratorDefaultValue) { EXPECT_FALSE(iterator.Valid()); } -TEST_F(RegistryTest, RecursiveDelete) { - RegKey key; +TYPED_TEST(RegistryTest, RecursiveDelete) { + typename TypeParam::RegType key(TypeParam::Create()); // Create root_key() // \->Bar (TestValue) // \->Foo (TestValue) @@ -200,7 +244,7 @@ TEST_F(RegistryTest, RecursiveDelete) { // \->Moo // \->Foo // and delete root_key() - std::wstring key_path = root_key(); + std::wstring key_path = this->root_key(); ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_CREATE_SUB_KEY)); ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Bar", KEY_WRITE)); @@ -222,7 +266,7 @@ TEST_F(RegistryTest, RecursiveDelete) { key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ)); ASSERT_EQ(ERROR_SUCCESS, - key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_WRITE)); + key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_WRITE)); ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"")); ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"Bar\\Foo")); ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"Bar")); @@ -239,33 +283,35 @@ TEST_F(RegistryTest, RecursiveDelete) { key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ)); ASSERT_EQ(ERROR_SUCCESS, - key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_WRITE)); + key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_WRITE)); ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(L"Bar")); ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(L"Bar")); ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_path.c_str(), KEY_READ)); } -TEST_F(RegistryTest, OpenSubKey) { - RegKey key; - ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, root_key().c_str(), +TYPED_TEST(RegistryTest, OpenSubKey) { + typename TypeParam::RegType key(TypeParam::Create()); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ | KEY_CREATE_SUB_KEY)); ASSERT_NE(ERROR_SUCCESS, key.OpenKey(L"foo", KEY_READ)); ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"foo", KEY_READ)); ASSERT_EQ(ERROR_SUCCESS, - key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ)); + key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ)); ASSERT_EQ(ERROR_SUCCESS, key.OpenKey(L"foo", KEY_READ)); - std::wstring foo_key = root_key() + L"\\Foo"; + std::wstring foo_key = this->root_key() + L"\\Foo"; ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_READ)); ASSERT_EQ(ERROR_SUCCESS, - key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_WRITE)); + key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_WRITE)); ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(L"foo")); } +namespace { + class TestChangeDelegate { public: TestChangeDelegate() = default; @@ -286,22 +332,25 @@ class TestChangeDelegate { bool called_ = false; }; -TEST_F(RegistryTest, ChangeCallback) { - RegKey key; +} // namespace + +TYPED_TEST(RegistryTest, ChangeCallback) { + typename TypeParam::RegType key(TypeParam::Create()); TestChangeDelegate delegate; test::TaskEnvironment task_environment; ASSERT_EQ(ERROR_SUCCESS, - key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ)); + key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ)); ASSERT_TRUE(key.StartWatching( BindOnce(&TestChangeDelegate::OnKeyChanged, Unretained(&delegate)))); EXPECT_FALSE(delegate.WasCalled()); // Make some change. - RegKey key2; - ASSERT_EQ(ERROR_SUCCESS, key2.Open(HKEY_CURRENT_USER, root_key().c_str(), - KEY_READ | KEY_SET_VALUE)); + typename TypeParam::RegType key2(TypeParam::Create()); + ASSERT_EQ(ERROR_SUCCESS, + key2.Open(HKEY_CURRENT_USER, this->root_key().c_str(), + KEY_READ | KEY_SET_VALUE)); ASSERT_TRUE(key2.Valid()); EXPECT_EQ(ERROR_SUCCESS, key2.WriteValue(L"name", L"data")); @@ -346,20 +395,21 @@ class RegistryWatcherThread : public SimpleThread { } // namespace -TEST_F(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) { - RegKey key; +TYPED_TEST(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) { + typename TypeParam::RegType key(TypeParam::Create()); - ASSERT_EQ(key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_READ), + ASSERT_EQ(key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ), ERROR_SUCCESS); auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>( base::TestMockTimeTaskRunner::Type::kBoundToThread); - ::testing::StrictMock<base::MockCallback<base::win::RegKey::ChangeCallback>> + ::testing::StrictMock< + base::MockCallback<typename TypeParam::RegType::ChangeCallback>> change_cb; - test_task_runner->PostTask(FROM_HERE, - BindOnce(IgnoreResult(&RegKey::StartWatching), - Unretained(&key), change_cb.Get())); + test_task_runner->PostTask( + FROM_HERE, BindOnce(IgnoreResult(&TypeParam::RegType::StartWatching), + Unretained(&key), change_cb.Get())); { // Start the watch on a thread that then goes away. @@ -379,8 +429,8 @@ TEST_F(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) { EXPECT_CALL(change_cb, Run).WillOnce([&run_loop]() { run_loop.Quit(); }); // Make some change. - RegKey key2; - ASSERT_EQ(key2.Open(HKEY_CURRENT_USER, root_key().c_str(), + typename TypeParam::RegType key2(TypeParam::Create()); + ASSERT_EQ(key2.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_READ | KEY_SET_VALUE), ERROR_SUCCESS); ASSERT_TRUE(key2.Valid()); @@ -390,12 +440,13 @@ TEST_F(RegistryTest, WatcherNotSignaledOnInitiatingThreadExit) { run_loop.Run(); } -TEST_F(RegistryTest, TestMoveConstruct) { - RegKey key; +TYPED_TEST(RegistryTest, TestMoveConstruct) { + typename TypeParam::RegType key(TypeParam::Create()); - ASSERT_EQ(key.Open(HKEY_CURRENT_USER, root_key().c_str(), KEY_SET_VALUE), - ERROR_SUCCESS); - RegKey key2(std::move(key)); + ASSERT_EQ( + key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_SET_VALUE), + ERROR_SUCCESS); + typename TypeParam::RegType key2(std::move(key)); // The old key should be meaningless now. EXPECT_EQ(key.Handle(), nullptr); @@ -405,17 +456,17 @@ TEST_F(RegistryTest, TestMoveConstruct) { EXPECT_EQ(key2.WriteValue(L"foo", 1U), ERROR_SUCCESS); } -TEST_F(RegistryTest, TestMoveAssign) { - RegKey key; - RegKey key2; +TYPED_TEST(RegistryTest, TestMoveAssign) { + typename TypeParam::RegType key(TypeParam::Create()); + typename TypeParam::RegType key2(TypeParam::Create()); const wchar_t kFooValueName[] = L"foo"; - ASSERT_EQ(key.Open(HKEY_CURRENT_USER, root_key().c_str(), + ASSERT_EQ(key.Open(HKEY_CURRENT_USER, this->root_key().c_str(), KEY_SET_VALUE | KEY_QUERY_VALUE), ERROR_SUCCESS); ASSERT_EQ(key.WriteValue(kFooValueName, 1U), ERROR_SUCCESS); - ASSERT_EQ(key2.Create(HKEY_CURRENT_USER, (root_key() + L"\\child").c_str(), - KEY_SET_VALUE), + ASSERT_EQ(key2.Create(HKEY_CURRENT_USER, + (this->root_key() + L"\\child").c_str(), KEY_SET_VALUE), ERROR_SUCCESS); key2 = std::move(key); @@ -431,8 +482,8 @@ TEST_F(RegistryTest, TestMoveAssign) { // Verify that either the platform, or the API-integration, causes deletion // attempts via an invalid handle to fail with the expected error code. -TEST_F(RegistryTest, DeleteWithInvalidRegKey) { - RegKey key; +TYPED_TEST(RegistryTest, DeleteWithInvalidRegKey) { + typename TypeParam::RegType key(TypeParam::Create()); static const wchar_t kFooName[] = L"foo"; @@ -441,9 +492,12 @@ TEST_F(RegistryTest, DeleteWithInvalidRegKey) { EXPECT_EQ(key.DeleteValue(kFooName), ERROR_INVALID_HANDLE); } +namespace { + // A test harness for tests that use HKLM to test WoW redirection and such. // TODO(https://crbug.com/377917): The tests here that write to the registry are // disabled because they need work to handle parallel runs of different tests. +template <typename Traits> class RegistryTestHKLM : public ::testing::Test { protected: enum : REGSAM { @@ -470,59 +524,71 @@ class RegistryTestHKLM : public ::testing::Test { const std::wstring foo_software_key_; }; -class RegistryTestHKLMAdmin : public RegistryTestHKLM { +} // namespace + +TYPED_TEST_SUITE(RegistryTestHKLM, RegistryTypes, RegistryTypeNames); + +namespace { + +template <typename Traits> +class RegistryTestHKLMAdmin : public RegistryTestHKLM<Traits> { protected: void SetUp() override { - if (!IsRedirectorPresent()) { + if (!this->IsRedirectorPresent()) { GTEST_SKIP(); } if (!::IsUserAnAdmin()) { GTEST_SKIP(); } // Clean up any stale registry keys. - for (const REGSAM mask : {kNativeViewMask, kRedirectedViewMask}) { - RegKey key; + for (const REGSAM mask : + {this->kNativeViewMask, this->kRedirectedViewMask}) { + typename Traits::RegType key(Traits::Create()); key.Open(HKEY_LOCAL_MACHINE, L"Software", KEY_SET_VALUE | mask); key.DeleteKey(kRootKey); } } }; +} // namespace + +TYPED_TEST_SUITE(RegistryTestHKLMAdmin, RegistryTypes, RegistryTypeNames); + // This test requires running as an Administrator as it tests redirected // registry writes to HKLM\Software // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384253.aspx -TEST_F(RegistryTestHKLMAdmin, Wow64RedirectedFromNative) { - RegKey key; +TYPED_TEST(RegistryTestHKLMAdmin, Wow64RedirectedFromNative) { + typename TypeParam::RegType key(TypeParam::Create()); // Test redirected key access from non-redirected. ASSERT_EQ(ERROR_SUCCESS, - key.Create(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), - KEY_WRITE | kRedirectedViewMask)); - ASSERT_NE(ERROR_SUCCESS, - key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), KEY_READ)); + key.Create(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(), + KEY_WRITE | this->kRedirectedViewMask)); + ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, + this->foo_software_key_.c_str(), KEY_READ)); ASSERT_NE(ERROR_SUCCESS, - key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), - KEY_READ | kNativeViewMask)); + key.Open(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(), + KEY_READ | this->kNativeViewMask)); // Open the non-redirected view of the parent and try to delete the test key. ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software", KEY_SET_VALUE)); ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey)); ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software", - KEY_SET_VALUE | kNativeViewMask)); + KEY_SET_VALUE | this->kNativeViewMask)); ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey)); // Open the redirected view and delete the key created above. ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software", - KEY_SET_VALUE | kRedirectedViewMask)); + KEY_SET_VALUE | this->kRedirectedViewMask)); ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey)); } // Test for the issue found in http://crbug.com/384587 where OpenKey would call // Close() and reset wow64_access_ flag to 0 and cause a NOTREACHED to hit on a // subsequent OpenKey call. -TEST_F(RegistryTestHKLM, SameWowFlags) { - RegKey key; +TYPED_TEST(RegistryTestHKLM, SameWowFlags) { + typename TypeParam::RegType key(TypeParam::Create()); ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software", KEY_READ | KEY_WOW64_64KEY)); @@ -531,30 +597,28 @@ TEST_F(RegistryTestHKLM, SameWowFlags) { ASSERT_EQ(ERROR_SUCCESS, key.OpenKey(L"Windows", KEY_READ | KEY_WOW64_64KEY)); } -TEST_F(RegistryTestHKLMAdmin, Wow64NativeFromRedirected) { - RegKey key; +TYPED_TEST(RegistryTestHKLMAdmin, Wow64NativeFromRedirected) { + typename TypeParam::RegType key(TypeParam::Create()); // Test non-redirected key access from redirected. ASSERT_EQ(ERROR_SUCCESS, - key.Create(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), - KEY_WRITE | kNativeViewMask)); - ASSERT_EQ(ERROR_SUCCESS, - key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), KEY_READ)); + key.Create(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(), + KEY_WRITE | this->kNativeViewMask)); + ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, + this->foo_software_key_.c_str(), KEY_READ)); ASSERT_NE(ERROR_SUCCESS, - key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), - KEY_READ | kRedirectedViewMask)); + key.Open(HKEY_LOCAL_MACHINE, this->foo_software_key_.c_str(), + KEY_READ | this->kRedirectedViewMask)); // Open the redirected view of the parent and try to delete the test key // from the non-redirected view. ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software", - KEY_SET_VALUE | kRedirectedViewMask)); + KEY_SET_VALUE | this->kRedirectedViewMask)); ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey)); ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_LOCAL_MACHINE, L"Software", - KEY_SET_VALUE | kNativeViewMask)); + KEY_SET_VALUE | this->kNativeViewMask)); ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey)); } -} // namespace - } // namespace base::win diff --git a/base/win/scoped_co_mem.h b/base/win/scoped_co_mem.h index 078290094..0667fbc7e 100644 --- a/base/win/scoped_co_mem.h +++ b/base/win/scoped_co_mem.h @@ -8,6 +8,7 @@ #include <objbase.h> #include "base/check.h" +#include "base/memory/raw_ptr_exclusion.h" namespace base { namespace win { @@ -54,7 +55,9 @@ class ScopedCoMem { T* get() const { return mem_ptr_; } private: - T* mem_ptr_; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #addr-of, #union + RAW_PTR_EXCLUSION T* mem_ptr_; }; } // namespace win diff --git a/base/win/scoped_safearray.h b/base/win/scoped_safearray.h index a7303fee7..d83fdf998 100644 --- a/base/win/scoped_safearray.h +++ b/base/win/scoped_safearray.h @@ -9,6 +9,7 @@ #include "base/base_export.h" #include "base/check_op.h" +#include "base/memory/raw_ptr_exclusion.h" #include "base/win/variant_conversions.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -106,7 +107,9 @@ class BASE_EXPORT ScopedSafearray { array_size_ = 0U; } - SAFEARRAY* safearray_ = nullptr; + // This field is not a raw_ptr<> because it was filtered by the rewriter + // for: #union + RAW_PTR_EXCLUSION SAFEARRAY* safearray_ = nullptr; VARTYPE vartype_ = VT_EMPTY; pointer array_ = nullptr; size_t array_size_ = 0U; @@ -218,7 +221,9 @@ class BASE_EXPORT ScopedSafearray { bool operator!=(const ScopedSafearray& safearray2) const = delete; private: - SAFEARRAY* safearray_; + // This field is not a raw_ptr<> because it was filtered by the rewriter for: + // #addr-of + RAW_PTR_EXCLUSION SAFEARRAY* safearray_; }; } // namespace win |