diff options
Diffstat (limited to 'deps/v8/src/strings/string-stream.cc')
-rw-r--r-- | deps/v8/src/strings/string-stream.cc | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/deps/v8/src/strings/string-stream.cc b/deps/v8/src/strings/string-stream.cc new file mode 100644 index 0000000000..db1891949e --- /dev/null +++ b/deps/v8/src/strings/string-stream.cc @@ -0,0 +1,476 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/strings/string-stream.h" + +#include <memory> + +#include "src/handles/handles-inl.h" +#include "src/logging/log.h" +#include "src/objects/js-array-inl.h" +#include "src/objects/objects-inl.h" +#include "src/objects/prototype.h" +#include "src/utils/vector.h" + +namespace v8 { +namespace internal { + +static const int kMentionedObjectCacheMaxSize = 256; + +char* HeapStringAllocator::allocate(unsigned bytes) { + space_ = NewArray<char>(bytes); + return space_; +} + +char* FixedStringAllocator::allocate(unsigned bytes) { + CHECK_LE(bytes, length_); + return buffer_; +} + +char* FixedStringAllocator::grow(unsigned* old) { + *old = length_; + return buffer_; +} + +bool StringStream::Put(char c) { + if (full()) return false; + DCHECK(length_ < capacity_); + // Since the trailing '\0' is not accounted for in length_ fullness is + // indicated by a difference of 1 between length_ and capacity_. Thus when + // reaching a difference of 2 we need to grow the buffer. + if (length_ == capacity_ - 2) { + unsigned new_capacity = capacity_; + char* new_buffer = allocator_->grow(&new_capacity); + if (new_capacity > capacity_) { + capacity_ = new_capacity; + buffer_ = new_buffer; + } else { + // Reached the end of the available buffer. + DCHECK_GE(capacity_, 5); + length_ = capacity_ - 1; // Indicate fullness of the stream. + buffer_[length_ - 4] = '.'; + buffer_[length_ - 3] = '.'; + buffer_[length_ - 2] = '.'; + buffer_[length_ - 1] = '\n'; + buffer_[length_] = '\0'; + return false; + } + } + buffer_[length_] = c; + buffer_[length_ + 1] = '\0'; + length_++; + return true; +} + +// A control character is one that configures a format element. For +// instance, in %.5s, .5 are control characters. +static bool IsControlChar(char c) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case '-': + return true; + default: + return false; + } +} + +void StringStream::Add(Vector<const char> format, Vector<FmtElm> elms) { + // If we already ran out of space then return immediately. + if (full()) return; + int offset = 0; + int elm = 0; + while (offset < format.length()) { + if (format[offset] != '%' || elm == elms.length()) { + Put(format[offset]); + offset++; + continue; + } + // Read this formatting directive into a temporary buffer + EmbeddedVector<char, 24> temp; + int format_length = 0; + // Skip over the whole control character sequence until the + // format element type + temp[format_length++] = format[offset++]; + while (offset < format.length() && IsControlChar(format[offset])) + temp[format_length++] = format[offset++]; + if (offset >= format.length()) return; + char type = format[offset]; + temp[format_length++] = type; + temp[format_length] = '\0'; + offset++; + FmtElm current = elms[elm++]; + switch (type) { + case 's': { + DCHECK_EQ(FmtElm::C_STR, current.type_); + const char* value = current.data_.u_c_str_; + Add(value); + break; + } + case 'w': { + DCHECK_EQ(FmtElm::LC_STR, current.type_); + Vector<const uc16> value = *current.data_.u_lc_str_; + for (int i = 0; i < value.length(); i++) + Put(static_cast<char>(value[i])); + break; + } + case 'o': { + DCHECK_EQ(FmtElm::OBJ, current.type_); + Object obj(current.data_.u_obj_); + PrintObject(obj); + break; + } + case 'k': { + DCHECK_EQ(FmtElm::INT, current.type_); + int value = current.data_.u_int_; + if (0x20 <= value && value <= 0x7F) { + Put(value); + } else if (value <= 0xFF) { + Add("\\x%02x", value); + } else { + Add("\\u%04x", value); + } + break; + } + case 'i': + case 'd': + case 'u': + case 'x': + case 'c': + case 'X': { + int value = current.data_.u_int_; + EmbeddedVector<char, 24> formatted; + int length = SNPrintF(formatted, temp.begin(), value); + Add(Vector<const char>(formatted.begin(), length)); + break; + } + case 'f': + case 'g': + case 'G': + case 'e': + case 'E': { + double value = current.data_.u_double_; + int inf = std::isinf(value); + if (inf == -1) { + Add("-inf"); + } else if (inf == 1) { + Add("inf"); + } else if (std::isnan(value)) { + Add("nan"); + } else { + EmbeddedVector<char, 28> formatted; + SNPrintF(formatted, temp.begin(), value); + Add(formatted.begin()); + } + break; + } + case 'p': { + void* value = current.data_.u_pointer_; + EmbeddedVector<char, 20> formatted; + SNPrintF(formatted, temp.begin(), value); + Add(formatted.begin()); + break; + } + default: + UNREACHABLE(); + } + } + + // Verify that the buffer is 0-terminated + DCHECK_EQ(buffer_[length_], '\0'); +} + +void StringStream::PrintObject(Object o) { + o.ShortPrint(this); + if (o.IsString()) { + if (String::cast(o).length() <= String::kMaxShortPrintLength) { + return; + } + } else if (o.IsNumber() || o.IsOddball()) { + return; + } + if (o.IsHeapObject() && object_print_mode_ == kPrintObjectVerbose) { + // TODO(delphick): Consider whether we can get the isolate without using + // TLS. + Isolate* isolate = Isolate::Current(); + DebugObjectCache* debug_object_cache = + isolate->string_stream_debug_object_cache(); + for (size_t i = 0; i < debug_object_cache->size(); i++) { + if (*(*debug_object_cache)[i] == o) { + Add("#%d#", static_cast<int>(i)); + return; + } + } + if (debug_object_cache->size() < kMentionedObjectCacheMaxSize) { + Add("#%d#", static_cast<int>(debug_object_cache->size())); + debug_object_cache->push_back(handle(HeapObject::cast(o), isolate)); + } else { + Add("@%p", o); + } + } +} + +std::unique_ptr<char[]> StringStream::ToCString() const { + char* str = NewArray<char>(length_ + 1); + MemCopy(str, buffer_, length_); + str[length_] = '\0'; + return std::unique_ptr<char[]>(str); +} + +void StringStream::Log(Isolate* isolate) { + LOG(isolate, StringEvent("StackDump", buffer_)); +} + +void StringStream::OutputToFile(FILE* out) { + // Dump the output to stdout, but make sure to break it up into + // manageable chunks to avoid losing parts of the output in the OS + // printing code. This is a problem on Windows in particular; see + // the VPrint() function implementations in platform-win32.cc. + unsigned position = 0; + for (unsigned next; (next = position + 2048) < length_; position = next) { + char save = buffer_[next]; + buffer_[next] = '\0'; + internal::PrintF(out, "%s", &buffer_[position]); + buffer_[next] = save; + } + internal::PrintF(out, "%s", &buffer_[position]); +} + +Handle<String> StringStream::ToString(Isolate* isolate) { + return isolate->factory() + ->NewStringFromUtf8(Vector<const char>(buffer_, length_)) + .ToHandleChecked(); +} + +void StringStream::ClearMentionedObjectCache(Isolate* isolate) { + isolate->set_string_stream_current_security_token(Object()); + if (isolate->string_stream_debug_object_cache() == nullptr) { + isolate->set_string_stream_debug_object_cache(new DebugObjectCache()); + } + isolate->string_stream_debug_object_cache()->clear(); +} + +#ifdef DEBUG +bool StringStream::IsMentionedObjectCacheClear(Isolate* isolate) { + return object_print_mode_ == kPrintObjectConcise || + isolate->string_stream_debug_object_cache()->size() == 0; +} +#endif + +bool StringStream::Put(String str) { return Put(str, 0, str.length()); } + +bool StringStream::Put(String str, int start, int end) { + StringCharacterStream stream(str, start); + for (int i = start; i < end && stream.HasMore(); i++) { + uint16_t c = stream.GetNext(); + if (c >= 127 || c < 32) { + c = '?'; + } + if (!Put(static_cast<char>(c))) { + return false; // Output was truncated. + } + } + return true; +} + +void StringStream::PrintName(Object name) { + if (name.IsString()) { + String str = String::cast(name); + if (str.length() > 0) { + Put(str); + } else { + Add("/* anonymous */"); + } + } else { + Add("%o", name); + } +} + +void StringStream::PrintUsingMap(JSObject js_object) { + Map map = js_object.map(); + int real_size = map.NumberOfOwnDescriptors(); + DescriptorArray descs = map.instance_descriptors(); + for (int i = 0; i < real_size; i++) { + PropertyDetails details = descs.GetDetails(i); + if (details.location() == kField) { + DCHECK_EQ(kData, details.kind()); + Object key = descs.GetKey(i); + if (key.IsString() || key.IsNumber()) { + int len = 3; + if (key.IsString()) { + len = String::cast(key).length(); + } + for (; len < 18; len++) Put(' '); + if (key.IsString()) { + Put(String::cast(key)); + } else { + key.ShortPrint(); + } + Add(": "); + FieldIndex index = FieldIndex::ForDescriptor(map, i); + if (js_object.IsUnboxedDoubleField(index)) { + double value = js_object.RawFastDoublePropertyAt(index); + Add("<unboxed double> %.16g\n", FmtElm(value)); + } else { + Object value = js_object.RawFastPropertyAt(index); + Add("%o\n", value); + } + } + } + } +} + +void StringStream::PrintFixedArray(FixedArray array, unsigned int limit) { + ReadOnlyRoots roots = array.GetReadOnlyRoots(); + for (unsigned int i = 0; i < 10 && i < limit; i++) { + Object element = array.get(i); + if (element.IsTheHole(roots)) continue; + for (int len = 1; len < 18; len++) { + Put(' '); + } + Add("%d: %o\n", i, array.get(i)); + } + if (limit >= 10) { + Add(" ...\n"); + } +} + +void StringStream::PrintByteArray(ByteArray byte_array) { + unsigned int limit = byte_array.length(); + for (unsigned int i = 0; i < 10 && i < limit; i++) { + byte b = byte_array.get(i); + Add(" %d: %3d 0x%02x", i, b, b); + if (b >= ' ' && b <= '~') { + Add(" '%c'", b); + } else if (b == '\n') { + Add(" '\n'"); + } else if (b == '\r') { + Add(" '\r'"); + } else if (b >= 1 && b <= 26) { + Add(" ^%c", b + 'A' - 1); + } + Add("\n"); + } + if (limit >= 10) { + Add(" ...\n"); + } +} + +void StringStream::PrintMentionedObjectCache(Isolate* isolate) { + if (object_print_mode_ == kPrintObjectConcise) return; + DebugObjectCache* debug_object_cache = + isolate->string_stream_debug_object_cache(); + Add("==== Key ============================================\n\n"); + for (size_t i = 0; i < debug_object_cache->size(); i++) { + HeapObject printee = *(*debug_object_cache)[i]; + Add(" #%d# %p: ", static_cast<int>(i), + reinterpret_cast<void*>(printee.ptr())); + printee.ShortPrint(this); + Add("\n"); + if (printee.IsJSObject()) { + if (printee.IsJSValue()) { + Add(" value(): %o\n", JSValue::cast(printee).value()); + } + PrintUsingMap(JSObject::cast(printee)); + if (printee.IsJSArray()) { + JSArray array = JSArray::cast(printee); + if (array.HasObjectElements()) { + unsigned int limit = FixedArray::cast(array.elements()).length(); + unsigned int length = + static_cast<uint32_t>(JSArray::cast(array).length().Number()); + if (length < limit) limit = length; + PrintFixedArray(FixedArray::cast(array.elements()), limit); + } + } + } else if (printee.IsByteArray()) { + PrintByteArray(ByteArray::cast(printee)); + } else if (printee.IsFixedArray()) { + unsigned int limit = FixedArray::cast(printee).length(); + PrintFixedArray(FixedArray::cast(printee), limit); + } + } +} + +void StringStream::PrintSecurityTokenIfChanged(JSFunction fun) { + Object token = fun.native_context().security_token(); + Isolate* isolate = fun.GetIsolate(); + if (token != isolate->string_stream_current_security_token()) { + Add("Security context: %o\n", token); + isolate->set_string_stream_current_security_token(token); + } +} + +void StringStream::PrintFunction(JSFunction fun, Object receiver, Code* code) { + PrintPrototype(fun, receiver); + *code = fun.code(); +} + +void StringStream::PrintPrototype(JSFunction fun, Object receiver) { + Object name = fun.shared().Name(); + bool print_name = false; + Isolate* isolate = fun.GetIsolate(); + if (receiver.IsNullOrUndefined(isolate) || receiver.IsTheHole(isolate) || + receiver.IsJSProxy()) { + print_name = true; + } else if (!isolate->context().is_null()) { + if (!receiver.IsJSObject()) { + receiver = receiver.GetPrototypeChainRootMap(isolate).prototype(); + } + + for (PrototypeIterator iter(isolate, JSObject::cast(receiver), + kStartAtReceiver); + !iter.IsAtEnd(); iter.Advance()) { + if (iter.GetCurrent().IsJSProxy()) break; + Object key = iter.GetCurrent<JSObject>().SlowReverseLookup(fun); + if (!key.IsUndefined(isolate)) { + if (!name.IsString() || !key.IsString() || + !String::cast(name).Equals(String::cast(key))) { + print_name = true; + } + if (name.IsString() && String::cast(name).length() == 0) { + print_name = false; + } + name = key; + break; + } + } + } + PrintName(name); + // Also known as - if the name in the function doesn't match the name under + // which it was looked up. + if (print_name) { + Add("(aka "); + PrintName(fun.shared().Name()); + Put(')'); + } +} + +char* HeapStringAllocator::grow(unsigned* bytes) { + unsigned new_bytes = *bytes * 2; + // Check for overflow. + if (new_bytes <= *bytes) { + return space_; + } + char* new_space = NewArray<char>(new_bytes); + if (new_space == nullptr) { + return space_; + } + MemCopy(new_space, space_, *bytes); + *bytes = new_bytes; + DeleteArray(space_); + space_ = new_space; + return new_space; +} + +} // namespace internal +} // namespace v8 |