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 <stddef.h>
6 #include <stdint.h>
7 
8 #include <string>
9 #include <vector>
10 
11 #include "base/strings/string_number_conversions.h"
12 #include "base/threading/platform_thread.h"
13 #include "ppapi/c/pp_var.h"
14 #include "ppapi/c/ppb_var.h"
15 #include "ppapi/proxy/ppapi_proxy_test.h"
16 #include "ppapi/shared_impl/ppb_var_shared.h"
17 
18 namespace {
VarToString(const PP_Var & var,const PPB_Var * ppb_var)19 std::string VarToString(const PP_Var& var, const PPB_Var* ppb_var) {
20   uint32_t len = 0;
21   const char* utf8 = ppb_var->VarToUtf8(var, &len);
22   return std::string(utf8, len);
23 }
24 const size_t kNumStrings = 100;
25 const size_t kNumThreads = 20;
26 const int kRefsToAdd = 20;
27 }  // namespace
28 
29 namespace ppapi {
30 namespace proxy {
31 
32 class PPB_VarTest : public PluginProxyTest {
33  public:
PPB_VarTest()34   PPB_VarTest()
35       : test_strings_(kNumStrings), vars_(kNumStrings),
36         ppb_var_(ppapi::PPB_Var_Shared::GetVarInterface1_2()) {
37     // Set the value of test_strings_[i] to "i".
38     for (size_t i = 0; i < kNumStrings; ++i)
39       test_strings_[i] = base::NumberToString(i);
40   }
41  protected:
42   std::vector<std::string> test_strings_;
43   std::vector<PP_Var> vars_;
44   const PPB_Var* ppb_var_;
45 };
46 
47 // Test basic String operations.
TEST_F(PPB_VarTest,Strings)48 TEST_F(PPB_VarTest, Strings) {
49   for (size_t i = 0; i < kNumStrings; ++i) {
50     vars_[i] = ppb_var_->VarFromUtf8(
51         test_strings_[i].c_str(),
52         static_cast<uint32_t>(test_strings_[i].length()));
53     EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
54   }
55   // At this point, they should each have a ref count of 1. Add some more.
56   for (int ref = 0; ref < kRefsToAdd; ++ref) {
57     for (size_t i = 0; i < kNumStrings; ++i) {
58       ppb_var_->AddRef(vars_[i]);
59       // Make sure the string is still there with the right value.
60       EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
61     }
62   }
63   for (int ref = 0; ref < kRefsToAdd; ++ref) {
64     for (size_t i = 0; i < kNumStrings; ++i) {
65       ppb_var_->Release(vars_[i]);
66       // Make sure the string is still there with the right value.
67       EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
68     }
69   }
70   // Now remove the ref counts for each string and make sure they are gone.
71   for (size_t i = 0; i < kNumStrings; ++i) {
72     ppb_var_->Release(vars_[i]);
73     uint32_t len = 10;
74     const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len);
75     EXPECT_EQ(NULL, utf8);
76     EXPECT_EQ(0u, len);
77   }
78 }
79 
80 // PPB_VarTest.Threads tests string operations accessed by multiple threads.
81 namespace {
82 // These three delegate classes which precede the test are for use with
83 // PlatformThread. The test goes roughly like this:
84 // 1) Spawn kNumThreads 'CreateVar' threads, giving each a roughly equal subset
85 //    of test_strings_ to 'create'. Each 'CreateVar' thread also converts its
86 //    set of vars back in to strings so that the main test thread can verify
87 //    their values were correctly converted.
88 // 2) Spawn kNumThreads 'ChangeRefVar' threads. Each of these threads will
89 //    incremement & decrement the reference count of ALL vars kRefsToAdd times.
90 //    Finally, each thread adds 1 ref count. This leaves each var with a ref-
91 //    count of |kNumThreads + 1|. The main test thread removes a ref, leaving
92 //    each var with a ref-count of |kNumThreads|.
93 // 3) Spawn kNumThreads 'RemoveVar' threads. Each of these threads releases each
94 //    var once. Once all the threads have finished, there should be no vars
95 //    left.
96 class CreateVarThreadDelegate : public base::PlatformThread::Delegate {
97  public:
98   // |strings_in|, |vars|, and |strings_out| are arrays, and size is their size.
99   // For each |strings_in[i]|, we will set |vars[i]| using that value. Then we
100   // read the var back out to |strings_out[i]|.
CreateVarThreadDelegate(const std::string * strings_in,PP_Var * vars_out,std::string * strings_out,size_t size)101   CreateVarThreadDelegate(const std::string* strings_in,
102                           PP_Var* vars_out,
103                           std::string* strings_out,
104                           size_t size)
105       : strings_in_(strings_in),
106         vars_out_(vars_out),
107         strings_out_(strings_out),
108         size_(size) {}
~CreateVarThreadDelegate()109   virtual ~CreateVarThreadDelegate() {}
ThreadMain()110   virtual void ThreadMain() {
111     const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
112     for (size_t i = 0; i < size_; ++i) {
113       vars_out_[i] = ppb_var->VarFromUtf8(
114           strings_in_[i].c_str(),
115           static_cast<uint32_t>(strings_in_[i].length()));
116       strings_out_[i] = VarToString(vars_out_[i], ppb_var);
117     }
118   }
119  private:
120   const std::string* strings_in_;
121   PP_Var* vars_out_;
122   std::string* strings_out_;
123   size_t size_;
124 };
125 
126 // A thread that will increment and decrement the reference count of every var
127 // multiple times.
128 class ChangeRefVarThreadDelegate : public base::PlatformThread::Delegate {
129  public:
ChangeRefVarThreadDelegate(const std::vector<PP_Var> & vars)130   ChangeRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) {
131   }
~ChangeRefVarThreadDelegate()132   virtual ~ChangeRefVarThreadDelegate() {}
ThreadMain()133   virtual void ThreadMain() {
134     const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
135     // Increment and decrement the reference count for each var kRefsToAdd
136     // times. Note that we always AddRef once before doing the matching Release,
137     // to ensure that we never accidentally release the last reference.
138     for (int ref = 0; ref < kRefsToAdd; ++ref) {
139       for (size_t i = 0; i < kNumStrings; ++i) {
140         ppb_var->AddRef(vars_[i]);
141         ppb_var->Release(vars_[i]);
142       }
143     }
144     // Now add 1 ref to each Var. The net result is that all Vars will have a
145     // ref-count of (kNumThreads + 1) after this. That will allow us to have all
146     // threads release all vars later.
147     for (size_t i = 0; i < kNumStrings; ++i) {
148       ppb_var->AddRef(vars_[i]);
149     }
150   }
151  private:
152   std::vector<PP_Var> vars_;
153 };
154 
155 // A thread that will decrement the reference count of every var once.
156 class RemoveRefVarThreadDelegate : public base::PlatformThread::Delegate {
157  public:
RemoveRefVarThreadDelegate(const std::vector<PP_Var> & vars)158   RemoveRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) {
159   }
~RemoveRefVarThreadDelegate()160   virtual ~RemoveRefVarThreadDelegate() {}
ThreadMain()161   virtual void ThreadMain() {
162     const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_2();
163     for (size_t i = 0; i < kNumStrings; ++i) {
164       ppb_var->Release(vars_[i]);
165     }
166   }
167  private:
168   std::vector<PP_Var> vars_;
169 };
170 
171 }  // namespace
172 
TEST_F(PPB_VarTest,Threads)173 TEST_F(PPB_VarTest, Threads) {
174   std::vector<base::PlatformThreadHandle> create_var_threads(kNumThreads);
175   std::vector<CreateVarThreadDelegate> create_var_delegates;
176   // The strings that the threads will re-extract from Vars (so we can check
177   // that they match the original strings).
178   std::vector<std::string> strings_out(kNumStrings);
179   size_t strings_per_thread = kNumStrings/kNumThreads;
180   // Give each thread an equal slice of strings to turn in to vars. (Except the
181   // last thread may get fewer if kNumStrings is not evenly divisible by
182   // kNumThreads).
183   for (size_t slice_start= 0; slice_start < kNumStrings;
184        slice_start += strings_per_thread) {
185     create_var_delegates.push_back(CreateVarThreadDelegate(
186         &test_strings_[slice_start], &vars_[slice_start],
187         &strings_out[slice_start],
188         std::min(strings_per_thread, kNumStrings - slice_start)));
189   }
190   // Now run then join all the threads.
191   for (size_t i = 0; i < kNumThreads; ++i)
192     base::PlatformThread::Create(0, &create_var_delegates[i],
193                                  &create_var_threads[i]);
194   for (size_t i = 0; i < kNumThreads; ++i)
195     base::PlatformThread::Join(create_var_threads[i]);
196   // Now check that the strings have the expected values.
197   EXPECT_EQ(test_strings_, strings_out);
198 
199   // Tinker with the reference counts in a multithreaded way.
200   std::vector<base::PlatformThreadHandle> change_ref_var_threads(kNumThreads);
201   std::vector<ChangeRefVarThreadDelegate> change_ref_var_delegates;
202   for (size_t i = 0; i < kNumThreads; ++i)
203     change_ref_var_delegates.push_back(ChangeRefVarThreadDelegate(vars_));
204   for (size_t i = 0; i < kNumThreads; ++i) {
205     base::PlatformThread::Create(0, &change_ref_var_delegates[i],
206                                  &change_ref_var_threads[i]);
207   }
208   for (size_t i = 0; i < kNumThreads; ++i)
209     base::PlatformThread::Join(change_ref_var_threads[i]);
210 
211   // Now each var has a refcount of (kNumThreads + 1). Let's decrement each var
212   // once so that every 'RemoveRef' thread (spawned below) owns 1 reference, and
213   // when the last one removes a ref, the Var will be deleted.
214   for (size_t i = 0; i < kNumStrings; ++i) {
215     ppb_var_->Release(vars_[i]);
216   }
217 
218   // Check that all vars are still valid and have the values we expect.
219   for (size_t i = 0; i < kNumStrings; ++i)
220     EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_));
221 
222   // Remove the last reference counts for all vars.
223   std::vector<base::PlatformThreadHandle> remove_ref_var_threads(kNumThreads);
224   std::vector<RemoveRefVarThreadDelegate> remove_ref_var_delegates;
225   for (size_t i = 0; i < kNumThreads; ++i)
226     remove_ref_var_delegates.push_back(RemoveRefVarThreadDelegate(vars_));
227   for (size_t i = 0; i < kNumThreads; ++i) {
228     base::PlatformThread::Create(0, &remove_ref_var_delegates[i],
229                                  &remove_ref_var_threads[i]);
230   }
231   for (size_t i = 0; i < kNumThreads; ++i)
232     base::PlatformThread::Join(remove_ref_var_threads[i]);
233 
234   // All the vars should no longer represent valid strings.
235   for (size_t i = 0; i < kNumStrings; ++i) {
236     uint32_t len = 10;
237     const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len);
238     EXPECT_EQ(NULL, utf8);
239     EXPECT_EQ(0u, len);
240   }
241 }
242 
243 }  // namespace proxy
244 }  // namespace ppapi
245