1 /*
2  *  Minimal 'console' binding.
3  *
4  *  https://github.com/DeveloperToolsWG/console-object/blob/master/api.md
5  *  https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference
6  *  https://developer.mozilla.org/en/docs/Web/API/console
7  */
8 
9 #include <stdio.h>
10 #include <stdarg.h>
11 #include "duktape.h"
12 #include "duk_console.h"
13 #include "../../debug.h"
14 
15 /* XXX: Add some form of log level filtering. */
16 
17 /* XXX: Should all output be written via e.g. console.write(formattedMsg)?
18  * This would make it easier for user code to redirect all console output
19  * to a custom backend.
20  */
21 
22 /* XXX: Init console object using duk_def_prop() when that call is available. */
23 
duk__console_log_helper(duk_context * ctx,int level,const char * error_name)24 static duk_ret_t duk__console_log_helper(duk_context *ctx, int level, const char *error_name) {
25 
26 	duk_idx_t n = duk_get_top(ctx);
27 	duk_idx_t i;
28 
29 	duk_get_global_string(ctx, "console");
30 	duk_get_prop_string(ctx, -1, "format");
31 
32 	for (i = 0; i < n; i++) {
33 		if (duk_check_type_mask(ctx, i, DUK_TYPE_MASK_OBJECT)) {
34 			/* Slow path formatting. */
35 			duk_dup(ctx, -1);  /* console.format */
36 			duk_dup(ctx, i);
37 			duk_call(ctx, 1);
38 			duk_replace(ctx, i);  /* arg[i] = console.format(arg[i]); */
39 		}
40 	}
41 
42 	duk_pop_2(ctx);
43 
44 	duk_push_string(ctx, " ");
45 	duk_insert(ctx, 0);
46 	duk_join(ctx, n);
47 
48 	if (error_name) {
49 		duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_require_string(ctx, -1));
50 		duk_push_string(ctx, "name");
51 		duk_push_string(ctx, error_name);
52 		duk_def_prop(ctx, -3, DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE);  /* to get e.g. 'Trace: 1 2 3' */
53 		duk_get_prop_string(ctx, -1, "stack");
54 	}
55 
56 	JANUS_LOG(level, "%s\n", duk_to_string(ctx, -1));
57 
58 	return 0;
59 }
60 
duk__console_assert(duk_context * ctx)61 static duk_ret_t duk__console_assert(duk_context *ctx) {
62 	if (duk_to_boolean(ctx, 0)) {
63 		return 0;
64 	}
65 	duk_remove(ctx, 0);
66 
67 	return duk__console_log_helper(ctx, LOG_ERR, "AssertionError");
68 }
69 
duk__console_log(duk_context * ctx)70 static duk_ret_t duk__console_log(duk_context *ctx) {
71 	return duk__console_log_helper(ctx, LOG_INFO, NULL);
72 }
73 
duk__console_trace(duk_context * ctx)74 static duk_ret_t duk__console_trace(duk_context *ctx) {
75 	return duk__console_log_helper(ctx, LOG_INFO, "Trace");
76 }
77 
duk__console_info(duk_context * ctx)78 static duk_ret_t duk__console_info(duk_context *ctx) {
79 	return duk__console_log_helper(ctx, LOG_INFO, NULL);
80 }
81 
duk__console_warn(duk_context * ctx)82 static duk_ret_t duk__console_warn(duk_context *ctx) {
83 	return duk__console_log_helper(ctx, LOG_WARN, NULL);
84 }
85 
duk__console_error(duk_context * ctx)86 static duk_ret_t duk__console_error(duk_context *ctx) {
87 	return duk__console_log_helper(ctx, LOG_ERR, "Error");
88 }
89 
duk__console_dir(duk_context * ctx)90 static duk_ret_t duk__console_dir(duk_context *ctx) {
91 	/* For now, just share the formatting of .log() */
92 	return duk__console_log_helper(ctx, LOG_INFO, 0);
93 }
94 
duk__console_reg_vararg_func(duk_context * ctx,duk_c_function func,const char * name,duk_uint_t flags)95 static void duk__console_reg_vararg_func(duk_context *ctx, duk_c_function func, const char *name, duk_uint_t flags) {
96 	duk_push_c_function(ctx, func, DUK_VARARGS);
97 	duk_push_string(ctx, "name");
98 	duk_push_string(ctx, name);
99 	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);  /* Improve stacktraces by displaying function name */
100 	duk_set_magic(ctx, -1, (duk_int_t) flags);
101 	duk_put_prop_string(ctx, -2, name);
102 }
103 
duk_console_init(duk_context * ctx,duk_uint_t flags)104 void duk_console_init(duk_context *ctx, duk_uint_t flags) {
105 	duk_uint_t flags_orig;
106 
107 	/* If both DUK_CONSOLE_STDOUT_ONLY and DUK_CONSOLE_STDERR_ONLY where specified,
108 	 * just turn off DUK_CONSOLE_STDOUT_ONLY and keep DUK_CONSOLE_STDERR_ONLY.
109 	 */
110 	if ((flags & DUK_CONSOLE_STDOUT_ONLY) && (flags & DUK_CONSOLE_STDERR_ONLY)) {
111 	    flags &= ~DUK_CONSOLE_STDOUT_ONLY;
112 	}
113 	/* Remember the (possibly corrected) flags we received. */
114 	flags_orig = flags;
115 
116 	duk_push_object(ctx);
117 
118 	/* Custom function to format objects; user can replace.
119 	 * For now, try JX-formatting and if that fails, fall back
120 	 * to ToString(v).
121 	 */
122 	duk_eval_string(ctx,
123 		"(function (E) {"
124 		    "return function format(v){"
125 		        "try{"
126 		            "return E('jx',v);"
127 		        "}catch(e){"
128 		            "return String(v);"  /* String() allows symbols, ToString() internal algorithm doesn't. */
129 		        "}"
130 		    "};"
131 		"})(Duktape.enc)");
132 	duk_put_prop_string(ctx, -2, "format");
133 
134 	flags = flags_orig;
135 	if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
136 	    /* No output indicators were specified; these levels go to stdout. */
137 	    flags |= DUK_CONSOLE_STDOUT_ONLY;
138 	}
139 	duk__console_reg_vararg_func(ctx, duk__console_assert, "assert", flags);
140 	duk__console_reg_vararg_func(ctx, duk__console_log, "log", flags);
141 	duk__console_reg_vararg_func(ctx, duk__console_log, "debug", flags);  /* alias to console.log */
142 	duk__console_reg_vararg_func(ctx, duk__console_trace, "trace", flags);
143 	duk__console_reg_vararg_func(ctx, duk__console_info, "info", flags);
144 
145 	flags = flags_orig;
146 	if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
147 	    /* No output indicators were specified; these levels go to stderr. */
148 	    flags |= DUK_CONSOLE_STDERR_ONLY;
149 	}
150 	duk__console_reg_vararg_func(ctx, duk__console_warn, "warn", flags);
151 	duk__console_reg_vararg_func(ctx, duk__console_error, "error", flags);
152 	duk__console_reg_vararg_func(ctx, duk__console_error, "exception", flags);  /* alias to console.error */
153 	duk__console_reg_vararg_func(ctx, duk__console_dir, "dir", flags);
154 
155 	duk_put_global_string(ctx, "console");
156 
157 	/* Proxy wrapping: ensures any undefined console method calls are
158 	 * ignored silently.  This was required specifically by the
159 	 * DeveloperToolsWG proposal (and was implemented also by Firefox:
160 	 * https://bugzilla.mozilla.org/show_bug.cgi?id=629607).  This is
161 	 * apparently no longer the preferred way of implementing console.
162 	 * When Proxy is enabled, whitelist at least .toJSON() to avoid
163 	 * confusing JX serialization of the console object.
164 	 */
165 
166 	if (flags & DUK_CONSOLE_PROXY_WRAPPER) {
167 		/* Tolerate failure to initialize Proxy wrapper in case
168 		 * Proxy support is disabled.
169 		 */
170 		(void) duk_peval_string_noresult(ctx,
171 			"(function(){"
172 			    "var D=function(){};"
173 			    "var W={toJSON:true};"  /* whitelisted */
174 			    "console=new Proxy(console,{"
175 			        "get:function(t,k){"
176 			            "var v=t[k];"
177 			            "return typeof v==='function'||W[k]?v:D;"
178 			        "}"
179 			    "});"
180 			"})();"
181 		);
182 	}
183 }
184