1 // Copyright 2014 The Chromium 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.
5 #include "extensions/renderer/safe_builtins.h"
8 #include "base/bind_helpers.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/stl_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/synchronization/lock.h"
14 #include "extensions/renderer/object_backed_native_handler.h"
15 #include "extensions/renderer/script_context.h"
16 #include "extensions/renderer/v8_helpers.h"
17 #include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
19 namespace extensions
{
21 using namespace v8_helpers
;
27 // This is the documentation for makeCallback() function in the JavaScript, out
28 // here to reduce the amount of effort that the v8 parser needs to do:
30 // Returns a new object with every function on |obj| configured to call()
31 // itself with the given arguments.
34 // var result = makeCallable(Function.prototype)
36 // |result| will be an object including 'bind' such that
37 // result.bind(foo, 1, 2, 3);
39 // is equivalent to Function.prototype.bind.call(foo, 1, 2, 3), and so on.
40 // This is a convenient way to save functions that user scripts may clobber.
42 // This is the source of a script which evaluates to a function, which should
43 // then be executed to save the bindings. This function is passed references
44 // to ScriptRunner::Apply and ScriptRunner::Save via the |natives| argument.
45 const char kScript
[] =
46 "(function(natives) {\n"
49 "// Used in the callback implementation, could potentially be clobbered.\n"
50 "function makeCallable(obj, target, isStatic, propertyNames) {\n"
51 " propertyNames.forEach(function(propertyName) {\n"
52 " var property = obj[propertyName];\n"
53 " target[propertyName] = function() {\n"
55 " var firstArgIndex = 0;\n"
57 " if (arguments.length == 0)\n"
58 " throw 'There must be at least one argument, the receiver';\n"
59 " recv = arguments[0];\n"
60 " firstArgIndex = 1;\n"
62 " return natives.Apply(\n"
63 " property, recv, arguments, firstArgIndex, arguments.length);\n"
68 "function saveBuiltin(builtin, protoPropertyNames, staticPropertyNames) {\n"
69 " var safe = function() {\n"
70 " throw 'Safe objects cannot be called nor constructed. ' +\n"
71 " 'Use $Foo.self() or new $Foo.self() instead.';\n"
73 " safe.self = builtin;\n"
74 " makeCallable(builtin.prototype, safe, false, protoPropertyNames);\n"
75 " if (staticPropertyNames)\n"
76 " makeCallable(builtin, safe, true, staticPropertyNames);\n"
77 " natives.Save(builtin.name, safe);\n"
80 "// Save only what is needed by the extension modules.\n"
81 "saveBuiltin(Object,\n"
82 " ['hasOwnProperty'],\n"
83 " ['create', 'defineProperty', 'freeze',\n"
84 " 'getOwnPropertyDescriptor', 'getPrototypeOf', 'keys']);\n"
85 "saveBuiltin(Function,\n"
86 " ['apply', 'bind', 'call']);\n"
87 "saveBuiltin(Array,\n"
88 " ['concat', 'forEach', 'indexOf', 'join', 'push', 'slice',\n"
89 " 'splice', 'map', 'filter']);\n"
90 "saveBuiltin(String,\n"
91 " ['indexOf', 'slice', 'split', 'substr', 'toUpperCase',\n"
93 "saveBuiltin(RegExp,\n"
95 "saveBuiltin(Error,\n"
97 " ['captureStackTrace']);\n"
99 "// JSON is trickier because extensions can override toJSON in\n"
100 "// incompatible ways, and we need to prevent that.\n"
101 "var builtinTypes = [\n"
102 " Object, Function, Array, String, Boolean, Number, Date, RegExp\n"
104 "var builtinToJSONs = builtinTypes.map(function(t) {\n"
105 " return t.toJSON;\n"
107 "var builtinArray = Array;\n"
108 "var builtinJSONStringify = JSON.stringify;\n"
109 "natives.Save('JSON', {\n"
110 " parse: JSON.parse,\n"
111 " stringify: function(obj) {\n"
112 " var savedToJSONs = new builtinArray(builtinTypes.length);\n"
114 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
116 " if (builtinTypes[i].prototype.toJSON !==\n"
117 " builtinToJSONs[i]) {\n"
118 " savedToJSONs[i] = builtinTypes[i].prototype.toJSON;\n"
119 " builtinTypes[i].prototype.toJSON = builtinToJSONs[i];\n"
125 " return builtinJSONStringify(obj);\n"
127 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
129 " if (i in savedToJSONs)\n"
130 " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
139 // Holds a compiled instance of |kScript| so that every instance of
140 // SafeBuiltins doesn't need to recompile it. Thread-safe.
141 // TODO(kalman): It would benefit to cache ModuleSystem's native handlers in
143 class CompiledScript
{
147 // Returns a handle to the instance of the compiled script, bound to the
148 // current context (assumed to be |context|).
149 v8::Local
<v8::Script
> GetForCurrentContext(v8::Local
<v8::Context
> context
) {
150 v8::Isolate
* isolate
= context
->GetIsolate();
151 DCHECK(v8::Isolate::GetCurrent() == isolate
&&
152 isolate
->GetCurrentContext() == context
);
153 v8::EscapableHandleScope
handle_scope(isolate
);
155 v8::Local
<v8::Script
> compiled_script
;
156 base::AutoLock
lock_scope(lock_
);
157 if (unbound_compiled_script_
.IsEmpty()) {
159 v8::Script::Compile(context
, ToV8StringUnsafe(isolate
, kScript
))
161 unbound_compiled_script_
.Reset(isolate
,
162 compiled_script
->GetUnboundScript());
165 v8::Local
<v8::UnboundScript
>::New(isolate
, unbound_compiled_script_
)
166 ->BindToCurrentContext();
168 return handle_scope
.Escape(compiled_script
);
172 // CompiledScript needs to be accessed on multiple threads - the main
173 // RenderThread, plus worker threads. Singletons are thread-safe, but
174 // access to |unbound_compiled_script_| must be locked.
177 // Use a v8::Persistent not a v8::Global because Globals attempt to reset the
178 // handle on destruction, and by the time CompiledScript is destroyed the
179 // renderer will be shutting down, and accessing into v8 will crash.
180 v8::Persistent
<v8::UnboundScript
> unbound_compiled_script_
;
182 DISALLOW_COPY_AND_ASSIGN(CompiledScript
);
185 base::LazyInstance
<CompiledScript
> g_compiled_script
=
186 LAZY_INSTANCE_INITIALIZER
;
188 // Returns a unique key to use as a hidden value in an object without a
189 // namespace collision.
190 v8::Local
<v8::String
> MakeKey(const char* name
, v8::Isolate
* isolate
) {
191 return ToV8StringUnsafe(isolate
,
192 base::StringPrintf("safe_builtins::%s", name
));
195 class ScriptNativeHandler
: public ObjectBackedNativeHandler
{
197 explicit ScriptNativeHandler(ScriptContext
* context
)
198 : ObjectBackedNativeHandler(context
) {
199 RouteFunction("Apply", base::Bind(&ScriptNativeHandler::Apply
,
200 base::Unretained(this)));
202 "Save", base::Bind(&ScriptNativeHandler::Save
, base::Unretained(this)));
205 ~ScriptNativeHandler() override
{ Invalidate(); }
208 // Takes 5 arguments:
209 // |function| The function that the arguments are being applied to.
210 // |recv| The receiver of the function call (i.e. the "this" value).
211 // |args| The arguments to the function call. This is actually an Arguments
212 // object but that isn't exposed in a convenient way in the v8 API, so we
213 // just use an Object and pass in |args_length| explicitly.
214 // |first_arg_index| The index of the first argument within |args|.
215 // This is 1 for prototype methods where the first argument to the
216 // function is the receiver. It's 0 for static methods which don't have a
218 // |args_length| The length of the argument list. This is needed because
219 // |args| is an Object which doesn't have a reliable concept of a length.
220 void Apply(const v8::FunctionCallbackInfo
<v8::Value
>& info
) {
221 CHECK(info
.Length() == 5 && info
[0]->IsFunction() && info
[2]->IsObject() &&
222 info
[3]->IsInt32() && info
[4]->IsInt32());
223 v8::Local
<v8::Function
> function
= info
[0].As
<v8::Function
>();
224 v8::Local
<v8::Value
> recv
= info
[1];
225 v8::Local
<v8::Object
> args
= info
[2].As
<v8::Object
>();
226 int first_arg_index
= info
[3].As
<v8::Int32
>()->Value();
227 int args_length
= info
[4].As
<v8::Int32
>()->Value();
229 v8::Local
<v8::Context
> v8_context
= context()->v8_context();
231 int argc
= args_length
- first_arg_index
;
232 scoped_ptr
<v8::Local
<v8::Value
> []> argv(new v8::Local
<v8::Value
>[argc
]);
233 for (int i
= 0; i
< argc
; ++i
) {
234 CHECK(IsTrue(args
->Has(v8_context
, i
+ first_arg_index
)));
235 // Getting a property value could throw an exception.
236 if (!GetProperty(v8_context
, args
, i
+ first_arg_index
, &argv
[i
]))
240 v8::Local
<v8::Value
> return_value
;
241 if (function
->Call(v8_context
, recv
, argc
, argv
.get())
242 .ToLocal(&return_value
))
243 info
.GetReturnValue().Set(return_value
);
246 void Save(const v8::FunctionCallbackInfo
<v8::Value
>& info
) {
247 CHECK(info
.Length() == 2 && info
[0]->IsString() && info
[1]->IsObject());
248 v8::Local
<v8::Object
> object
= info
[1].As
<v8::Object
>();
249 context()->v8_context()->Global()->SetHiddenValue(
250 MakeKey(*v8::String::Utf8Value(info
[0]), GetIsolate()), object
);
253 DISALLOW_COPY_AND_ASSIGN(ScriptNativeHandler
);
256 void DeleteScriptHandler(scoped_ptr
<ScriptNativeHandler
> script_handler
) {
257 // |script_handler| is a scoped_ptr so will delete itself.
262 SafeBuiltins::~SafeBuiltins() {}
265 scoped_ptr
<SafeBuiltins
> SafeBuiltins::Install(ScriptContext
* context
) {
266 v8::Isolate
* isolate
= context
->isolate();
267 v8::HandleScope
handle_scope(isolate
);
268 v8::Handle
<v8::Context
> v8_context
= context
->v8_context();
269 v8::Context::Scope
context_scope(v8_context
);
270 blink::WebScopedMicrotaskSuppression microtask_suppression
;
272 // Run the script to return a new function bound to this context.
273 v8::Local
<v8::Script
> script
=
274 g_compiled_script
.Get().GetForCurrentContext(v8_context
);
275 v8::Local
<v8::Value
> return_value
= script
->Run();
276 CHECK(return_value
->IsFunction());
277 v8::Local
<v8::Function
> script_function
= return_value
.As
<v8::Function
>();
279 // Call the script function to save builtins.
280 scoped_ptr
<ScriptNativeHandler
> script_handler(
281 new ScriptNativeHandler(context
));
282 v8::Local
<v8::Value
> args
[] = {script_handler
->NewInstance()};
283 CHECK(!script_function
->Call(v8_context
, v8_context
->Global(),
284 arraysize(args
), args
)
287 // Bind the lifetime of |script_handler| to |context|.
288 context
->AddInvalidationObserver(
289 base::Bind(&DeleteScriptHandler
, base::Passed(&script_handler
)));
291 // The SafeBuiltins instance itself is just a thin wrapper around accessing
292 // the hidden properties that were just installed on |context|.
293 return make_scoped_ptr(new SafeBuiltins(context
));
296 v8::Local
<v8::Object
> SafeBuiltins::GetArray() const {
297 return Load("Array");
300 v8::Local
<v8::Object
> SafeBuiltins::GetFunction() const {
301 return Load("Function");
304 v8::Local
<v8::Object
> SafeBuiltins::GetJSON() const {
308 v8::Local
<v8::Object
> SafeBuiltins::GetObjekt() const {
309 return Load("Object");
312 v8::Local
<v8::Object
> SafeBuiltins::GetRegExp() const {
313 return Load("RegExp");
316 v8::Local
<v8::Object
> SafeBuiltins::GetString() const {
317 return Load("String");
320 v8::Local
<v8::Object
> SafeBuiltins::GetError() const {
321 return Load("Error");
324 SafeBuiltins::SafeBuiltins(ScriptContext
* context
) : context_(context
) {}
326 v8::Local
<v8::Object
> SafeBuiltins::Load(const char* name
) const {
327 v8::Local
<v8::Value
> value
= context_
->v8_context()->Global()->GetHiddenValue(
328 MakeKey(name
, context_
->isolate()));
329 CHECK(!IsEmptyOrUndefined(value
));
330 CHECK(value
->IsObject()) << name
;
331 return v8::Local
<v8::Object
>::Cast(value
);
334 } // namespace extensions