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(&current)) {
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(&current)) {
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