// 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/string-stream.h" #include "src/handles-inl.h" #include "src/prototype.h" namespace v8 { namespace internal { static const int kMentionedObjectCacheMaxSize = 256; char* HeapStringAllocator::allocate(unsigned bytes) { space_ = NewArray(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(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 format, Vector 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 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 value = *current.data_.u_lc_str_; for (int i = 0; i < value.length(); i++) Put(static_cast(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 formatted; int length = SNPrintF(formatted, temp.start(), value); Add(Vector(formatted.start(), 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 formatted; SNPrintF(formatted, temp.start(), value); Add(formatted.start()); } break; } case 'p': { void* value = current.data_.u_pointer_; EmbeddedVector formatted; SNPrintF(formatted, temp.start(), value); Add(formatted.start()); break; } default: UNREACHABLE(); break; } } // Verify that the buffer is 0-terminated DCHECK(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) { HeapObject* ho = HeapObject::cast(o); DebugObjectCache* debug_object_cache = ho->GetIsolate()-> string_stream_debug_object_cache(); for (int i = 0; i < debug_object_cache->length(); i++) { if ((*debug_object_cache)[i] == o) { Add("#%d#", i); return; } } if (debug_object_cache->length() < kMentionedObjectCacheMaxSize) { Add("#%d#", debug_object_cache->length()); debug_object_cache->Add(HeapObject::cast(o)); } else { Add("@%p", o); } } } void StringStream::Add(const char* format) { Add(CStrVector(format)); } void StringStream::Add(Vector format) { Add(format, Vector::empty()); } void StringStream::Add(const char* format, FmtElm arg0) { const char argc = 1; FmtElm argv[argc] = { arg0 }; Add(CStrVector(format), Vector(argv, argc)); } void StringStream::Add(const char* format, FmtElm arg0, FmtElm arg1) { const char argc = 2; FmtElm argv[argc] = { arg0, arg1 }; Add(CStrVector(format), Vector(argv, argc)); } void StringStream::Add(const char* format, FmtElm arg0, FmtElm arg1, FmtElm arg2) { const char argc = 3; FmtElm argv[argc] = { arg0, arg1, arg2 }; Add(CStrVector(format), Vector(argv, argc)); } void StringStream::Add(const char* format, FmtElm arg0, FmtElm arg1, FmtElm arg2, FmtElm arg3) { const char argc = 4; FmtElm argv[argc] = { arg0, arg1, arg2, arg3 }; Add(CStrVector(format), Vector(argv, argc)); } void StringStream::Add(const char* format, FmtElm arg0, FmtElm arg1, FmtElm arg2, FmtElm arg3, FmtElm arg4) { const char argc = 5; FmtElm argv[argc] = { arg0, arg1, arg2, arg3, arg4 }; Add(CStrVector(format), Vector(argv, argc)); } base::SmartArrayPointer StringStream::ToCString() const { char* str = NewArray(length_ + 1); MemCopy(str, buffer_, length_); str[length_] = '\0'; return base::SmartArrayPointer(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 StringStream::ToString(Isolate* isolate) { return isolate->factory()->NewStringFromUtf8( Vector(buffer_, length_)).ToHandleChecked(); } void StringStream::ClearMentionedObjectCache(Isolate* isolate) { isolate->set_string_stream_current_security_token(NULL); if (isolate->string_stream_debug_object_cache() == NULL) { isolate->set_string_stream_debug_object_cache(new DebugObjectCache(0)); } 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()->length() == 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(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(); if (!js_object->GetHeap()->Contains(map) || !map->IsHeapObject() || !map->IsMap()) { Add("\n"); return; } 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.type() == DATA) { 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(" %.16g\n", FmtElm(value)); } else { Object* value = js_object->RawFastPropertyAt(index); Add("%o\n", value); } } } } } void StringStream::PrintFixedArray(FixedArray* array, unsigned int limit) { Heap* heap = array->GetHeap(); for (unsigned int i = 0; i < 10 && i < limit; i++) { Object* element = array->get(i); if (element != heap->the_hole_value()) { 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 (int i = 0; i < debug_object_cache->length(); i++) { HeapObject* printee = (*debug_object_cache)[i]; Add(" #%d# %p: ", i, printee); 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->HasFastObjectElements()) { unsigned int limit = FixedArray::cast(array->elements())->length(); unsigned int length = static_cast(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(Object* f) { if (!f->IsHeapObject()) return; HeapObject* obj = HeapObject::cast(f); Isolate* isolate = obj->GetIsolate(); Heap* heap = isolate->heap(); if (!heap->Contains(obj)) return; Map* map = obj->map(); if (!map->IsHeapObject() || !heap->Contains(map) || !map->IsMap() || !f->IsJSFunction()) { return; } JSFunction* fun = JSFunction::cast(f); Object* perhaps_context = fun->context(); if (perhaps_context->IsHeapObject() && heap->Contains(HeapObject::cast(perhaps_context)) && perhaps_context->IsContext()) { Context* context = fun->context(); if (!heap->Contains(context)) { Add("(Function context is outside heap)\n"); return; } Object* token = context->native_context()->security_token(); if (token != isolate->string_stream_current_security_token()) { Add("Security context: %o\n", token); isolate->set_string_stream_current_security_token(token); } } else { Add("(Function context is corrupt)\n"); } } void StringStream::PrintFunction(Object* f, Object* receiver, Code** code) { if (!f->IsHeapObject()) { Add("/* warning: 'function' was not a heap object */ "); return; } Heap* heap = HeapObject::cast(f)->GetHeap(); if (!heap->Contains(HeapObject::cast(f))) { Add("/* warning: 'function' was not on the heap */ "); return; } if (!heap->Contains(HeapObject::cast(f)->map())) { Add("/* warning: function's map was not on the heap */ "); return; } if (!HeapObject::cast(f)->map()->IsMap()) { Add("/* warning: function's map was not a valid map */ "); return; } if (f->IsJSFunction()) { JSFunction* fun = JSFunction::cast(f); // Common case: on-stack function present and resolved. PrintPrototype(fun, receiver); *code = fun->code(); } else if (f->IsInternalizedString()) { // Unresolved and megamorphic calls: Instead of the function // we have the function name on the stack. PrintName(f); Add("/* unresolved */ "); } else { // Unless this is the frame of a built-in function, we should always have // the callee function or name on the stack. If we don't, we have a // problem or a change of the stack frame layout. Add("%o", f); Add("/* warning: no JSFunction object or function name found */ "); } } void StringStream::PrintPrototype(JSFunction* fun, Object* receiver) { Object* name = fun->shared()->name(); bool print_name = false; Isolate* isolate = fun->GetIsolate(); for (PrototypeIterator iter(isolate, receiver, PrototypeIterator::START_AT_RECEIVER); !iter.IsAtEnd(); iter.Advance()) { if (iter.GetCurrent()->IsJSObject()) { Object* key = iter.GetCurrent()->SlowReverseLookup(fun); if (key != isolate->heap()->undefined_value()) { 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; } } else { print_name = true; } } 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(new_bytes); if (new_space == NULL) { return space_; } MemCopy(new_space, space_, *bytes); *bytes = new_bytes; DeleteArray(space_); space_ = new_space; return new_space; } } // namespace internal } // namespace v8