1// Copyright (c) 2012 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.
4
5#include "ppapi/tests/test_instance_deprecated.h"
6
7#include <assert.h>
8
9#include "ppapi/c/ppb_var.h"
10#include "ppapi/cpp/module.h"
11#include "ppapi/tests/testing_instance.h"
12
13static const char kSetValueFunction[] = "SetValue";
14static const char kSetExceptionFunction[] = "SetException";
15static const char kReturnValueFunction[] = "ReturnValue";
16
17InstanceSO::InstanceSO(TestInstance* i)
18    : test_instance_(i),
19      testing_interface_(i->testing_interface()) {
20}
21
22InstanceSO::~InstanceSO() {
23  if (test_instance_)
24    test_instance_->clear_instance_so();
25}
26
27bool InstanceSO::HasMethod(const pp::Var& name, pp::Var* exception) {
28  if (!name.is_string())
29    return false;
30  return name.AsString() == kSetValueFunction ||
31         name.AsString() == kSetExceptionFunction ||
32         name.AsString() == kReturnValueFunction;
33}
34
35pp::Var InstanceSO::Call(const pp::Var& method_name,
36                         const std::vector<pp::Var>& args,
37                         pp::Var* exception) {
38  if (!method_name.is_string())
39    return false;
40  std::string name = method_name.AsString();
41
42  if (name == kSetValueFunction) {
43    if (args.size() != 1 || !args[0].is_string())
44      *exception = pp::Var("Bad argument to SetValue(<string>)");
45    else if (test_instance_)
46      test_instance_->set_string(args[0].AsString());
47  } else if (name == kSetExceptionFunction) {
48    if (args.size() != 1 || !args[0].is_string())
49      *exception = pp::Var("Bad argument to SetException(<string>)");
50    else
51      *exception = args[0];
52  } else if (name == kReturnValueFunction) {
53    if (args.size() != 1)
54      *exception = pp::Var("Need single arg to call ReturnValue");
55    else
56      return args[0];
57  } else {
58    *exception = pp::Var("Bad function call");
59  }
60
61  return pp::Var();
62}
63
64REGISTER_TEST_CASE(Instance);
65
66TestInstance::TestInstance(TestingInstance* instance)
67    : TestCase(instance),
68      instance_so_(NULL) {}
69
70bool TestInstance::Init() {
71  return true;
72}
73
74TestInstance::~TestInstance() {
75  ResetTestObject();
76  if (testing_interface_->IsOutOfProcess() == PP_FALSE) {
77    // This should cause the instance object's descructor to be called.
78    testing_interface_->RunV8GC(instance_->pp_instance());
79
80    // Test a post-condition which ensures the instance objects destructor is
81    // called. This only works reliably in-process. Out-of-process, it only
82    // can work when the renderer stays alive a short while after the plugin
83    // instance is destroyed. If the renderer is being shut down, too much
84    // happens asynchronously for the out-of-process case to work reliably. In
85    // particular:
86    //   - The Var ReleaseObject message is asynchronous.
87    //   - The PPB_Var_Deprecated host-side proxy posts a task to actually
88    //     release the object when the ReleaseObject message is received.
89    //   - The PPP_Class Deallocate message is asynchronous.
90    // At time of writing this comment, if you modify the code so that the above
91    // happens synchronously, and you remove the restriction that the plugin
92    // can't be unblocked by a sync message, then this check actually passes
93    // reliably for out-of-process. But we don't want to make any of those
94    // changes so we just skip the check.
95    PP_DCHECK(!instance_so_);
96  } else {
97    // Out-of-process, this destructor might not actually get invoked. Clear
98    // the InstanceSOs reference to the instance so there is no UAF.
99    if (instance_so_)
100      instance_so_->clear_test_instance();
101  }
102
103  // Save the fact that we were destroyed in sessionStorage. This tests that
104  // we can ExecuteScript at instance destruction without crashing. It also
105  // allows us to check that ExecuteScript will run and succeed in certain
106  // cases. In particular, when the instance is destroyed by normal DOM
107  // deletion, ExecuteScript will actually work. See
108  // TestExecuteScriptInInstanceShutdown for that test. Note, however, that
109  // ExecuteScript will *not* have an effect when the instance is destroyed
110  // because the renderer was shut down.
111  pp::Var ret = instance()->ExecuteScript(
112      "sessionStorage.setItem('instance_destroyed', 'true');");
113}
114
115void TestInstance::RunTests(const std::string& filter) {
116  RUN_TEST(ExecuteScript, filter);
117  RUN_TEST(RecursiveObjects, filter);
118  RUN_TEST(LeakedObjectDestructors, filter);
119  RUN_TEST(SetupExecuteScriptAtInstanceShutdown, filter);
120  RUN_TEST(ExecuteScriptAtInstanceShutdown, filter);
121}
122
123void TestInstance::LeakReferenceAndIgnore(const pp::Var& leaked) {
124  static const PPB_Var* var_interface = static_cast<const PPB_Var*>(
125        pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE));
126  var_interface->AddRef(leaked.pp_var());
127  IgnoreLeakedVar(leaked.pp_var().value.as_id);
128}
129
130pp::deprecated::ScriptableObject* TestInstance::CreateTestObject() {
131  if (!instance_so_)
132    instance_so_ = new InstanceSO(this);
133  return instance_so_;
134}
135
136std::string TestInstance::TestExecuteScript() {
137  // Simple call back into the plugin.
138  pp::Var exception;
139  pp::Var ret = instance_->ExecuteScript(
140      "document.getElementById('plugin').SetValue('hello, world');",
141      &exception);
142  ASSERT_TRUE(ret.is_undefined());
143  ASSERT_TRUE(exception.is_undefined());
144  ASSERT_TRUE(string_ == "hello, world");
145
146  // Return values from the plugin should be returned.
147  ret = instance_->ExecuteScript(
148      "document.getElementById('plugin').ReturnValue('return value');",
149      &exception);
150  ASSERT_TRUE(ret.is_string() && ret.AsString() == "return value");
151  ASSERT_TRUE(exception.is_undefined());
152
153  // Exception thrown by the plugin should be caught.
154  ret = instance_->ExecuteScript(
155      "document.getElementById('plugin').SetException('plugin exception');",
156      &exception);
157  ASSERT_TRUE(ret.is_undefined());
158  ASSERT_TRUE(exception.is_string());
159  // Due to a limitation in the implementation of TryCatch, it doesn't actually
160  // pass the strings up. Since this is a trusted only interface, we've decided
161  // not to bother fixing this for now.
162
163  // Exception caused by string evaluation should be caught.
164  exception = pp::Var();
165  ret = instance_->ExecuteScript("document.doesntExist()", &exception);
166  ASSERT_TRUE(ret.is_undefined());
167  ASSERT_TRUE(exception.is_string());  // Don't know exactly what it will say.
168
169  PASS();
170}
171
172// A scriptable object that contains other scriptable objects recursively. This
173// is used to help verify that our scriptable object clean-up code works
174// properly.
175class ObjectWithChildren : public pp::deprecated::ScriptableObject {
176 public:
177  ObjectWithChildren(TestInstance* i, int num_descendents) {
178    if (num_descendents > 0) {
179      child_ = pp::VarPrivate(i->instance(),
180                              new ObjectWithChildren(i, num_descendents - 1));
181    }
182  }
183  struct IgnoreLeaks {};
184  ObjectWithChildren(TestInstance* i, int num_descendents, IgnoreLeaks) {
185    if (num_descendents > 0) {
186      child_ = pp::VarPrivate(i->instance(),
187                              new ObjectWithChildren(i, num_descendents - 1,
188                                                     IgnoreLeaks()));
189      i->IgnoreLeakedVar(child_.pp_var().value.as_id);
190    }
191  }
192 private:
193  pp::VarPrivate child_;
194};
195
196std::string TestInstance::TestRecursiveObjects() {
197  const int kNumChildren = 20;
198  {
199    // These should be deleted when we exit scope, so should not leak.
200    pp::VarPrivate not_leaked(instance(), new ObjectWithChildren(this,
201                                                                 kNumChildren));
202  }
203  // We need to run the GC multiple times until all of the vars are released.
204  // Each GC invocation will result in releasing a var, which will result in its
205  // children not having any references, allowing them also to be collected.
206  for (int i = 0; i < kNumChildren; ++i)
207    testing_interface_->RunV8GC(instance_->pp_instance());
208
209  // Leak some, but tell TestCase to ignore the leaks. This test is run and then
210  // reloaded (see ppapi_uitest.cc). If these aren't cleaned up when the first
211  // run is torn down, they will show up as leaks in the second run.
212  // NOTE: The ScriptableObjects are actually leaked, but they should be removed
213  //       from the tracker. See below for a test that verifies that the
214  //       destructor is not run.
215  pp::VarPrivate leaked(
216      instance(),
217      new ObjectWithChildren(this, kNumChildren,
218                             ObjectWithChildren::IgnoreLeaks()));
219  // Now leak a reference to the root object. This should force the root and
220  // all its descendents to stay in the tracker.
221  LeakReferenceAndIgnore(leaked);
222
223  PASS();
224}
225
226// A scriptable object that should cause a crash if its destructor is run. We
227// don't run the destructor for objects which the plugin leaks. This is to
228// prevent them doing dangerous things at cleanup time, such as executing script
229// or creating new objects.
230class BadDestructorObject : public pp::deprecated::ScriptableObject {
231 public:
232  BadDestructorObject() {}
233  ~BadDestructorObject() {
234    assert(false);
235  }
236};
237
238std::string TestInstance::TestLeakedObjectDestructors() {
239  pp::VarPrivate leaked(instance(), new BadDestructorObject());
240  // Leak a reference so it gets deleted on instance shutdown.
241  LeakReferenceAndIgnore(leaked);
242  PASS();
243}
244
245std::string TestInstance::TestSetupExecuteScriptAtInstanceShutdown() {
246  // This test only exists so that it can be run before
247  // TestExecuteScriptAtInstanceShutdown. See the comment for that test.
248  pp::Var exception;
249  pp::Var result = instance()->ExecuteScript(
250      "sessionStorage.removeItem('instance_destroyed');", &exception);
251  ASSERT_TRUE(exception.is_undefined());
252  ASSERT_TRUE(result.is_undefined());
253  PASS();
254}
255
256std::string TestInstance::TestExecuteScriptAtInstanceShutdown() {
257  // This test relies on the previous test being run in the same browser
258  // session, but in such a way that the instance is destroyed. See
259  // chrome/test/ppapi/ppapi_browsertest.cc for how the navigation happens.
260  //
261  // Given those constraints, ~TestInstance should have been invoked to set
262  // instance_destroyed in sessionStorage. So all we have to do is make sure
263  // that it was set as expected.
264  pp::Var result = instance()->ExecuteScript(
265      "sessionStorage.getItem('instance_destroyed');");
266  ASSERT_TRUE(result.is_string());
267  ASSERT_EQ(std::string("true"), result.AsString());
268  instance()->ExecuteScript("sessionStorage.removeItem('instance_destroyed');");
269
270  PASS();
271}
272
273