• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&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 
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