1 // Copyright 2016 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/inspector/v8-console-message.h" 6 7 #include "src/debug/debug-interface.h" 8 #include "src/inspector/inspected-context.h" 9 #include "src/inspector/protocol/Protocol.h" 10 #include "src/inspector/string-util.h" 11 #include "src/inspector/v8-console-agent-impl.h" 12 #include "src/inspector/v8-inspector-impl.h" 13 #include "src/inspector/v8-inspector-session-impl.h" 14 #include "src/inspector/v8-runtime-agent-impl.h" 15 #include "src/inspector/v8-stack-trace-impl.h" 16 17 #include "include/v8-inspector.h" 18 19 namespace v8_inspector { 20 21 namespace { 22 23 String16 consoleAPITypeValue(ConsoleAPIType type) { 24 switch (type) { 25 case ConsoleAPIType::kLog: 26 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Log; 27 case ConsoleAPIType::kDebug: 28 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Debug; 29 case ConsoleAPIType::kInfo: 30 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Info; 31 case ConsoleAPIType::kError: 32 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Error; 33 case ConsoleAPIType::kWarning: 34 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Warning; 35 case ConsoleAPIType::kClear: 36 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Clear; 37 case ConsoleAPIType::kDir: 38 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Dir; 39 case ConsoleAPIType::kDirXML: 40 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Dirxml; 41 case ConsoleAPIType::kTable: 42 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Table; 43 case ConsoleAPIType::kTrace: 44 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Trace; 45 case ConsoleAPIType::kStartGroup: 46 return protocol::Runtime::ConsoleAPICalled::TypeEnum::StartGroup; 47 case ConsoleAPIType::kStartGroupCollapsed: 48 return protocol::Runtime::ConsoleAPICalled::TypeEnum::StartGroupCollapsed; 49 case ConsoleAPIType::kEndGroup: 50 return protocol::Runtime::ConsoleAPICalled::TypeEnum::EndGroup; 51 case ConsoleAPIType::kAssert: 52 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Assert; 53 case ConsoleAPIType::kTimeEnd: 54 return protocol::Runtime::ConsoleAPICalled::TypeEnum::TimeEnd; 55 case ConsoleAPIType::kCount: 56 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Count; 57 } 58 return protocol::Runtime::ConsoleAPICalled::TypeEnum::Log; 59 } 60 61 const unsigned maxConsoleMessageCount = 1000; 62 const int maxConsoleMessageV8Size = 10 * 1024 * 1024; 63 const unsigned maxArrayItemsLimit = 10000; 64 const unsigned maxStackDepthLimit = 32; 65 66 class V8ValueStringBuilder { 67 public: 68 static String16 toString(v8::Local<v8::Value> value, 69 v8::Local<v8::Context> context) { 70 V8ValueStringBuilder builder(context); 71 if (!builder.append(value)) return String16(); 72 return builder.toString(); 73 } 74 75 private: 76 enum { 77 IgnoreNull = 1 << 0, 78 IgnoreUndefined = 1 << 1, 79 }; 80 81 explicit V8ValueStringBuilder(v8::Local<v8::Context> context) 82 : m_arrayLimit(maxArrayItemsLimit), 83 m_isolate(context->GetIsolate()), 84 m_tryCatch(context->GetIsolate()), 85 m_context(context) {} 86 87 bool append(v8::Local<v8::Value> value, unsigned ignoreOptions = 0) { 88 if (value.IsEmpty()) return true; 89 if ((ignoreOptions & IgnoreNull) && value->IsNull()) return true; 90 if ((ignoreOptions & IgnoreUndefined) && value->IsUndefined()) return true; 91 if (value->IsString()) return append(v8::Local<v8::String>::Cast(value)); 92 if (value->IsStringObject()) 93 return append(v8::Local<v8::StringObject>::Cast(value)->ValueOf()); 94 if (value->IsSymbol()) return append(v8::Local<v8::Symbol>::Cast(value)); 95 if (value->IsSymbolObject()) 96 return append(v8::Local<v8::SymbolObject>::Cast(value)->ValueOf()); 97 if (value->IsNumberObject()) { 98 m_builder.append(String16::fromDouble( 99 v8::Local<v8::NumberObject>::Cast(value)->ValueOf(), 6)); 100 return true; 101 } 102 if (value->IsBooleanObject()) { 103 m_builder.append(v8::Local<v8::BooleanObject>::Cast(value)->ValueOf() 104 ? "true" 105 : "false"); 106 return true; 107 } 108 if (value->IsArray()) return append(v8::Local<v8::Array>::Cast(value)); 109 if (value->IsProxy()) { 110 m_builder.append("[object Proxy]"); 111 return true; 112 } 113 if (value->IsObject() && !value->IsDate() && !value->IsFunction() && 114 !value->IsNativeError() && !value->IsRegExp()) { 115 v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(value); 116 v8::Local<v8::String> stringValue; 117 if (object->ObjectProtoToString(m_isolate->GetCurrentContext()) 118 .ToLocal(&stringValue)) 119 return append(stringValue); 120 } 121 v8::Local<v8::String> stringValue; 122 if (!value->ToString(m_isolate->GetCurrentContext()).ToLocal(&stringValue)) 123 return false; 124 return append(stringValue); 125 } 126 127 bool append(v8::Local<v8::Array> array) { 128 for (const auto& it : m_visitedArrays) { 129 if (it == array) return true; 130 } 131 uint32_t length = array->Length(); 132 if (length > m_arrayLimit) return false; 133 if (m_visitedArrays.size() > maxStackDepthLimit) return false; 134 135 bool result = true; 136 m_arrayLimit -= length; 137 m_visitedArrays.push_back(array); 138 for (uint32_t i = 0; i < length; ++i) { 139 if (i) m_builder.append(','); 140 v8::Local<v8::Value> value; 141 if (!array->Get(m_context, i).ToLocal(&value)) continue; 142 if (!append(value, IgnoreNull | IgnoreUndefined)) { 143 result = false; 144 break; 145 } 146 } 147 m_visitedArrays.pop_back(); 148 return result; 149 } 150 151 bool append(v8::Local<v8::Symbol> symbol) { 152 m_builder.append("Symbol("); 153 bool result = append(symbol->Name(), IgnoreUndefined); 154 m_builder.append(')'); 155 return result; 156 } 157 158 bool append(v8::Local<v8::String> string) { 159 if (m_tryCatch.HasCaught()) return false; 160 if (!string.IsEmpty()) m_builder.append(toProtocolString(string)); 161 return true; 162 } 163 164 String16 toString() { 165 if (m_tryCatch.HasCaught()) return String16(); 166 return m_builder.toString(); 167 } 168 169 uint32_t m_arrayLimit; 170 v8::Isolate* m_isolate; 171 String16Builder m_builder; 172 std::vector<v8::Local<v8::Array>> m_visitedArrays; 173 v8::TryCatch m_tryCatch; 174 v8::Local<v8::Context> m_context; 175 }; 176 177 } // namespace 178 179 V8ConsoleMessage::V8ConsoleMessage(V8MessageOrigin origin, double timestamp, 180 const String16& message) 181 : m_origin(origin), 182 m_timestamp(timestamp), 183 m_message(message), 184 m_lineNumber(0), 185 m_columnNumber(0), 186 m_scriptId(0), 187 m_contextId(0), 188 m_type(ConsoleAPIType::kLog), 189 m_exceptionId(0), 190 m_revokedExceptionId(0) {} 191 192 V8ConsoleMessage::~V8ConsoleMessage() {} 193 194 void V8ConsoleMessage::setLocation(const String16& url, unsigned lineNumber, 195 unsigned columnNumber, 196 std::unique_ptr<V8StackTraceImpl> stackTrace, 197 int scriptId) { 198 m_url = url; 199 m_lineNumber = lineNumber; 200 m_columnNumber = columnNumber; 201 m_stackTrace = std::move(stackTrace); 202 m_scriptId = scriptId; 203 } 204 205 void V8ConsoleMessage::reportToFrontend( 206 protocol::Console::Frontend* frontend) const { 207 DCHECK(m_origin == V8MessageOrigin::kConsole); 208 String16 level = protocol::Console::ConsoleMessage::LevelEnum::Log; 209 if (m_type == ConsoleAPIType::kDebug || m_type == ConsoleAPIType::kCount || 210 m_type == ConsoleAPIType::kTimeEnd) 211 level = protocol::Console::ConsoleMessage::LevelEnum::Debug; 212 else if (m_type == ConsoleAPIType::kError || 213 m_type == ConsoleAPIType::kAssert) 214 level = protocol::Console::ConsoleMessage::LevelEnum::Error; 215 else if (m_type == ConsoleAPIType::kWarning) 216 level = protocol::Console::ConsoleMessage::LevelEnum::Warning; 217 else if (m_type == ConsoleAPIType::kInfo) 218 level = protocol::Console::ConsoleMessage::LevelEnum::Info; 219 std::unique_ptr<protocol::Console::ConsoleMessage> result = 220 protocol::Console::ConsoleMessage::create() 221 .setSource(protocol::Console::ConsoleMessage::SourceEnum::ConsoleApi) 222 .setLevel(level) 223 .setText(m_message) 224 .build(); 225 result->setLine(static_cast<int>(m_lineNumber)); 226 result->setColumn(static_cast<int>(m_columnNumber)); 227 result->setUrl(m_url); 228 frontend->messageAdded(std::move(result)); 229 } 230 231 std::unique_ptr<protocol::Array<protocol::Runtime::RemoteObject>> 232 V8ConsoleMessage::wrapArguments(V8InspectorSessionImpl* session, 233 bool generatePreview) const { 234 V8InspectorImpl* inspector = session->inspector(); 235 int contextGroupId = session->contextGroupId(); 236 int contextId = m_contextId; 237 if (!m_arguments.size() || !contextId) return nullptr; 238 InspectedContext* inspectedContext = 239 inspector->getContext(contextGroupId, contextId); 240 if (!inspectedContext) return nullptr; 241 242 v8::Isolate* isolate = inspectedContext->isolate(); 243 v8::HandleScope handles(isolate); 244 v8::Local<v8::Context> context = inspectedContext->context(); 245 246 std::unique_ptr<protocol::Array<protocol::Runtime::RemoteObject>> args = 247 protocol::Array<protocol::Runtime::RemoteObject>::create(); 248 if (m_type == ConsoleAPIType::kTable && generatePreview) { 249 v8::Local<v8::Value> table = m_arguments[0]->Get(isolate); 250 v8::Local<v8::Value> columns = m_arguments.size() > 1 251 ? m_arguments[1]->Get(isolate) 252 : v8::Local<v8::Value>(); 253 std::unique_ptr<protocol::Runtime::RemoteObject> wrapped = 254 session->wrapTable(context, table, columns); 255 inspectedContext = inspector->getContext(contextGroupId, contextId); 256 if (!inspectedContext) return nullptr; 257 if (wrapped) 258 args->addItem(std::move(wrapped)); 259 else 260 args = nullptr; 261 } else { 262 for (size_t i = 0; i < m_arguments.size(); ++i) { 263 std::unique_ptr<protocol::Runtime::RemoteObject> wrapped = 264 session->wrapObject(context, m_arguments[i]->Get(isolate), "console", 265 generatePreview); 266 inspectedContext = inspector->getContext(contextGroupId, contextId); 267 if (!inspectedContext) return nullptr; 268 if (!wrapped) { 269 args = nullptr; 270 break; 271 } 272 args->addItem(std::move(wrapped)); 273 } 274 } 275 return args; 276 } 277 278 void V8ConsoleMessage::reportToFrontend(protocol::Runtime::Frontend* frontend, 279 V8InspectorSessionImpl* session, 280 bool generatePreview) const { 281 int contextGroupId = session->contextGroupId(); 282 V8InspectorImpl* inspector = session->inspector(); 283 284 if (m_origin == V8MessageOrigin::kException) { 285 std::unique_ptr<protocol::Runtime::RemoteObject> exception = 286 wrapException(session, generatePreview); 287 if (!inspector->hasConsoleMessageStorage(contextGroupId)) return; 288 std::unique_ptr<protocol::Runtime::ExceptionDetails> exceptionDetails = 289 protocol::Runtime::ExceptionDetails::create() 290 .setExceptionId(m_exceptionId) 291 .setText(exception ? m_message : m_detailedMessage) 292 .setLineNumber(m_lineNumber ? m_lineNumber - 1 : 0) 293 .setColumnNumber(m_columnNumber ? m_columnNumber - 1 : 0) 294 .build(); 295 if (m_scriptId) 296 exceptionDetails->setScriptId(String16::fromInteger(m_scriptId)); 297 if (!m_url.isEmpty()) exceptionDetails->setUrl(m_url); 298 if (m_stackTrace) 299 exceptionDetails->setStackTrace(m_stackTrace->buildInspectorObjectImpl()); 300 if (m_contextId) exceptionDetails->setExecutionContextId(m_contextId); 301 if (exception) exceptionDetails->setException(std::move(exception)); 302 frontend->exceptionThrown(m_timestamp, std::move(exceptionDetails)); 303 return; 304 } 305 if (m_origin == V8MessageOrigin::kRevokedException) { 306 frontend->exceptionRevoked(m_message, m_revokedExceptionId); 307 return; 308 } 309 if (m_origin == V8MessageOrigin::kConsole) { 310 std::unique_ptr<protocol::Array<protocol::Runtime::RemoteObject>> 311 arguments = wrapArguments(session, generatePreview); 312 if (!inspector->hasConsoleMessageStorage(contextGroupId)) return; 313 if (!arguments) { 314 arguments = protocol::Array<protocol::Runtime::RemoteObject>::create(); 315 if (!m_message.isEmpty()) { 316 std::unique_ptr<protocol::Runtime::RemoteObject> messageArg = 317 protocol::Runtime::RemoteObject::create() 318 .setType(protocol::Runtime::RemoteObject::TypeEnum::String) 319 .build(); 320 messageArg->setValue(protocol::StringValue::create(m_message)); 321 arguments->addItem(std::move(messageArg)); 322 } 323 } 324 frontend->consoleAPICalled( 325 consoleAPITypeValue(m_type), std::move(arguments), m_contextId, 326 m_timestamp, 327 m_stackTrace ? m_stackTrace->buildInspectorObjectImpl() : nullptr); 328 return; 329 } 330 UNREACHABLE(); 331 } 332 333 std::unique_ptr<protocol::Runtime::RemoteObject> 334 V8ConsoleMessage::wrapException(V8InspectorSessionImpl* session, 335 bool generatePreview) const { 336 if (!m_arguments.size() || !m_contextId) return nullptr; 337 DCHECK_EQ(1u, m_arguments.size()); 338 InspectedContext* inspectedContext = 339 session->inspector()->getContext(session->contextGroupId(), m_contextId); 340 if (!inspectedContext) return nullptr; 341 342 v8::Isolate* isolate = inspectedContext->isolate(); 343 v8::HandleScope handles(isolate); 344 // TODO(dgozman): should we use different object group? 345 return session->wrapObject(inspectedContext->context(), 346 m_arguments[0]->Get(isolate), "console", 347 generatePreview); 348 } 349 350 V8MessageOrigin V8ConsoleMessage::origin() const { return m_origin; } 351 352 ConsoleAPIType V8ConsoleMessage::type() const { return m_type; } 353 354 // static 355 std::unique_ptr<V8ConsoleMessage> V8ConsoleMessage::createForConsoleAPI( 356 double timestamp, ConsoleAPIType type, 357 const std::vector<v8::Local<v8::Value>>& arguments, 358 std::unique_ptr<V8StackTraceImpl> stackTrace, 359 InspectedContext* inspectedContext) { 360 v8::Isolate* isolate = inspectedContext->isolate(); 361 int contextId = inspectedContext->contextId(); 362 int contextGroupId = inspectedContext->contextGroupId(); 363 V8InspectorImpl* inspector = inspectedContext->inspector(); 364 v8::Local<v8::Context> context = inspectedContext->context(); 365 366 std::unique_ptr<V8ConsoleMessage> message( 367 new V8ConsoleMessage(V8MessageOrigin::kConsole, timestamp, String16())); 368 if (stackTrace && !stackTrace->isEmpty()) { 369 message->m_url = toString16(stackTrace->topSourceURL()); 370 message->m_lineNumber = stackTrace->topLineNumber(); 371 message->m_columnNumber = stackTrace->topColumnNumber(); 372 } 373 message->m_stackTrace = std::move(stackTrace); 374 message->m_type = type; 375 message->m_contextId = contextId; 376 for (size_t i = 0; i < arguments.size(); ++i) { 377 message->m_arguments.push_back(std::unique_ptr<v8::Global<v8::Value>>( 378 new v8::Global<v8::Value>(isolate, arguments.at(i)))); 379 message->m_v8Size += 380 v8::debug::EstimatedValueSize(isolate, arguments.at(i)); 381 } 382 if (arguments.size()) 383 message->m_message = V8ValueStringBuilder::toString(arguments[0], context); 384 385 v8::Isolate::MessageErrorLevel clientLevel = v8::Isolate::kMessageInfo; 386 if (type == ConsoleAPIType::kDebug || type == ConsoleAPIType::kCount || 387 type == ConsoleAPIType::kTimeEnd) { 388 clientLevel = v8::Isolate::kMessageDebug; 389 } else if (type == ConsoleAPIType::kError || 390 type == ConsoleAPIType::kAssert) { 391 clientLevel = v8::Isolate::kMessageError; 392 } else if (type == ConsoleAPIType::kWarning) { 393 clientLevel = v8::Isolate::kMessageWarning; 394 } else if (type == ConsoleAPIType::kInfo || type == ConsoleAPIType::kLog) { 395 clientLevel = v8::Isolate::kMessageInfo; 396 } 397 398 if (type != ConsoleAPIType::kClear) { 399 inspector->client()->consoleAPIMessage( 400 contextGroupId, clientLevel, toStringView(message->m_message), 401 toStringView(message->m_url), message->m_lineNumber, 402 message->m_columnNumber, message->m_stackTrace.get()); 403 } 404 405 return message; 406 } 407 408 // static 409 std::unique_ptr<V8ConsoleMessage> V8ConsoleMessage::createForException( 410 double timestamp, const String16& detailedMessage, const String16& url, 411 unsigned lineNumber, unsigned columnNumber, 412 std::unique_ptr<V8StackTraceImpl> stackTrace, int scriptId, 413 v8::Isolate* isolate, const String16& message, int contextId, 414 v8::Local<v8::Value> exception, unsigned exceptionId) { 415 std::unique_ptr<V8ConsoleMessage> consoleMessage( 416 new V8ConsoleMessage(V8MessageOrigin::kException, timestamp, message)); 417 consoleMessage->setLocation(url, lineNumber, columnNumber, 418 std::move(stackTrace), scriptId); 419 consoleMessage->m_exceptionId = exceptionId; 420 consoleMessage->m_detailedMessage = detailedMessage; 421 if (contextId && !exception.IsEmpty()) { 422 consoleMessage->m_contextId = contextId; 423 consoleMessage->m_arguments.push_back( 424 std::unique_ptr<v8::Global<v8::Value>>( 425 new v8::Global<v8::Value>(isolate, exception))); 426 consoleMessage->m_v8Size += 427 v8::debug::EstimatedValueSize(isolate, exception); 428 } 429 return consoleMessage; 430 } 431 432 // static 433 std::unique_ptr<V8ConsoleMessage> V8ConsoleMessage::createForRevokedException( 434 double timestamp, const String16& messageText, 435 unsigned revokedExceptionId) { 436 std::unique_ptr<V8ConsoleMessage> message(new V8ConsoleMessage( 437 V8MessageOrigin::kRevokedException, timestamp, messageText)); 438 message->m_revokedExceptionId = revokedExceptionId; 439 return message; 440 } 441 442 void V8ConsoleMessage::contextDestroyed(int contextId) { 443 if (contextId != m_contextId) return; 444 m_contextId = 0; 445 if (m_message.isEmpty()) m_message = "<message collected>"; 446 Arguments empty; 447 m_arguments.swap(empty); 448 m_v8Size = 0; 449 } 450 451 // ------------------------ V8ConsoleMessageStorage ---------------------------- 452 453 V8ConsoleMessageStorage::V8ConsoleMessageStorage(V8InspectorImpl* inspector, 454 int contextGroupId) 455 : m_inspector(inspector), m_contextGroupId(contextGroupId) {} 456 457 V8ConsoleMessageStorage::~V8ConsoleMessageStorage() { clear(); } 458 459 void V8ConsoleMessageStorage::addMessage( 460 std::unique_ptr<V8ConsoleMessage> message) { 461 int contextGroupId = m_contextGroupId; 462 V8InspectorImpl* inspector = m_inspector; 463 if (message->type() == ConsoleAPIType::kClear) clear(); 464 465 V8InspectorSessionImpl* session = 466 inspector->sessionForContextGroup(contextGroupId); 467 if (session) { 468 if (message->origin() == V8MessageOrigin::kConsole) 469 session->consoleAgent()->messageAdded(message.get()); 470 session->runtimeAgent()->messageAdded(message.get()); 471 } 472 if (!inspector->hasConsoleMessageStorage(contextGroupId)) return; 473 474 DCHECK(m_messages.size() <= maxConsoleMessageCount); 475 if (m_messages.size() == maxConsoleMessageCount) { 476 m_estimatedSize -= m_messages.front()->estimatedSize(); 477 m_messages.pop_front(); 478 } 479 while (m_estimatedSize + message->estimatedSize() > maxConsoleMessageV8Size && 480 !m_messages.empty()) { 481 m_estimatedSize -= m_messages.front()->estimatedSize(); 482 m_messages.pop_front(); 483 } 484 485 m_messages.push_back(std::move(message)); 486 m_estimatedSize += m_messages.back()->estimatedSize(); 487 } 488 489 void V8ConsoleMessageStorage::clear() { 490 m_messages.clear(); 491 m_estimatedSize = 0; 492 if (V8InspectorSessionImpl* session = 493 m_inspector->sessionForContextGroup(m_contextGroupId)) 494 session->releaseObjectGroup("console"); 495 } 496 497 void V8ConsoleMessageStorage::contextDestroyed(int contextId) { 498 m_estimatedSize = 0; 499 for (size_t i = 0; i < m_messages.size(); ++i) { 500 m_messages[i]->contextDestroyed(contextId); 501 m_estimatedSize += m_messages[i]->estimatedSize(); 502 } 503 } 504 505 } // namespace v8_inspector 506