// Copyright (c) 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/trace_event/trace_event_argument.h" #include #include #include "base/bits.h" #include "base/containers/circular_deque.h" #include "base/json/string_escape.h" #include "base/memory/ptr_util.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event_impl.h" #include "base/trace_event/trace_event_memory_overhead.h" #include "base/values.h" namespace base { namespace trace_event { namespace { const char kTypeStartDict = '{'; const char kTypeEndDict = '}'; const char kTypeStartArray = '['; const char kTypeEndArray = ']'; const char kTypeBool = 'b'; const char kTypeInt = 'i'; const char kTypeDouble = 'd'; const char kTypeString = 's'; const char kTypeCStr = '*'; // only used for key names #ifndef NDEBUG const bool kStackTypeDict = false; const bool kStackTypeArray = true; #define DCHECK_CURRENT_CONTAINER_IS(x) DCHECK_EQ(x, nesting_stack_.back()) #define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) DCHECK_EQ(x, nesting_stack_.size()) #define DEBUG_PUSH_CONTAINER(x) nesting_stack_.push_back(x) #define DEBUG_POP_CONTAINER() nesting_stack_.pop_back() #else #define DCHECK_CURRENT_CONTAINER_IS(x) do {} while (0) #define DCHECK_CONTAINER_STACK_DEPTH_EQ(x) do {} while (0) #define DEBUG_PUSH_CONTAINER(x) do {} while (0) #define DEBUG_POP_CONTAINER() do {} while (0) #endif inline void WriteKeyNameAsRawPtr(Pickle& pickle, const char* ptr) { pickle.WriteBytes(&kTypeCStr, 1); pickle.WriteUInt64(static_cast(reinterpret_cast(ptr))); } inline void WriteKeyNameWithCopy(Pickle& pickle, base::StringPiece str) { pickle.WriteBytes(&kTypeString, 1); pickle.WriteString(str); } std::string ReadKeyName(PickleIterator& pickle_iterator) { const char* type = nullptr; bool res = pickle_iterator.ReadBytes(&type, 1); std::string key_name; if (res && *type == kTypeCStr) { uint64_t ptr_value = 0; res = pickle_iterator.ReadUInt64(&ptr_value); key_name = reinterpret_cast(static_cast(ptr_value)); } else if (res && *type == kTypeString) { res = pickle_iterator.ReadString(&key_name); } DCHECK(res); return key_name; } } // namespace TracedValue::TracedValue() : TracedValue(0) { } TracedValue::TracedValue(size_t capacity) { DEBUG_PUSH_CONTAINER(kStackTypeDict); if (capacity) pickle_.Reserve(capacity); } TracedValue::~TracedValue() { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); DEBUG_POP_CONTAINER(); DCHECK_CONTAINER_STACK_DEPTH_EQ(0u); } void TracedValue::SetInteger(const char* name, int value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeInt, 1); pickle_.WriteInt(value); WriteKeyNameAsRawPtr(pickle_, name); } void TracedValue::SetIntegerWithCopiedName(base::StringPiece name, int value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeInt, 1); pickle_.WriteInt(value); WriteKeyNameWithCopy(pickle_, name); } void TracedValue::SetDouble(const char* name, double value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeDouble, 1); pickle_.WriteDouble(value); WriteKeyNameAsRawPtr(pickle_, name); } void TracedValue::SetDoubleWithCopiedName(base::StringPiece name, double value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeDouble, 1); pickle_.WriteDouble(value); WriteKeyNameWithCopy(pickle_, name); } void TracedValue::SetBoolean(const char* name, bool value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeBool, 1); pickle_.WriteBool(value); WriteKeyNameAsRawPtr(pickle_, name); } void TracedValue::SetBooleanWithCopiedName(base::StringPiece name, bool value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeBool, 1); pickle_.WriteBool(value); WriteKeyNameWithCopy(pickle_, name); } void TracedValue::SetString(const char* name, base::StringPiece value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeString, 1); pickle_.WriteString(value); WriteKeyNameAsRawPtr(pickle_, name); } void TracedValue::SetStringWithCopiedName(base::StringPiece name, base::StringPiece value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); pickle_.WriteBytes(&kTypeString, 1); pickle_.WriteString(value); WriteKeyNameWithCopy(pickle_, name); } void TracedValue::SetValue(const char* name, const TracedValue& value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); BeginDictionary(name); pickle_.WriteBytes(value.pickle_.payload(), static_cast(value.pickle_.payload_size())); EndDictionary(); } void TracedValue::SetValueWithCopiedName(base::StringPiece name, const TracedValue& value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); BeginDictionaryWithCopiedName(name); pickle_.WriteBytes(value.pickle_.payload(), static_cast(value.pickle_.payload_size())); EndDictionary(); } void TracedValue::BeginDictionary(const char* name) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); DEBUG_PUSH_CONTAINER(kStackTypeDict); pickle_.WriteBytes(&kTypeStartDict, 1); WriteKeyNameAsRawPtr(pickle_, name); } void TracedValue::BeginDictionaryWithCopiedName(base::StringPiece name) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); DEBUG_PUSH_CONTAINER(kStackTypeDict); pickle_.WriteBytes(&kTypeStartDict, 1); WriteKeyNameWithCopy(pickle_, name); } void TracedValue::BeginArray(const char* name) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); DEBUG_PUSH_CONTAINER(kStackTypeArray); pickle_.WriteBytes(&kTypeStartArray, 1); WriteKeyNameAsRawPtr(pickle_, name); } void TracedValue::BeginArrayWithCopiedName(base::StringPiece name) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); DEBUG_PUSH_CONTAINER(kStackTypeArray); pickle_.WriteBytes(&kTypeStartArray, 1); WriteKeyNameWithCopy(pickle_, name); } void TracedValue::EndDictionary() { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); DEBUG_POP_CONTAINER(); pickle_.WriteBytes(&kTypeEndDict, 1); } void TracedValue::AppendInteger(int value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); pickle_.WriteBytes(&kTypeInt, 1); pickle_.WriteInt(value); } void TracedValue::AppendDouble(double value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); pickle_.WriteBytes(&kTypeDouble, 1); pickle_.WriteDouble(value); } void TracedValue::AppendBoolean(bool value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); pickle_.WriteBytes(&kTypeBool, 1); pickle_.WriteBool(value); } void TracedValue::AppendString(base::StringPiece value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); pickle_.WriteBytes(&kTypeString, 1); pickle_.WriteString(value); } void TracedValue::BeginArray() { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); DEBUG_PUSH_CONTAINER(kStackTypeArray); pickle_.WriteBytes(&kTypeStartArray, 1); } void TracedValue::BeginDictionary() { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); DEBUG_PUSH_CONTAINER(kStackTypeDict); pickle_.WriteBytes(&kTypeStartDict, 1); } void TracedValue::EndArray() { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); DEBUG_POP_CONTAINER(); pickle_.WriteBytes(&kTypeEndArray, 1); } void TracedValue::SetValue(const char* name, std::unique_ptr value) { SetBaseValueWithCopiedName(name, *value); } void TracedValue::SetBaseValueWithCopiedName(base::StringPiece name, const base::Value& value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); switch (value.type()) { case base::Value::Type::NONE: case base::Value::Type::BINARY: NOTREACHED(); break; case base::Value::Type::BOOLEAN: { bool bool_value; value.GetAsBoolean(&bool_value); SetBooleanWithCopiedName(name, bool_value); } break; case base::Value::Type::INTEGER: { int int_value; value.GetAsInteger(&int_value); SetIntegerWithCopiedName(name, int_value); } break; case base::Value::Type::DOUBLE: { double double_value; value.GetAsDouble(&double_value); SetDoubleWithCopiedName(name, double_value); } break; case base::Value::Type::STRING: { const Value* string_value; value.GetAsString(&string_value); SetStringWithCopiedName(name, string_value->GetString()); } break; case base::Value::Type::DICTIONARY: { const DictionaryValue* dict_value; value.GetAsDictionary(&dict_value); BeginDictionaryWithCopiedName(name); for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd(); it.Advance()) { SetBaseValueWithCopiedName(it.key(), it.value()); } EndDictionary(); } break; case base::Value::Type::LIST: { const ListValue* list_value; value.GetAsList(&list_value); BeginArrayWithCopiedName(name); for (const auto& base_value : *list_value) AppendBaseValue(base_value); EndArray(); } break; } } void TracedValue::AppendBaseValue(const base::Value& value) { DCHECK_CURRENT_CONTAINER_IS(kStackTypeArray); switch (value.type()) { case base::Value::Type::NONE: case base::Value::Type::BINARY: NOTREACHED(); break; case base::Value::Type::BOOLEAN: { bool bool_value; value.GetAsBoolean(&bool_value); AppendBoolean(bool_value); } break; case base::Value::Type::INTEGER: { int int_value; value.GetAsInteger(&int_value); AppendInteger(int_value); } break; case base::Value::Type::DOUBLE: { double double_value; value.GetAsDouble(&double_value); AppendDouble(double_value); } break; case base::Value::Type::STRING: { const Value* string_value; value.GetAsString(&string_value); AppendString(string_value->GetString()); } break; case base::Value::Type::DICTIONARY: { const DictionaryValue* dict_value; value.GetAsDictionary(&dict_value); BeginDictionary(); for (DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd(); it.Advance()) { SetBaseValueWithCopiedName(it.key(), it.value()); } EndDictionary(); } break; case base::Value::Type::LIST: { const ListValue* list_value; value.GetAsList(&list_value); BeginArray(); for (const auto& base_value : *list_value) AppendBaseValue(base_value); EndArray(); } break; } } std::unique_ptr TracedValue::ToBaseValue() const { base::Value root(base::Value::Type::DICTIONARY); Value* cur_dict = &root; Value* cur_list = nullptr; std::vector stack; PickleIterator it(pickle_); const char* type; while (it.ReadBytes(&type, 1)) { DCHECK((cur_dict && !cur_list) || (cur_list && !cur_dict)); switch (*type) { case kTypeStartDict: { base::Value new_dict(base::Value::Type::DICTIONARY); if (cur_dict) { stack.push_back(cur_dict); cur_dict = cur_dict->SetKey(ReadKeyName(it), std::move(new_dict)); } else { cur_list->GetList().push_back(std::move(new_dict)); // |new_dict| is invalidated at this point, so |cur_dict| needs to be // reset. cur_dict = &cur_list->GetList().back(); stack.push_back(cur_list); cur_list = nullptr; } } break; case kTypeEndArray: case kTypeEndDict: { if (stack.back()->is_dict()) { cur_dict = stack.back(); cur_list = nullptr; } else if (stack.back()->is_list()) { cur_list = stack.back(); cur_dict = nullptr; } stack.pop_back(); } break; case kTypeStartArray: { base::Value new_list(base::Value::Type::LIST); if (cur_dict) { stack.push_back(cur_dict); cur_list = cur_dict->SetKey(ReadKeyName(it), std::move(new_list)); cur_dict = nullptr; } else { cur_list->GetList().push_back(std::move(new_list)); stack.push_back(cur_list); // |cur_list| is invalidated at this point by the Append, so it needs // to be reset. cur_list = &cur_list->GetList().back(); } } break; case kTypeBool: { bool value; CHECK(it.ReadBool(&value)); base::Value new_bool(value); if (cur_dict) { cur_dict->SetKey(ReadKeyName(it), std::move(new_bool)); } else { cur_list->GetList().push_back(std::move(new_bool)); } } break; case kTypeInt: { int value; CHECK(it.ReadInt(&value)); base::Value new_int(value); if (cur_dict) { cur_dict->SetKey(ReadKeyName(it), std::move(new_int)); } else { cur_list->GetList().push_back(std::move(new_int)); } } break; case kTypeDouble: { double value; CHECK(it.ReadDouble(&value)); base::Value new_double(value); if (cur_dict) { cur_dict->SetKey(ReadKeyName(it), std::move(new_double)); } else { cur_list->GetList().push_back(std::move(new_double)); } } break; case kTypeString: { std::string value; CHECK(it.ReadString(&value)); base::Value new_str(std::move(value)); if (cur_dict) { cur_dict->SetKey(ReadKeyName(it), std::move(new_str)); } else { cur_list->GetList().push_back(std::move(new_str)); } } break; default: NOTREACHED(); } } DCHECK(stack.empty()); return base::Value::ToUniquePtrValue(std::move(root)); } void TracedValue::AppendAsTraceFormat(std::string* out) const { DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict); DCHECK_CONTAINER_STACK_DEPTH_EQ(1u); struct State { enum Type { kTypeDict, kTypeArray }; Type type; bool needs_comma; }; auto maybe_append_key_name = [](State current_state, PickleIterator* it, std::string* out) { if (current_state.type == State::kTypeDict) { EscapeJSONString(ReadKeyName(*it), true, out); out->append(":"); } }; base::circular_deque state_stack; out->append("{"); state_stack.push_back({State::kTypeDict}); PickleIterator it(pickle_); for (const char* type; it.ReadBytes(&type, 1);) { switch (*type) { case kTypeEndDict: out->append("}"); state_stack.pop_back(); continue; case kTypeEndArray: out->append("]"); state_stack.pop_back(); continue; } // Use an index so it will stay valid across resizes. size_t current_state_index = state_stack.size() - 1; if (state_stack[current_state_index].needs_comma) out->append(","); switch (*type) { case kTypeStartDict: { maybe_append_key_name(state_stack[current_state_index], &it, out); out->append("{"); state_stack.push_back({State::kTypeDict}); break; } case kTypeStartArray: { maybe_append_key_name(state_stack[current_state_index], &it, out); out->append("["); state_stack.push_back({State::kTypeArray}); break; } case kTypeBool: { TraceEvent::TraceValue json_value; CHECK(it.ReadBool(&json_value.as_bool)); maybe_append_key_name(state_stack[current_state_index], &it, out); TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_BOOL, json_value, out); break; } case kTypeInt: { int value; CHECK(it.ReadInt(&value)); maybe_append_key_name(state_stack[current_state_index], &it, out); TraceEvent::TraceValue json_value; json_value.as_int = value; TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_INT, json_value, out); break; } case kTypeDouble: { TraceEvent::TraceValue json_value; CHECK(it.ReadDouble(&json_value.as_double)); maybe_append_key_name(state_stack[current_state_index], &it, out); TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_DOUBLE, json_value, out); break; } case kTypeString: { std::string value; CHECK(it.ReadString(&value)); maybe_append_key_name(state_stack[current_state_index], &it, out); TraceEvent::TraceValue json_value; json_value.as_string = value.c_str(); TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_STRING, json_value, out); break; } default: NOTREACHED(); } state_stack[current_state_index].needs_comma = true; } out->append("}"); state_stack.pop_back(); DCHECK(state_stack.empty()); } void TracedValue::EstimateTraceMemoryOverhead( TraceEventMemoryOverhead* overhead) { overhead->Add(TraceEventMemoryOverhead::kTracedValue, /* allocated size */ pickle_.GetTotalAllocatedSize(), /* resident size */ pickle_.size()); } } // namespace trace_event } // namespace base