1// Copyright 2017 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 <stack> 6 7#include "src/api/api-inl.h" 8#include "src/builtins/builtins-utils-inl.h" 9#include "src/builtins/builtins.h" 10#include "src/debug/interface-types.h" 11#include "src/logging/counters.h" 12#include "src/logging/log.h" 13#include "src/objects/objects-inl.h" 14 15namespace v8 { 16namespace internal { 17 18// ----------------------------------------------------------------------------- 19// Console 20 21#define CONSOLE_METHOD_LIST(V) \ 22 V(Dir, dir) \ 23 V(DirXml, dirXml) \ 24 V(Table, table) \ 25 V(GroupEnd, groupEnd) \ 26 V(Clear, clear) \ 27 V(Count, count) \ 28 V(CountReset, countReset) \ 29 V(Profile, profile) \ 30 V(ProfileEnd, profileEnd) \ 31 V(TimeLog, timeLog) 32 33#define CONSOLE_METHOD_WITH_FORMATTER_LIST(V) \ 34 V(Debug, debug, 1) \ 35 V(Error, error, 1) \ 36 V(Info, info, 1) \ 37 V(Log, log, 1) \ 38 V(Warn, warn, 1) \ 39 V(Trace, trace, 1) \ 40 V(Group, group, 1) \ 41 V(GroupCollapsed, groupCollapsed, 1) \ 42 V(Assert, assert, 2) 43 44namespace { 45 46// 2.2 Formatter(args) [https://console.spec.whatwg.org/#formatter] 47// 48// This implements the formatter operation defined in the Console 49// specification to the degree that it makes sense for V8. That 50// means we primarily deal with %s, %i, %f, and %d, and any side 51// effects caused by the type conversions, and we preserve the %o, 52// %c, and %O specifiers and their parameters unchanged, and instead 53// leave it to the debugger front-end to make sense of those. 54// 55// Chrome also supports the non-standard bypass format specifier %_ 56// which just skips over the parameter. 57// 58// This implementation updates the |args| in-place with the results 59// from the conversion. 60// 61// The |index| describes the position of the format string within, 62// |args| (starting with 1, since |args| also includes the receiver), 63// which is different for example in case of `console.log` where it 64// is 1 compared to `console.assert` where it is 2. 65bool Formatter(Isolate* isolate, BuiltinArguments& args, int index) { 66 if (args.length() < index + 2 || !args[index].IsString()) { 67 return true; 68 } 69 struct State { 70 Handle<String> str; 71 int off; 72 }; 73 std::stack<State> states; 74 HandleScope scope(isolate); 75 auto percent = isolate->factory()->LookupSingleCharacterStringFromCode('%'); 76 states.push({args.at<String>(index++), 0}); 77 while (!states.empty() && index < args.length()) { 78 State& state = states.top(); 79 state.off = String::IndexOf(isolate, state.str, percent, state.off); 80 if (state.off < 0 || state.off == state.str->length() - 1) { 81 states.pop(); 82 continue; 83 } 84 Handle<Object> current = args.at(index); 85 uint16_t specifier = state.str->Get(state.off + 1, isolate); 86 if (specifier == 'd' || specifier == 'f' || specifier == 'i') { 87 if (current->IsSymbol()) { 88 current = isolate->factory()->nan_value(); 89 } else { 90 Handle<Object> params[] = {current, 91 isolate->factory()->NewNumberFromInt(10)}; 92 auto builtin = specifier == 'f' ? isolate->global_parse_float_fun() 93 : isolate->global_parse_int_fun(); 94 if (!Execution::CallBuiltin(isolate, builtin, 95 isolate->factory()->undefined_value(), 96 arraysize(params), params) 97 .ToHandle(¤t)) { 98 return false; 99 } 100 } 101 } else if (specifier == 's') { 102 Handle<Object> params[] = {current}; 103 if (!Execution::CallBuiltin(isolate, isolate->string_function(), 104 isolate->factory()->undefined_value(), 105 arraysize(params), params) 106 .ToHandle(¤t)) { 107 return false; 108 } 109 110 // Recurse into string results from type conversions, as they 111 // can themselves contain formatting specifiers. 112 states.push({Handle<String>::cast(current), 0}); 113 } else if (specifier == 'c' || specifier == 'o' || specifier == 'O' || 114 specifier == '_') { 115 // We leave the interpretation of %c (CSS), %o (optimally useful 116 // formatting), and %O (generic JavaScript object formatting) as 117 // well as the non-standard %_ (bypass formatter in Chrome) to 118 // the debugger front-end, and preserve these specifiers as well 119 // as their arguments verbatim. 120 index++; 121 state.off += 2; 122 continue; 123 } else if (specifier == '%') { 124 // Chrome also supports %% as a way to generate a single % in the 125 // output. 126 state.off += 2; 127 continue; 128 } else { 129 state.off++; 130 continue; 131 } 132 133 // Replace the |specifier| (including the '%' character) in |target| 134 // with the |current| value. We perform the replacement only morally 135 // by updating the argument to the conversion result, but leave it to 136 // the debugger front-end to perform the actual substitution. 137 args.set_at(index++, *current); 138 state.off += 2; 139 } 140 return true; 141} 142 143void ConsoleCall( 144 Isolate* isolate, const internal::BuiltinArguments& args, 145 void (debug::ConsoleDelegate::*func)(const v8::debug::ConsoleCallArguments&, 146 const v8::debug::ConsoleContext&)) { 147 CHECK(!isolate->has_pending_exception()); 148 CHECK(!isolate->has_scheduled_exception()); 149 if (!isolate->console_delegate()) return; 150 HandleScope scope(isolate); 151 debug::ConsoleCallArguments wrapper(args); 152 Handle<Object> context_id_obj = JSObject::GetDataProperty( 153 isolate, args.target(), isolate->factory()->console_context_id_symbol()); 154 int context_id = 155 context_id_obj->IsSmi() ? Handle<Smi>::cast(context_id_obj)->value() : 0; 156 Handle<Object> context_name_obj = JSObject::GetDataProperty( 157 isolate, args.target(), 158 isolate->factory()->console_context_name_symbol()); 159 Handle<String> context_name = context_name_obj->IsString() 160 ? Handle<String>::cast(context_name_obj) 161 : isolate->factory()->anonymous_string(); 162 (isolate->console_delegate()->*func)( 163 wrapper, 164 v8::debug::ConsoleContext(context_id, Utils::ToLocal(context_name))); 165} 166 167void LogTimerEvent(Isolate* isolate, BuiltinArguments args, 168 v8::LogEventStatus se) { 169 if (!isolate->logger()->is_logging()) return; 170 HandleScope scope(isolate); 171 std::unique_ptr<char[]> name; 172 const char* raw_name = "default"; 173 if (args.length() > 1 && args[1].IsString()) { 174 // Try converting the first argument to a string. 175 name = args.at<String>(1)->ToCString(); 176 raw_name = name.get(); 177 } 178 LOG(isolate, TimerEvent(se, raw_name)); 179} 180 181} // namespace 182 183#define CONSOLE_BUILTIN_IMPLEMENTATION(call, name) \ 184 BUILTIN(Console##call) { \ 185 ConsoleCall(isolate, args, &debug::ConsoleDelegate::call); \ 186 RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); \ 187 return ReadOnlyRoots(isolate).undefined_value(); \ 188 } 189CONSOLE_METHOD_LIST(CONSOLE_BUILTIN_IMPLEMENTATION) 190#undef CONSOLE_BUILTIN_IMPLEMENTATION 191 192#define CONSOLE_BUILTIN_IMPLEMENTATION(call, name, index) \ 193 BUILTIN(Console##call) { \ 194 if (!Formatter(isolate, args, index)) { \ 195 return ReadOnlyRoots(isolate).exception(); \ 196 } \ 197 ConsoleCall(isolate, args, &debug::ConsoleDelegate::call); \ 198 RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); \ 199 return ReadOnlyRoots(isolate).undefined_value(); \ 200 } 201CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_IMPLEMENTATION) 202#undef CONSOLE_BUILTIN_IMPLEMENTATION 203 204BUILTIN(ConsoleTime) { 205 LogTimerEvent(isolate, args, v8::LogEventStatus::kStart); 206 ConsoleCall(isolate, args, &debug::ConsoleDelegate::Time); 207 RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); 208 return ReadOnlyRoots(isolate).undefined_value(); 209} 210 211BUILTIN(ConsoleTimeEnd) { 212 LogTimerEvent(isolate, args, v8::LogEventStatus::kEnd); 213 ConsoleCall(isolate, args, &debug::ConsoleDelegate::TimeEnd); 214 RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); 215 return ReadOnlyRoots(isolate).undefined_value(); 216} 217 218BUILTIN(ConsoleTimeStamp) { 219 LogTimerEvent(isolate, args, v8::LogEventStatus::kStamp); 220 ConsoleCall(isolate, args, &debug::ConsoleDelegate::TimeStamp); 221 RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); 222 return ReadOnlyRoots(isolate).undefined_value(); 223} 224 225namespace { 226 227void InstallContextFunction(Isolate* isolate, Handle<JSObject> target, 228 const char* name, Builtin builtin, int context_id, 229 Handle<Object> context_name) { 230 Factory* const factory = isolate->factory(); 231 232 Handle<NativeContext> context(isolate->native_context()); 233 Handle<Map> map = isolate->sloppy_function_without_prototype_map(); 234 235 Handle<String> name_string = 236 Name::ToFunctionName(isolate, factory->InternalizeUtf8String(name)) 237 .ToHandleChecked(); 238 Handle<SharedFunctionInfo> info = 239 factory->NewSharedFunctionInfoForBuiltin(name_string, builtin); 240 info->set_language_mode(LanguageMode::kSloppy); 241 242 Handle<JSFunction> fun = 243 Factory::JSFunctionBuilder{isolate, info, context}.set_map(map).Build(); 244 245 fun->shared().set_native(true); 246 fun->shared().DontAdaptArguments(); 247 fun->shared().set_length(1); 248 249 JSObject::AddProperty(isolate, fun, factory->console_context_id_symbol(), 250 handle(Smi::FromInt(context_id), isolate), NONE); 251 if (context_name->IsString()) { 252 JSObject::AddProperty(isolate, fun, factory->console_context_name_symbol(), 253 context_name, NONE); 254 } 255 JSObject::AddProperty(isolate, target, name_string, fun, NONE); 256} 257 258} // namespace 259 260BUILTIN(ConsoleContext) { 261 HandleScope scope(isolate); 262 263 Factory* const factory = isolate->factory(); 264 Handle<String> name = factory->InternalizeUtf8String("Context"); 265 Handle<SharedFunctionInfo> info = 266 factory->NewSharedFunctionInfoForBuiltin(name, Builtin::kIllegal); 267 info->set_language_mode(LanguageMode::kSloppy); 268 269 Handle<JSFunction> cons = 270 Factory::JSFunctionBuilder{isolate, info, isolate->native_context()} 271 .Build(); 272 273 Handle<JSObject> prototype = factory->NewJSObject(isolate->object_function()); 274 JSFunction::SetPrototype(cons, prototype); 275 276 Handle<JSObject> context = factory->NewJSObject(cons, AllocationType::kOld); 277 DCHECK(context->IsJSObject()); 278 int id = isolate->last_console_context_id() + 1; 279 isolate->set_last_console_context_id(id); 280 281#define CONSOLE_BUILTIN_SETUP(call, name, ...) \ 282 InstallContextFunction(isolate, context, #name, Builtin::kConsole##call, id, \ 283 args.at(1)); 284 CONSOLE_METHOD_LIST(CONSOLE_BUILTIN_SETUP) 285 CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_SETUP) 286#undef CONSOLE_BUILTIN_SETUP 287 InstallContextFunction(isolate, context, "time", Builtin::kConsoleTime, id, 288 args.at(1)); 289 InstallContextFunction(isolate, context, "timeEnd", Builtin::kConsoleTimeEnd, 290 id, args.at(1)); 291 InstallContextFunction(isolate, context, "timeStamp", 292 Builtin::kConsoleTimeStamp, id, args.at(1)); 293 294 return *context; 295} 296 297#undef CONSOLE_METHOD_LIST 298 299} // namespace internal 300} // namespace v8 301