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
15 namespace v8 {
16 namespace 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
44 namespace {
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.
Formatter(Isolate * isolate,BuiltinArguments & args,int index)65 bool 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
ConsoleCall(Isolate * isolate,const internal::BuiltinArguments & args,void (debug::ConsoleDelegate::* func)(const v8::debug::ConsoleCallArguments &,const v8::debug::ConsoleContext &))143 void 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
LogTimerEvent(Isolate * isolate,BuiltinArguments args,v8::LogEventStatus se)167 void 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 }
189 CONSOLE_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 }
CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_IMPLEMENTATION)201 CONSOLE_METHOD_WITH_FORMATTER_LIST(CONSOLE_BUILTIN_IMPLEMENTATION)
202 #undef CONSOLE_BUILTIN_IMPLEMENTATION
203
204 BUILTIN(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
BUILTIN(ConsoleTimeEnd)211 BUILTIN(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
BUILTIN(ConsoleTimeStamp)218 BUILTIN(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
225 namespace {
226
InstallContextFunction(Isolate * isolate,Handle<JSObject> target,const char * name,Builtin builtin,int context_id,Handle<Object> context_name)227 void 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
BUILTIN(ConsoleContext)260 BUILTIN(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