1 | // Copyright 2014 the V8 project authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "src/string-stream.h" |
6 | |
7 | #include <memory> |
8 | |
9 | #include "src/handles-inl.h" |
10 | #include "src/log.h" |
11 | #include "src/objects-inl.h" |
12 | #include "src/objects/js-array-inl.h" |
13 | #include "src/prototype.h" |
14 | #include "src/vector.h" |
15 | |
16 | namespace v8 { |
17 | namespace internal { |
18 | |
19 | static const int kMentionedObjectCacheMaxSize = 256; |
20 | |
21 | char* HeapStringAllocator::allocate(unsigned bytes) { |
22 | space_ = NewArray<char>(bytes); |
23 | return space_; |
24 | } |
25 | |
26 | |
27 | char* FixedStringAllocator::allocate(unsigned bytes) { |
28 | CHECK_LE(bytes, length_); |
29 | return buffer_; |
30 | } |
31 | |
32 | |
33 | char* FixedStringAllocator::grow(unsigned* old) { |
34 | *old = length_; |
35 | return buffer_; |
36 | } |
37 | |
38 | |
39 | bool StringStream::Put(char c) { |
40 | if (full()) return false; |
41 | DCHECK(length_ < capacity_); |
42 | // Since the trailing '\0' is not accounted for in length_ fullness is |
43 | // indicated by a difference of 1 between length_ and capacity_. Thus when |
44 | // reaching a difference of 2 we need to grow the buffer. |
45 | if (length_ == capacity_ - 2) { |
46 | unsigned new_capacity = capacity_; |
47 | char* new_buffer = allocator_->grow(&new_capacity); |
48 | if (new_capacity > capacity_) { |
49 | capacity_ = new_capacity; |
50 | buffer_ = new_buffer; |
51 | } else { |
52 | // Reached the end of the available buffer. |
53 | DCHECK_GE(capacity_, 5); |
54 | length_ = capacity_ - 1; // Indicate fullness of the stream. |
55 | buffer_[length_ - 4] = '.'; |
56 | buffer_[length_ - 3] = '.'; |
57 | buffer_[length_ - 2] = '.'; |
58 | buffer_[length_ - 1] = '\n'; |
59 | buffer_[length_] = '\0'; |
60 | return false; |
61 | } |
62 | } |
63 | buffer_[length_] = c; |
64 | buffer_[length_ + 1] = '\0'; |
65 | length_++; |
66 | return true; |
67 | } |
68 | |
69 | |
70 | // A control character is one that configures a format element. For |
71 | // instance, in %.5s, .5 are control characters. |
72 | static bool IsControlChar(char c) { |
73 | switch (c) { |
74 | case '0': case '1': case '2': case '3': case '4': case '5': |
75 | case '6': case '7': case '8': case '9': case '.': case '-': |
76 | return true; |
77 | default: |
78 | return false; |
79 | } |
80 | } |
81 | |
82 | |
83 | void StringStream::Add(Vector<const char> format, Vector<FmtElm> elms) { |
84 | // If we already ran out of space then return immediately. |
85 | if (full()) return; |
86 | int offset = 0; |
87 | int elm = 0; |
88 | while (offset < format.length()) { |
89 | if (format[offset] != '%' || elm == elms.length()) { |
90 | Put(format[offset]); |
91 | offset++; |
92 | continue; |
93 | } |
94 | // Read this formatting directive into a temporary buffer |
95 | EmbeddedVector<char, 24> temp; |
96 | int format_length = 0; |
97 | // Skip over the whole control character sequence until the |
98 | // format element type |
99 | temp[format_length++] = format[offset++]; |
100 | while (offset < format.length() && IsControlChar(format[offset])) |
101 | temp[format_length++] = format[offset++]; |
102 | if (offset >= format.length()) |
103 | return; |
104 | char type = format[offset]; |
105 | temp[format_length++] = type; |
106 | temp[format_length] = '\0'; |
107 | offset++; |
108 | FmtElm current = elms[elm++]; |
109 | switch (type) { |
110 | case 's': { |
111 | DCHECK_EQ(FmtElm::C_STR, current.type_); |
112 | const char* value = current.data_.u_c_str_; |
113 | Add(value); |
114 | break; |
115 | } |
116 | case 'w': { |
117 | DCHECK_EQ(FmtElm::LC_STR, current.type_); |
118 | Vector<const uc16> value = *current.data_.u_lc_str_; |
119 | for (int i = 0; i < value.length(); i++) |
120 | Put(static_cast<char>(value[i])); |
121 | break; |
122 | } |
123 | case 'o': { |
124 | DCHECK_EQ(FmtElm::OBJ, current.type_); |
125 | Object obj(current.data_.u_obj_); |
126 | PrintObject(obj); |
127 | break; |
128 | } |
129 | case 'k': { |
130 | DCHECK_EQ(FmtElm::INT, current.type_); |
131 | int value = current.data_.u_int_; |
132 | if (0x20 <= value && value <= 0x7F) { |
133 | Put(value); |
134 | } else if (value <= 0xFF) { |
135 | Add("\\x%02x" , value); |
136 | } else { |
137 | Add("\\u%04x" , value); |
138 | } |
139 | break; |
140 | } |
141 | case 'i': case 'd': case 'u': case 'x': case 'c': case 'X': { |
142 | int value = current.data_.u_int_; |
143 | EmbeddedVector<char, 24> formatted; |
144 | int length = SNPrintF(formatted, temp.start(), value); |
145 | Add(Vector<const char>(formatted.start(), length)); |
146 | break; |
147 | } |
148 | case 'f': case 'g': case 'G': case 'e': case 'E': { |
149 | double value = current.data_.u_double_; |
150 | int inf = std::isinf(value); |
151 | if (inf == -1) { |
152 | Add("-inf" ); |
153 | } else if (inf == 1) { |
154 | Add("inf" ); |
155 | } else if (std::isnan(value)) { |
156 | Add("nan" ); |
157 | } else { |
158 | EmbeddedVector<char, 28> formatted; |
159 | SNPrintF(formatted, temp.start(), value); |
160 | Add(formatted.start()); |
161 | } |
162 | break; |
163 | } |
164 | case 'p': { |
165 | void* value = current.data_.u_pointer_; |
166 | EmbeddedVector<char, 20> formatted; |
167 | SNPrintF(formatted, temp.start(), value); |
168 | Add(formatted.start()); |
169 | break; |
170 | } |
171 | default: |
172 | UNREACHABLE(); |
173 | break; |
174 | } |
175 | } |
176 | |
177 | // Verify that the buffer is 0-terminated |
178 | DCHECK_EQ(buffer_[length_], '\0'); |
179 | } |
180 | |
181 | void StringStream::PrintObject(Object o) { |
182 | o->ShortPrint(this); |
183 | if (o->IsString()) { |
184 | if (String::cast(o)->length() <= String::kMaxShortPrintLength) { |
185 | return; |
186 | } |
187 | } else if (o->IsNumber() || o->IsOddball()) { |
188 | return; |
189 | } |
190 | if (o->IsHeapObject() && object_print_mode_ == kPrintObjectVerbose) { |
191 | // TODO(delphick): Consider whether we can get the isolate without using |
192 | // TLS. |
193 | Isolate* isolate = Isolate::Current(); |
194 | DebugObjectCache* debug_object_cache = |
195 | isolate->string_stream_debug_object_cache(); |
196 | for (size_t i = 0; i < debug_object_cache->size(); i++) { |
197 | if (*(*debug_object_cache)[i] == o) { |
198 | Add("#%d#" , static_cast<int>(i)); |
199 | return; |
200 | } |
201 | } |
202 | if (debug_object_cache->size() < kMentionedObjectCacheMaxSize) { |
203 | Add("#%d#" , static_cast<int>(debug_object_cache->size())); |
204 | debug_object_cache->push_back(handle(HeapObject::cast(o), isolate)); |
205 | } else { |
206 | Add("@%p" , o); |
207 | } |
208 | } |
209 | } |
210 | |
211 | std::unique_ptr<char[]> StringStream::ToCString() const { |
212 | char* str = NewArray<char>(length_ + 1); |
213 | MemCopy(str, buffer_, length_); |
214 | str[length_] = '\0'; |
215 | return std::unique_ptr<char[]>(str); |
216 | } |
217 | |
218 | |
219 | void StringStream::Log(Isolate* isolate) { |
220 | LOG(isolate, StringEvent("StackDump" , buffer_)); |
221 | } |
222 | |
223 | |
224 | void StringStream::OutputToFile(FILE* out) { |
225 | // Dump the output to stdout, but make sure to break it up into |
226 | // manageable chunks to avoid losing parts of the output in the OS |
227 | // printing code. This is a problem on Windows in particular; see |
228 | // the VPrint() function implementations in platform-win32.cc. |
229 | unsigned position = 0; |
230 | for (unsigned next; (next = position + 2048) < length_; position = next) { |
231 | char save = buffer_[next]; |
232 | buffer_[next] = '\0'; |
233 | internal::PrintF(out, "%s" , &buffer_[position]); |
234 | buffer_[next] = save; |
235 | } |
236 | internal::PrintF(out, "%s" , &buffer_[position]); |
237 | } |
238 | |
239 | |
240 | Handle<String> StringStream::ToString(Isolate* isolate) { |
241 | return isolate->factory()->NewStringFromUtf8( |
242 | Vector<const char>(buffer_, length_)).ToHandleChecked(); |
243 | } |
244 | |
245 | |
246 | void StringStream::ClearMentionedObjectCache(Isolate* isolate) { |
247 | isolate->set_string_stream_current_security_token(Object()); |
248 | if (isolate->string_stream_debug_object_cache() == nullptr) { |
249 | isolate->set_string_stream_debug_object_cache(new DebugObjectCache()); |
250 | } |
251 | isolate->string_stream_debug_object_cache()->clear(); |
252 | } |
253 | |
254 | |
255 | #ifdef DEBUG |
256 | bool StringStream::IsMentionedObjectCacheClear(Isolate* isolate) { |
257 | return object_print_mode_ == kPrintObjectConcise || |
258 | isolate->string_stream_debug_object_cache()->size() == 0; |
259 | } |
260 | #endif |
261 | |
262 | bool StringStream::Put(String str) { return Put(str, 0, str->length()); } |
263 | |
264 | bool StringStream::Put(String str, int start, int end) { |
265 | StringCharacterStream stream(str, start); |
266 | for (int i = start; i < end && stream.HasMore(); i++) { |
267 | uint16_t c = stream.GetNext(); |
268 | if (c >= 127 || c < 32) { |
269 | c = '?'; |
270 | } |
271 | if (!Put(static_cast<char>(c))) { |
272 | return false; // Output was truncated. |
273 | } |
274 | } |
275 | return true; |
276 | } |
277 | |
278 | void StringStream::PrintName(Object name) { |
279 | if (name->IsString()) { |
280 | String str = String::cast(name); |
281 | if (str->length() > 0) { |
282 | Put(str); |
283 | } else { |
284 | Add("/* anonymous */" ); |
285 | } |
286 | } else { |
287 | Add("%o" , name); |
288 | } |
289 | } |
290 | |
291 | void StringStream::PrintUsingMap(JSObject js_object) { |
292 | Map map = js_object->map(); |
293 | int real_size = map->NumberOfOwnDescriptors(); |
294 | DescriptorArray descs = map->instance_descriptors(); |
295 | for (int i = 0; i < real_size; i++) { |
296 | PropertyDetails details = descs->GetDetails(i); |
297 | if (details.location() == kField) { |
298 | DCHECK_EQ(kData, details.kind()); |
299 | Object key = descs->GetKey(i); |
300 | if (key->IsString() || key->IsNumber()) { |
301 | int len = 3; |
302 | if (key->IsString()) { |
303 | len = String::cast(key)->length(); |
304 | } |
305 | for (; len < 18; len++) |
306 | Put(' '); |
307 | if (key->IsString()) { |
308 | Put(String::cast(key)); |
309 | } else { |
310 | key->ShortPrint(); |
311 | } |
312 | Add(": " ); |
313 | FieldIndex index = FieldIndex::ForDescriptor(map, i); |
314 | if (js_object->IsUnboxedDoubleField(index)) { |
315 | double value = js_object->RawFastDoublePropertyAt(index); |
316 | Add("<unboxed double> %.16g\n" , FmtElm(value)); |
317 | } else { |
318 | Object value = js_object->RawFastPropertyAt(index); |
319 | Add("%o\n" , value); |
320 | } |
321 | } |
322 | } |
323 | } |
324 | } |
325 | |
326 | void StringStream::PrintFixedArray(FixedArray array, unsigned int limit) { |
327 | ReadOnlyRoots roots = array->GetReadOnlyRoots(); |
328 | for (unsigned int i = 0; i < 10 && i < limit; i++) { |
329 | Object element = array->get(i); |
330 | if (element->IsTheHole(roots)) continue; |
331 | for (int len = 1; len < 18; len++) { |
332 | Put(' '); |
333 | } |
334 | Add("%d: %o\n" , i, array->get(i)); |
335 | } |
336 | if (limit >= 10) { |
337 | Add(" ...\n" ); |
338 | } |
339 | } |
340 | |
341 | void StringStream::PrintByteArray(ByteArray byte_array) { |
342 | unsigned int limit = byte_array->length(); |
343 | for (unsigned int i = 0; i < 10 && i < limit; i++) { |
344 | byte b = byte_array->get(i); |
345 | Add(" %d: %3d 0x%02x" , i, b, b); |
346 | if (b >= ' ' && b <= '~') { |
347 | Add(" '%c'" , b); |
348 | } else if (b == '\n') { |
349 | Add(" '\n'" ); |
350 | } else if (b == '\r') { |
351 | Add(" '\r'" ); |
352 | } else if (b >= 1 && b <= 26) { |
353 | Add(" ^%c" , b + 'A' - 1); |
354 | } |
355 | Add("\n" ); |
356 | } |
357 | if (limit >= 10) { |
358 | Add(" ...\n" ); |
359 | } |
360 | } |
361 | |
362 | void StringStream::PrintMentionedObjectCache(Isolate* isolate) { |
363 | if (object_print_mode_ == kPrintObjectConcise) return; |
364 | DebugObjectCache* debug_object_cache = |
365 | isolate->string_stream_debug_object_cache(); |
366 | Add("==== Key ============================================\n\n" ); |
367 | for (size_t i = 0; i < debug_object_cache->size(); i++) { |
368 | HeapObject printee = *(*debug_object_cache)[i]; |
369 | Add(" #%d# %p: " , static_cast<int>(i), |
370 | reinterpret_cast<void*>(printee->ptr())); |
371 | printee->ShortPrint(this); |
372 | Add("\n" ); |
373 | if (printee->IsJSObject()) { |
374 | if (printee->IsJSValue()) { |
375 | Add(" value(): %o\n" , JSValue::cast(printee)->value()); |
376 | } |
377 | PrintUsingMap(JSObject::cast(printee)); |
378 | if (printee->IsJSArray()) { |
379 | JSArray array = JSArray::cast(printee); |
380 | if (array->HasObjectElements()) { |
381 | unsigned int limit = FixedArray::cast(array->elements())->length(); |
382 | unsigned int length = |
383 | static_cast<uint32_t>(JSArray::cast(array)->length()->Number()); |
384 | if (length < limit) limit = length; |
385 | PrintFixedArray(FixedArray::cast(array->elements()), limit); |
386 | } |
387 | } |
388 | } else if (printee->IsByteArray()) { |
389 | PrintByteArray(ByteArray::cast(printee)); |
390 | } else if (printee->IsFixedArray()) { |
391 | unsigned int limit = FixedArray::cast(printee)->length(); |
392 | PrintFixedArray(FixedArray::cast(printee), limit); |
393 | } |
394 | } |
395 | } |
396 | |
397 | void StringStream::PrintSecurityTokenIfChanged(JSFunction fun) { |
398 | Object token = fun->native_context()->security_token(); |
399 | Isolate* isolate = fun->GetIsolate(); |
400 | if (token != isolate->string_stream_current_security_token()) { |
401 | Add("Security context: %o\n" , token); |
402 | isolate->set_string_stream_current_security_token(token); |
403 | } |
404 | } |
405 | |
406 | void StringStream::PrintFunction(JSFunction fun, Object receiver, Code* code) { |
407 | PrintPrototype(fun, receiver); |
408 | *code = fun->code(); |
409 | } |
410 | |
411 | void StringStream::PrintPrototype(JSFunction fun, Object receiver) { |
412 | Object name = fun->shared()->Name(); |
413 | bool print_name = false; |
414 | Isolate* isolate = fun->GetIsolate(); |
415 | if (receiver->IsNullOrUndefined(isolate) || receiver->IsTheHole(isolate) || |
416 | receiver->IsJSProxy()) { |
417 | print_name = true; |
418 | } else if (!isolate->context().is_null()) { |
419 | if (!receiver->IsJSObject()) { |
420 | receiver = receiver->GetPrototypeChainRootMap(isolate)->prototype(); |
421 | } |
422 | |
423 | for (PrototypeIterator iter(isolate, JSObject::cast(receiver), |
424 | kStartAtReceiver); |
425 | !iter.IsAtEnd(); iter.Advance()) { |
426 | if (iter.GetCurrent()->IsJSProxy()) break; |
427 | Object key = iter.GetCurrent<JSObject>()->SlowReverseLookup(fun); |
428 | if (!key->IsUndefined(isolate)) { |
429 | if (!name->IsString() || |
430 | !key->IsString() || |
431 | !String::cast(name)->Equals(String::cast(key))) { |
432 | print_name = true; |
433 | } |
434 | if (name->IsString() && String::cast(name)->length() == 0) { |
435 | print_name = false; |
436 | } |
437 | name = key; |
438 | break; |
439 | } |
440 | } |
441 | } |
442 | PrintName(name); |
443 | // Also known as - if the name in the function doesn't match the name under |
444 | // which it was looked up. |
445 | if (print_name) { |
446 | Add("(aka " ); |
447 | PrintName(fun->shared()->Name()); |
448 | Put(')'); |
449 | } |
450 | } |
451 | |
452 | char* HeapStringAllocator::grow(unsigned* bytes) { |
453 | unsigned new_bytes = *bytes * 2; |
454 | // Check for overflow. |
455 | if (new_bytes <= *bytes) { |
456 | return space_; |
457 | } |
458 | char* new_space = NewArray<char>(new_bytes); |
459 | if (new_space == nullptr) { |
460 | return space_; |
461 | } |
462 | MemCopy(new_space, space_, *bytes); |
463 | *bytes = new_bytes; |
464 | DeleteArray(space_); |
465 | space_ = new_space; |
466 | return new_space; |
467 | } |
468 | |
469 | |
470 | } // namespace internal |
471 | } // namespace v8 |
472 | |