// 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(); DescriptorArray descs = map.instance_descriptors(); for (InternalIndex i : map.IterateOwnDescriptors()) { 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.IsJSPrimitiveWrapper()) { Add(" value(): %o\n", JSPrimitiveWrapper::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