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