cc: For debugging, show checkerboards with the tile border color
[chromium-blink-merge.git] / extensions / renderer / safe_builtins.cc
blob2103ff226ab709a4d008395f02b8e7a8f77ae796
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"
7 #include "base/bind.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;
23 namespace {
25 // Preface:
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.
33 // E.g. given
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"
47 "'use strict';\n"
48 "\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"
54 " var recv = obj;\n"
55 " var firstArgIndex = 0;\n"
56 " if (!isStatic) {\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"
61 " }\n"
62 " return natives.Apply(\n"
63 " property, recv, arguments, firstArgIndex, arguments.length);\n"
64 " };\n"
65 " });\n"
66 "}\n"
67 "\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"
72 " };\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"
78 "}\n"
79 "\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"
92 " 'replace']);\n"
93 "saveBuiltin(RegExp,\n"
94 " ['test']);\n"
95 "saveBuiltin(Error,\n"
96 " [],\n"
97 " ['captureStackTrace']);\n"
98 "\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"
103 "];\n"
104 "var builtinToJSONs = builtinTypes.map(function(t) {\n"
105 " return t.toJSON;\n"
106 "});\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"
113 " try {\n"
114 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
115 " try {\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"
120 " }\n"
121 " } catch (e) {}\n"
122 " }\n"
123 " } catch (e) {}\n"
124 " try {\n"
125 " return builtinJSONStringify(obj);\n"
126 " } finally {\n"
127 " for (var i = 0; i < builtinTypes.length; ++i) {\n"
128 " try {\n"
129 " if (i in savedToJSONs)\n"
130 " builtinTypes[i].prototype.toJSON = savedToJSONs[i];\n"
131 " } catch (e) {}\n"
132 " }\n"
133 " }\n"
134 " }\n"
135 "});\n"
136 "\n"
137 "});\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
142 // this way as well.
143 class CompiledScript {
144 public:
145 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()) {
158 compiled_script =
159 v8::Script::Compile(context, ToV8StringUnsafe(isolate, kScript))
160 .ToLocalChecked();
161 unbound_compiled_script_.Reset(isolate,
162 compiled_script->GetUnboundScript());
163 } else {
164 compiled_script =
165 v8::Local<v8::UnboundScript>::New(isolate, unbound_compiled_script_)
166 ->BindToCurrentContext();
168 return handle_scope.Escape(compiled_script);
171 private:
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.
175 base::Lock lock_;
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 {
196 public:
197 explicit ScriptNativeHandler(ScriptContext* context)
198 : ObjectBackedNativeHandler(context) {
199 RouteFunction("Apply", base::Bind(&ScriptNativeHandler::Apply,
200 base::Unretained(this)));
201 RouteFunction(
202 "Save", base::Bind(&ScriptNativeHandler::Save, base::Unretained(this)));
205 ~ScriptNativeHandler() override { Invalidate(); }
207 private:
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
217 // receiver.
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]))
237 return;
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.
260 } // namespace
262 SafeBuiltins::~SafeBuiltins() {}
264 // static
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)
285 .IsEmpty());
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 {
305 return Load("JSON");
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