test_instance_deprecated.cc 10 KB

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