Home | History | Annotate | Download | only in tests
      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 
     13 static const char kSetValueFunction[] = "SetValue";
     14 static const char kSetExceptionFunction[] = "SetException";
     15 static const char kReturnValueFunction[] = "ReturnValue";
     16 
     17 InstanceSO::InstanceSO(TestInstance* i)
     18     : test_instance_(i),
     19       testing_interface_(i->testing_interface()) {
     20 }
     21 
     22 InstanceSO::~InstanceSO() {
     23   if (test_instance_)
     24     test_instance_->clear_instance_so();
     25 }
     26 
     27 bool 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 
     35 pp::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 
     64 REGISTER_TEST_CASE(Instance);
     65 
     66 TestInstance::TestInstance(TestingInstance* instance)
     67     : TestCase(instance),
     68       instance_so_(NULL) {}
     69 
     70 bool TestInstance::Init() {
     71   return true;
     72 }
     73 
     74 TestInstance::~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 
    115 void 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 
    123 void 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 
    130 pp::deprecated::ScriptableObject* TestInstance::CreateTestObject() {
    131   if (!instance_so_)
    132     instance_so_ = new InstanceSO(this);
    133   return instance_so_;
    134 }
    135 
    136 std::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.
    175 class 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 
    196 std::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.
    230 class BadDestructorObject : public pp::deprecated::ScriptableObject {
    231  public:
    232   BadDestructorObject() {}
    233   ~BadDestructorObject() {
    234     assert(false);
    235   }
    236 };
    237 
    238 std::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 
    245 std::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 
    256 std::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