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