chromium/components/crash/core/common/crash_key.h

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_H_
#define COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_H_

#include <stdint.h>

#include <string>
#include <string_view>

#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "build/build_config.h"
#include "components/crash/core/common/crash_buildflags.h"
#include "components/crash/core/common/crash_export.h"

// The crash key interface exposed by this file is the same as the Crashpad
// Annotation interface. Because not all platforms use Crashpad yet, a
// source-compatible interface is provided on top of the older Breakpad
// storage mechanism.
//
// See https://cs.chromium.org/chromium/src/docs/debugging_with_crash_keys.md
// for more information on using this.
#if BUILDFLAG(USE_CRASHPAD_ANNOTATION)
#include "third_party/crashpad/crashpad/client/annotation.h"  // nogncheck
#endif

namespace base {
namespace debug {
class StackTrace;
}  // namespace debug
}  // namespace base

namespace crash_reporter {

// A CrashKeyString stores a name-value pair that will be recorded within a
// crash report.
//
// The crash key name must be a constant string expression, and the value
// should be unique and identifying. The maximum size for the value is
// specified as the template argument, and values greater than this are
// truncated. When specifying a value size, space should be left for the
// `NUL` byte. Crash keys should be declared with static storage duration.
//
// Examples:
// \code
//    // This crash key is only set in one function:
//    void DidNavigate(const GURL& gurl) {
//      static crash_reporter::CrashKeyString<256> url_key("url");
//      url_key.Set(gurl.ToString());
//    }
//
//    // This crash key can be set/cleared across different functions:
//    namespace {
//    crash_reporter::CrashKeyString<32> g_operation_id("operation-req-id");
//    }
//
//    void OnStartingOperation(const std::string& request_id) {
//      g_operation_id.Set(request_id);
//    }
//
//    void OnEndingOperation() {
//      g_operation_id.Clear()
//    }
// \endcode
#if BUILDFLAG(USE_CRASHPAD_ANNOTATION)

CrashKeyString;

#else  // Crashpad-compatible crash key interface:

class CrashKeyBreakpadTest;

namespace internal {

constexpr size_t kCrashKeyStorageKeySize = 40;
constexpr size_t kCrashKeyStorageNumEntries = 200;
constexpr size_t kCrashKeyStorageValueSize = 128;

// Base implementation of a CrashKeyString for non-Crashpad clients. A separate
// base class is used to avoid inlining complex logic into the public template
// API.
class CRASH_KEY_EXPORT CrashKeyStringImpl {
 public:
  constexpr explicit CrashKeyStringImpl(const char name[],
                                        size_t* index_array,
                                        size_t index_array_count)
      : name_(name),
        index_array_(index_array),
        index_array_count_(index_array_count) {}

  CrashKeyStringImpl(const CrashKeyStringImpl&) = delete;
  CrashKeyStringImpl& operator=(const CrashKeyStringImpl&) = delete;

  void Set(std::string_view value);
  void Clear();

  bool is_set() const;

 private:
  friend class crash_reporter::CrashKeyBreakpadTest;

  // The name of the crash key.
  const char* const name_;

  // If the crash key is set, this is the index into the storage that can be
  // used to set/clear the key without requiring a linear scan of the storage
  // table. This will be |num_entries| if unset.
  // RAW_PTR_EXCLUSION: #global-scope
  RAW_PTR_EXCLUSION size_t* index_array_;
  size_t index_array_count_;
};

// This type creates a C array that is initialized with a specific default
// value, rather than the standard zero-initialized default.
template <typename T,
          size_t TotalSize,
          T DefaultValue,
          size_t Count,
          T... Values>
struct InitializedArrayImpl {
  using Type = typename InitializedArrayImpl<T,
                                             TotalSize,
                                             DefaultValue,
                                             Count - 1,
                                             DefaultValue,
                                             Values...>::Type;
};

template <typename T, size_t TotalSize, T DefaultValue, T... Values>
struct InitializedArrayImpl<T, TotalSize, DefaultValue, 0, Values...> {
  using Type = InitializedArrayImpl<T, TotalSize, DefaultValue, 0, Values...>;
  T data[TotalSize]{Values...};
};

template <typename T, size_t ArraySize, T DefaultValue>
using InitializedArray =
    typename InitializedArrayImpl<T, ArraySize, DefaultValue, ArraySize>::Type;

}  // namespace internal

template <uint32_t MaxLength>
class CrashKeyStringBreakpad : public internal::CrashKeyStringImpl {
 public:
  constexpr static size_t chunk_count =
      (MaxLength / internal::kCrashKeyStorageValueSize) + 1;

  // A constructor tag that can be used to initialize a C array of crash keys.
  enum class Tag { kArray };

  constexpr explicit CrashKeyStringBreakpad(const char name[])
      : internal::CrashKeyStringImpl(name, indexes_.data, chunk_count) {}

  constexpr CrashKeyStringBreakpad(const char name[], Tag tag)
      : CrashKeyStringBreakpad(name) {}

  CrashKeyStringBreakpad(const CrashKeyStringBreakpad&) = delete;
  CrashKeyStringBreakpad& operator=(const CrashKeyStringBreakpad&) = delete;

 private:
  // Indexes into the TransitionalCrashKeyStorage for when a value is set.
  // See the comment in CrashKeyStringImpl for details.
  // An unset index in the storage is represented by a sentinel value, which
  // is the total number of entries. This will initialize the array with
  // that sentinel value as a compile-time expression.
  internal::InitializedArray<size_t,
                             chunk_count,
                             internal::kCrashKeyStorageNumEntries>
      indexes_;
};

template <uint32_t MaxLength>
using CrashKeyString = CrashKeyStringBreakpad<MaxLength>;

#endif  // BUILDFLAG(USE_CRASHPAD_ANNOTATION)

// This scoper clears the specified annotation's value when it goes out of
// scope.
//
// Example:
//    void DoSomething(const std::string& data) {
//      static crash_reporter::CrashKeyString<32> crash_key("DoSomething-data");
//      crash_reporter::ScopedCrashKeyString auto_clear(&crash_key, data);
//
//      DoSomethignImpl(data);
//    }
class [[nodiscard]] ScopedCrashKeyString {};

namespace internal {
// Formats a stack trace into a string whose length will not exceed
// |max_length|. This function ensures no addresses are truncated when
// being formatted.
CRASH_KEY_EXPORT std::string FormatStackTrace(
    const base::debug::StackTrace& trace,
    size_t max_length);
}  // namespace internal

// Formats a base::debug::StackTrace as a string of space-separated hexadecimal
// numbers and stores it in a CrashKeyString.
// TODO(rsesek): When all clients use Crashpad, traces should become a first-
// class Annotation type rather than being forced through string conversion.
template <uint32_t Size>
void SetCrashKeyStringToStackTrace(CrashKeyString<Size>* key,
                                   const base::debug::StackTrace& trace) {}

// Initializes the crash key subsystem if it is required. Calling this multiple
// times is safe (though not thread-safe) and will not result in data loss from
// crash keys set prior to the last initialization.
CRASH_KEY_EXPORT void InitializeCrashKeys();

#if defined(UNIT_TEST) || defined(CRASH_CORE_COMMON_IMPLEMENTATION)
// Returns a value for the crash key named |key_name|. For Crashpad-based
// clients, this returns the first instance found of the name. On Breakpad
// clients, oversized crash key values (those longer than
// |kCrashKeyStorageValueSize| - 1) are stored in chunks and must be retrieved
// piecewise, using syntax <key name>__1, <key name>__2, etc.
// Note: In a component build, this will only retrieve crash keys for the
// current component.
CRASH_KEY_EXPORT std::string GetCrashKeyValue(const std::string& key_name);

// Initializes the crash key subsystem with testing configuration if it is
// required.
CRASH_KEY_EXPORT void InitializeCrashKeysForTesting();

// Resets crash key state and, depending on the platform, de-initializes
// the system.
// WARNING: this does not work on Breakpad, which is used by Chrome on Linux
// (crbug.com/1041106).
CRASH_KEY_EXPORT void ResetCrashKeysForTesting();
#endif

}  // namespace crash_reporter

#endif  // COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_H_