// Copyright 2017 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 <stack> #include "src/api/api-inl.h" #include "src/builtins/builtins-utils-inl.h" #include "src/builtins/builtins.h" #include "src/debug/interface-types.h" #include "src/logging/counters.h" #include "src/logging/log.h" #include "src/objects/objects-inl.h" namespace v8 { namespace internal { // ----------------------------------------------------------------------------- // Console #define CONSOLE_METHOD_LIST(V) \ V(Dir, dir) \ V(DirXml, dirXml) \ V(Table, table) \ V(GroupEnd, groupEnd) \ V(Clear, clear) \ V(Count, count) \ V(CountReset, countReset) \ V(Profile, profile) \ V(ProfileEnd, profileEnd) \ V(TimeLog, timeLog) #define CONSOLE_METHOD_WITH_FORMATTER_LIST(V) \ V(Debug, debug, 1) \ V(Error, error, 1) \ V(Info, info, 1) \ V(Log, log, 1) \ V(Warn, warn, 1) \ V(Trace, trace, 1) \ V(Group, group, 1) \ V(GroupCollapsed, groupCollapsed, 1) \ V(Assert, assert, 2) namespace { // 2.2 Formatter(args) [https://console.spec.whatwg.org/#formatter] // // This implements the formatter operation defined in the Console // specification to the degree that it makes sense for V8. That // means we primarily deal with %s, %i, %f, and %d, and any side // effects caused by the type conversions, and we preserve the %o, // %c, and %O specifiers and their parameters unchanged, and instead // leave it to the debugger front-end to make sense of those. // // Chrome also supports the non-standard bypass format specifier %_ // which just skips over the parameter. // // This implementation updates the |args| in-place with the results // from the conversion. // // The |index| describes the position of the format string within, // |args| (starting with 1, since |args| also includes the receiver), // which is different for example in case of `console.log` where it // is 1 compared to `console.assert` where it is 2. bool Formatter(Isolate* isolate, BuiltinArguments& args, int index) { if (args.length() < index + 2 || !args[index].IsString()) { return true; } struct State { Handle<String> str; int off; }; std::stack<State> states; HandleScope scope(isolate); auto percent = isolate->factory()->LookupSingleCharacterStringFromCode('%'); states.push({args.at<String>(index++), 0}); while (!states.empty() && index < args.length()) { State& state = states.top(); state.off = String::IndexOf(isolate, state.str, percent, state.off); if (state.off < 0 || state.off == state.str->length() - 1) { states.pop(); continue; } Handle<Object> current = args.at(index); uint16_t specifier = state.str->Get(state.off + 1, isolate); if (specifier == 'd' || specifier == 'f' || specifier == 'i') { if (current->IsSymbol()) { current = isolate->factory()->nan_value(); } else { Handle<Object> params[] = {current, isolate->factory()->NewNumberFromInt(10)}; auto builtin = specifier == 'f' ? isolate->global_parse_float_fun() : isolate->global_parse_int_fun(); if (!Execution::CallBuiltin(isolate, builtin, isolate->factory()->undefined_value(), arraysize(params), params) .ToHandle(¤t)) { return false; } } } else if (specifier == 's') { Handle<Object> params[] = {current}; if (!Execution::CallBuiltin(isolate, isolate->string_function(), isolate->factory()->undefined_value(), arraysize(params), params) .ToHandle(¤t)) { return false; } // Recurse into string results from type conversions, as they // can themselves contain formatting specifiers. states.push({Handle<String>::cast(current), 0}); } else if (specifier == 'c' || specifier == 'o' || specifier == 'O' || specifier == '_') { // We leave the interpretation of %c (CSS), %o (optimally useful // formatting), and %O (generic JavaScript object formatting) as // well as the non-standard %_ (bypass formatter in Chrome) to // the debugger front-end, and preserve these specifiers as well // as their arguments verbatim. index++; state.off += 2; continue; } else if (specifier == '%') { // Chrome also supports %% as a way to generate a single % in the // output. state.off += 2; continue; } else { state.off++; continue; } // Replace the |specifier| (including the '%' character) in |target| // with the |current| value. We perform the replacement only morally // by updating the argument to the conversion result, but leave it to // the debugger front-end to perform the actual substitution. args.set_at(index++, *current); state.off += 2; } return true; } void ConsoleCall( Isolate* isolate, const internal::BuiltinArguments& args, void (debug::ConsoleDelegate::*func)(const v8::debug::ConsoleCallArguments&, const v8::debug::ConsoleContext&)) { CHECK(!isolate->has_pending_exception()); CHECK(!isolate->has_scheduled_exception()); if (!isolate->console_delegate()) return; HandleScope scope(isolate); debug::ConsoleCallArguments wrapper(args); Handle<Object> context_id_obj = JSObject::GetDataProperty( isolate, args.target(), isolate->factory()->console_context_id_symbol()); int context_id = context_id_obj->IsSmi() ? Handle<Smi>::cast(context_id_obj)->value() : 0; Handle<Object> context_name_obj = JSObject::GetDataProperty( isolate, args.target(), isolate->factory()->console_context_name_symbol()); Handle<String> context_name = context_name_obj->IsString() ? Handle<String>::cast(context_name_obj) : isolate->factory()->anonymous_string(); (isolate->console_delegate()->*func)( wrapper, v8::debug::ConsoleContext(context_id, Utils::ToLocal(context_name))); } void LogTimerEvent(Isolate* isolate, BuiltinArguments args, v8::LogEventStatus se) { if (!isolate->v8_file_logger()->is_logging()) return; HandleScope scope(isolate); std::unique_ptr<char[]> name; const char* raw_name = "default"; if (args.length() > 1 && args[1].IsString()) { // Try converting the first argument to a string. name = args.at<String>(1)->ToCString(); raw_name = name.get(); } LOG(isolate, TimerEvent(se, raw_name)); } } // namespace #define CONSOLE_BUILTIN_IMPLEMENTATION(call, name) \ BUILTIN(Console##call) { \ ConsoleCall(isolate, args, &debug::ConsoleDelegate::call); \ RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); \ return ReadOnlyRoots(isolate).undefined_value(); \ } CONSOLE_METHOD_LIST(CONSOLE_BUILTIN_IMPLEMENTATION) #undef CONSOLE_BUILTIN_IMPLEMENTATION #define CONSOLE_BUILTIN_IMPLEMENTATION(call, name, index) \ BUILTIN(Console##call) { \ if (!Formatter(isolate, args, index)) { \ return ReadOnlyRoots(isolate).exception(); \ } \ ConsoleCall(isolate, args, &debug::ConsoleDelegate::call); \ RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); \ return ReadOnlyRoots(isolate).undefined_value(); \ } CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_IMPLEMENTATION) #undef CONSOLE_BUILTIN_IMPLEMENTATION BUILTIN(ConsoleTime) { LogTimerEvent(isolate, args, v8::LogEventStatus::kStart); ConsoleCall(isolate, args, &debug::ConsoleDelegate::Time); RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); return ReadOnlyRoots(isolate).undefined_value(); } BUILTIN(ConsoleTimeEnd) { LogTimerEvent(isolate, args, v8::LogEventStatus::kEnd); ConsoleCall(isolate, args, &debug::ConsoleDelegate::TimeEnd); RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); return ReadOnlyRoots(isolate).undefined_value(); } BUILTIN(ConsoleTimeStamp) { LogTimerEvent(isolate, args, v8::LogEventStatus::kStamp); ConsoleCall(isolate, args, &debug::ConsoleDelegate::TimeStamp); RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); return ReadOnlyRoots(isolate).undefined_value(); } namespace { void InstallContextFunction(Isolate* isolate, Handle<JSObject> target, const char* name, Builtin builtin, int context_id, Handle<Object> context_name) { Factory* const factory = isolate->factory(); Handle<NativeContext> context(isolate->native_context()); Handle<Map> map = isolate->sloppy_function_without_prototype_map(); Handle<String> name_string = Name::ToFunctionName(isolate, factory->InternalizeUtf8String(name)) .ToHandleChecked(); Handle<SharedFunctionInfo> info = factory->NewSharedFunctionInfoForBuiltin(name_string, builtin); info->set_language_mode(LanguageMode::kSloppy); Handle<JSFunction> fun = Factory::JSFunctionBuilder{isolate, info, context}.set_map(map).Build(); fun->shared().set_native(true); fun->shared().DontAdaptArguments(); fun->shared().set_length(1); JSObject::AddProperty(isolate, fun, factory->console_context_id_symbol(), handle(Smi::FromInt(context_id), isolate), NONE); if (context_name->IsString()) { JSObject::AddProperty(isolate, fun, factory->console_context_name_symbol(), context_name, NONE); } JSObject::AddProperty(isolate, target, name_string, fun, NONE); } } // namespace BUILTIN(ConsoleContext) { HandleScope scope(isolate); Factory* const factory = isolate->factory(); Handle<String> name = factory->InternalizeUtf8String("Context"); Handle<SharedFunctionInfo> info = factory->NewSharedFunctionInfoForBuiltin(name, Builtin::kIllegal); info->set_language_mode(LanguageMode::kSloppy); Handle<JSFunction> cons = Factory::JSFunctionBuilder{isolate, info, isolate->native_context()} .Build(); Handle<JSObject> prototype = factory->NewJSObject(isolate->object_function()); JSFunction::SetPrototype(cons, prototype); Handle<JSObject> context = factory->NewJSObject(cons, AllocationType::kOld); DCHECK(context->IsJSObject()); int id = isolate->last_console_context_id() + 1; isolate->set_last_console_context_id(id); #define CONSOLE_BUILTIN_SETUP(call, name, ...) \ InstallContextFunction(isolate, context, #name, Builtin::kConsole##call, id, \ args.at(1)); CONSOLE_METHOD_LIST(CONSOLE_BUILTIN_SETUP) CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_SETUP) #undef CONSOLE_BUILTIN_SETUP InstallContextFunction(isolate, context, "time", Builtin::kConsoleTime, id, args.at(1)); InstallContextFunction(isolate, context, "timeEnd", Builtin::kConsoleTimeEnd, id, args.at(1)); InstallContextFunction(isolate, context, "timeStamp", Builtin::kConsoleTimeStamp, id, args.at(1)); return *context; } #undef CONSOLE_METHOD_LIST } // namespace internal } // namespace v8