1 // Copyright 2013 The Chromium Authors
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 "components/nacl/browser/pnacl_host.h"
6
7 #include <stddef.h>
8 #include <stdio.h>
9 #include <string.h>
10
11 #include <string>
12
13 #include "base/compiler_specific.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/functional/bind.h"
16 #include "base/memory/raw_ptr.h"
17 #include "base/run_loop.h"
18 #include "build/build_config.h"
19 #include "components/nacl/browser/pnacl_translation_cache.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/test/browser_task_environment.h"
22 #include "content/public/test/test_utils.h"
23 #include "net/base/test_completion_callback.h"
24 #include "net/disk_cache/disk_cache.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 namespace pnacl {
28 namespace {
29
30 // Size of a buffer used for writing and reading from a file.
31 const size_t kBufferSize = 16u;
32
33 } // namespace
34
35 class PnaclHostTest : public testing::Test {
36 protected:
PnaclHostTest()37 PnaclHostTest()
38 : host_(nullptr),
39 temp_callback_count_(0),
40 write_callback_count_(0),
41 task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}
SetUp()42 void SetUp() override {
43 host_ = PnaclHost::GetInstance();
44 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
45 host_->InitForTest(temp_dir_.GetPath(), true);
46 base::RunLoop().RunUntilIdle();
47 EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_);
48 }
TearDown()49 void TearDown() override {
50 EXPECT_EQ(0U, host_->pending_translations());
51 // Give the host a chance to de-init the backend, and then delete it.
52 host_->RendererClosing(0);
53 content::RunAllTasksUntilIdle();
54 disk_cache::FlushCacheThreadForTesting();
55 EXPECT_EQ(PnaclHost::CacheUninitialized, host_->cache_state_);
56 }
GetCacheSize()57 int GetCacheSize() { return host_->disk_cache_->Size(); }
CacheIsInitialized()58 int CacheIsInitialized() {
59 return host_->cache_state_ == PnaclHost::CacheReady;
60 }
ReInitBackend()61 void ReInitBackend() {
62 host_->InitForTest(temp_dir_.GetPath(), true);
63 base::RunLoop().RunUntilIdle();
64 EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_);
65 }
66
67 public: // Required for derived classes to bind this method
68 // Callbacks used by tests which call GetNexeFd.
69 // CallbackExpectMiss checks that the fd is valid and a miss is reported,
70 // and also writes some data into the file, which is read back by
71 // CallbackExpectHit
CallbackExpectMiss(const base::File & file,bool is_hit)72 void CallbackExpectMiss(const base::File& file, bool is_hit) {
73 EXPECT_FALSE(is_hit);
74 ASSERT_TRUE(file.IsValid());
75 base::File::Info info;
76 base::File* mutable_file = const_cast<base::File*>(&file);
77 EXPECT_TRUE(mutable_file->GetInfo(&info));
78 EXPECT_FALSE(info.is_directory);
79 EXPECT_EQ(0LL, info.size);
80 char str[kBufferSize];
81 memset(str, 0x0, kBufferSize);
82 snprintf(str, kBufferSize, "testdata%d", ++write_callback_count_);
83 EXPECT_EQ(kBufferSize, static_cast<size_t>(UNSAFE_TODO(
84 mutable_file->Write(0, str, kBufferSize))));
85 temp_callback_count_++;
86 }
CallbackExpectHit(const base::File & file,bool is_hit)87 void CallbackExpectHit(const base::File& file, bool is_hit) {
88 EXPECT_TRUE(is_hit);
89 ASSERT_TRUE(file.IsValid());
90 base::File::Info info;
91 base::File* mutable_file = const_cast<base::File*>(&file);
92 EXPECT_TRUE(mutable_file->GetInfo(&info));
93 EXPECT_FALSE(info.is_directory);
94 EXPECT_EQ(kBufferSize, static_cast<size_t>(info.size));
95 char data[kBufferSize];
96 memset(data, 0x0, kBufferSize);
97 char str[kBufferSize];
98 memset(str, 0x0, kBufferSize);
99 snprintf(str, kBufferSize, "testdata%d", write_callback_count_);
100 EXPECT_EQ(kBufferSize, static_cast<size_t>(UNSAFE_TODO(
101 mutable_file->Read(0, data, kBufferSize))));
102 EXPECT_STREQ(str, data);
103 temp_callback_count_++;
104 }
105
106 protected:
107 raw_ptr<PnaclHost> host_;
108 int temp_callback_count_;
109 int write_callback_count_;
110 content::BrowserTaskEnvironment task_environment_;
111 base::ScopedTempDir temp_dir_;
112 };
113
GetTestCacheInfo()114 static nacl::PnaclCacheInfo GetTestCacheInfo() {
115 nacl::PnaclCacheInfo info;
116 info.pexe_url = GURL("http://www.google.com");
117 info.abi_version = 0;
118 info.opt_level = 0;
119 info.has_no_store_header = false;
120 info.use_subzero = false;
121 return info;
122 }
123
124 #define GET_NEXE_FD(renderer, instance, incognito, info, expect_hit) \
125 do { \
126 SCOPED_TRACE(""); \
127 host_->GetNexeFd( \
128 renderer, instance, incognito, info, \
129 base::BindRepeating(expect_hit ? &PnaclHostTest::CallbackExpectHit \
130 : &PnaclHostTest::CallbackExpectMiss, \
131 base::Unretained(this))); \
132 } while (0)
133
TEST_F(PnaclHostTest,BasicMiss)134 TEST_F(PnaclHostTest, BasicMiss) {
135 nacl::PnaclCacheInfo info = GetTestCacheInfo();
136 // Test cold miss.
137 GET_NEXE_FD(0, 0, false, info, false);
138 EXPECT_EQ(1U, host_->pending_translations());
139 content::RunAllTasksUntilIdle();
140 EXPECT_EQ(1U, host_->pending_translations());
141 EXPECT_EQ(1, temp_callback_count_);
142 host_->TranslationFinished(0, 0, true);
143 content::RunAllTasksUntilIdle();
144 EXPECT_EQ(0U, host_->pending_translations());
145 // Test that a different cache info field also misses.
146 info.etag = std::string("something else");
147 GET_NEXE_FD(0, 0, false, info, false);
148 content::RunAllTasksUntilIdle();
149 EXPECT_EQ(2, temp_callback_count_);
150 EXPECT_EQ(1U, host_->pending_translations());
151 host_->RendererClosing(0);
152 content::RunAllTasksUntilIdle();
153 // Check that the cache has de-initialized after the last renderer goes away.
154 EXPECT_FALSE(CacheIsInitialized());
155 }
156
TEST_F(PnaclHostTest,BadArguments)157 TEST_F(PnaclHostTest, BadArguments) {
158 nacl::PnaclCacheInfo info = GetTestCacheInfo();
159 GET_NEXE_FD(0, 0, false, info, false);
160 EXPECT_EQ(1U, host_->pending_translations());
161 host_->TranslationFinished(0, 1, true); // nonexistent translation
162 EXPECT_EQ(1U, host_->pending_translations());
163 host_->RendererClosing(1); // nonexistent renderer
164 EXPECT_EQ(1U, host_->pending_translations());
165 content::RunAllTasksUntilIdle();
166 EXPECT_EQ(1, temp_callback_count_);
167 host_->RendererClosing(0); // close without finishing
168 }
169
TEST_F(PnaclHostTest,BasicHit)170 TEST_F(PnaclHostTest, BasicHit) {
171 nacl::PnaclCacheInfo info = GetTestCacheInfo();
172 GET_NEXE_FD(0, 0, false, info, false);
173 content::RunAllTasksUntilIdle();
174 EXPECT_EQ(1, temp_callback_count_);
175 host_->TranslationFinished(0, 0, true);
176 content::RunAllTasksUntilIdle();
177 GET_NEXE_FD(0, 1, false, info, true);
178 content::RunAllTasksUntilIdle();
179 EXPECT_EQ(2, temp_callback_count_);
180 EXPECT_EQ(0U, host_->pending_translations());
181 }
182
TEST_F(PnaclHostTest,TranslationErrors)183 TEST_F(PnaclHostTest, TranslationErrors) {
184 nacl::PnaclCacheInfo info = GetTestCacheInfo();
185 GET_NEXE_FD(0, 0, false, info, false);
186 // Early abort, before temp file request returns
187 host_->TranslationFinished(0, 0, false);
188 content::RunAllTasksUntilIdle();
189 EXPECT_EQ(0U, host_->pending_translations());
190 EXPECT_EQ(0, temp_callback_count_);
191 // The backend will have been freed when the query comes back and there
192 // are no pending translations.
193 EXPECT_FALSE(CacheIsInitialized());
194 ReInitBackend();
195 // Check that another request for the same info misses successfully.
196 GET_NEXE_FD(0, 0, false, info, false);
197 content::RunAllTasksUntilIdle();
198 host_->TranslationFinished(0, 0, true);
199 content::RunAllTasksUntilIdle();
200 EXPECT_EQ(1, temp_callback_count_);
201 EXPECT_EQ(0U, host_->pending_translations());
202
203 // Now try sending the error after the temp file request returns
204 info.abi_version = 222;
205 GET_NEXE_FD(0, 0, false, info, false);
206 content::RunAllTasksUntilIdle();
207 EXPECT_EQ(2, temp_callback_count_);
208 host_->TranslationFinished(0, 0, false);
209 content::RunAllTasksUntilIdle();
210 EXPECT_EQ(0U, host_->pending_translations());
211 // Check another successful miss
212 GET_NEXE_FD(0, 0, false, info, false);
213 content::RunAllTasksUntilIdle();
214 EXPECT_EQ(3, temp_callback_count_);
215 host_->TranslationFinished(0, 0, false);
216 EXPECT_EQ(0U, host_->pending_translations());
217 }
218
TEST_F(PnaclHostTest,OverlappedMissesAfterTempReturn)219 TEST_F(PnaclHostTest, OverlappedMissesAfterTempReturn) {
220 nacl::PnaclCacheInfo info = GetTestCacheInfo();
221 GET_NEXE_FD(0, 0, false, info, false);
222 content::RunAllTasksUntilIdle();
223 EXPECT_EQ(1, temp_callback_count_);
224 EXPECT_EQ(1U, host_->pending_translations());
225 // Test that a second request for the same nexe while the first one is still
226 // outstanding eventually hits.
227 GET_NEXE_FD(0, 1, false, info, true);
228 content::RunAllTasksUntilIdle();
229 EXPECT_EQ(2U, host_->pending_translations());
230 // The temp file should not be returned to the second request until after the
231 // first is finished translating.
232 EXPECT_EQ(1, temp_callback_count_);
233 host_->TranslationFinished(0, 0, true);
234 content::RunAllTasksUntilIdle();
235 EXPECT_EQ(2, temp_callback_count_);
236 EXPECT_EQ(0U, host_->pending_translations());
237 }
238
TEST_F(PnaclHostTest,OverlappedMissesBeforeTempReturn)239 TEST_F(PnaclHostTest, OverlappedMissesBeforeTempReturn) {
240 nacl::PnaclCacheInfo info = GetTestCacheInfo();
241 GET_NEXE_FD(0, 0, false, info, false);
242 // Send the 2nd fd request before the first one returns a temp file.
243 GET_NEXE_FD(0, 1, false, info, true);
244 content::RunAllTasksUntilIdle();
245 EXPECT_EQ(1, temp_callback_count_);
246 EXPECT_EQ(2U, host_->pending_translations());
247 content::RunAllTasksUntilIdle();
248 EXPECT_EQ(2U, host_->pending_translations());
249 EXPECT_EQ(1, temp_callback_count_);
250 host_->TranslationFinished(0, 0, true);
251 content::RunAllTasksUntilIdle();
252 EXPECT_EQ(2, temp_callback_count_);
253 EXPECT_EQ(0U, host_->pending_translations());
254 }
255
TEST_F(PnaclHostTest,OverlappedHitsBeforeTempReturn)256 TEST_F(PnaclHostTest, OverlappedHitsBeforeTempReturn) {
257 nacl::PnaclCacheInfo info = GetTestCacheInfo();
258 // Store one in the cache and complete it.
259 GET_NEXE_FD(0, 0, false, info, false);
260 content::RunAllTasksUntilIdle();
261 EXPECT_EQ(1, temp_callback_count_);
262 host_->TranslationFinished(0, 0, true);
263 content::RunAllTasksUntilIdle();
264 EXPECT_EQ(0U, host_->pending_translations());
265 GET_NEXE_FD(0, 0, false, info, true);
266 // Request the second before the first temp file returns.
267 GET_NEXE_FD(0, 1, false, info, true);
268 content::RunAllTasksUntilIdle();
269 EXPECT_EQ(3, temp_callback_count_);
270 EXPECT_EQ(0U, host_->pending_translations());
271 }
272
TEST_F(PnaclHostTest,OverlappedHitsAfterTempReturn)273 TEST_F(PnaclHostTest, OverlappedHitsAfterTempReturn) {
274 nacl::PnaclCacheInfo info = GetTestCacheInfo();
275 // Store one in the cache and complete it.
276 GET_NEXE_FD(0, 0, false, info, false);
277 content::RunAllTasksUntilIdle();
278 EXPECT_EQ(1, temp_callback_count_);
279 host_->TranslationFinished(0, 0, true);
280 content::RunAllTasksUntilIdle();
281 EXPECT_EQ(0U, host_->pending_translations());
282 GET_NEXE_FD(0, 0, false, info, true);
283 content::RunAllTasksUntilIdle();
284 GET_NEXE_FD(0, 1, false, info, true);
285 content::RunAllTasksUntilIdle();
286 EXPECT_EQ(3, temp_callback_count_);
287 EXPECT_EQ(0U, host_->pending_translations());
288 }
289
TEST_F(PnaclHostTest,OverlappedMissesRendererClosing)290 TEST_F(PnaclHostTest, OverlappedMissesRendererClosing) {
291 nacl::PnaclCacheInfo info = GetTestCacheInfo();
292 GET_NEXE_FD(0, 0, false, info, false);
293 // Send the 2nd fd request from a different renderer.
294 // Test that it eventually gets an fd after the first renderer closes.
295 GET_NEXE_FD(1, 1, false, info, false);
296 content::RunAllTasksUntilIdle();
297 EXPECT_EQ(1, temp_callback_count_);
298 EXPECT_EQ(2U, host_->pending_translations());
299 content::RunAllTasksUntilIdle();
300 EXPECT_EQ(2U, host_->pending_translations());
301 EXPECT_EQ(1, temp_callback_count_);
302 host_->RendererClosing(0);
303 content::RunAllTasksUntilIdle();
304 EXPECT_EQ(2, temp_callback_count_);
305 EXPECT_EQ(1U, host_->pending_translations());
306 host_->RendererClosing(1);
307 }
308
TEST_F(PnaclHostTest,Incognito)309 TEST_F(PnaclHostTest, Incognito) {
310 nacl::PnaclCacheInfo info = GetTestCacheInfo();
311 GET_NEXE_FD(0, 0, true, info, false);
312 content::RunAllTasksUntilIdle();
313 EXPECT_EQ(1, temp_callback_count_);
314 host_->TranslationFinished(0, 0, true);
315 content::RunAllTasksUntilIdle();
316 // Check that an incognito translation is not stored in the cache
317 GET_NEXE_FD(0, 0, false, info, false);
318 content::RunAllTasksUntilIdle();
319 EXPECT_EQ(2, temp_callback_count_);
320 host_->TranslationFinished(0, 0, true);
321 content::RunAllTasksUntilIdle();
322 // Check that an incognito translation can hit from a normal one.
323 GET_NEXE_FD(0, 0, true, info, true);
324 content::RunAllTasksUntilIdle();
325 EXPECT_EQ(3, temp_callback_count_);
326 }
327
TEST_F(PnaclHostTest,IncognitoOverlappedMiss)328 TEST_F(PnaclHostTest, IncognitoOverlappedMiss) {
329 nacl::PnaclCacheInfo info = GetTestCacheInfo();
330 GET_NEXE_FD(0, 0, true, info, false);
331 GET_NEXE_FD(0, 1, false, info, false);
332 content::RunAllTasksUntilIdle();
333 // Check that both translations have returned misses, (i.e. that the
334 // second one has not blocked on the incognito one)
335 EXPECT_EQ(2, temp_callback_count_);
336 host_->TranslationFinished(0, 0, true);
337 host_->TranslationFinished(0, 1, true);
338 content::RunAllTasksUntilIdle();
339 EXPECT_EQ(0U, host_->pending_translations());
340
341 // Same test, but issue the 2nd request after the first has returned a miss.
342 info.abi_version = 222;
343 GET_NEXE_FD(0, 0, true, info, false);
344 content::RunAllTasksUntilIdle();
345 EXPECT_EQ(3, temp_callback_count_);
346 GET_NEXE_FD(0, 1, false, info, false);
347 content::RunAllTasksUntilIdle();
348 EXPECT_EQ(4, temp_callback_count_);
349 host_->RendererClosing(0);
350 }
351
TEST_F(PnaclHostTest,IncognitoSecondOverlappedMiss)352 TEST_F(PnaclHostTest, IncognitoSecondOverlappedMiss) {
353 // If the non-incognito request comes first, it should
354 // behave exactly like OverlappedMissBeforeTempReturn
355 nacl::PnaclCacheInfo info = GetTestCacheInfo();
356 GET_NEXE_FD(0, 0, false, info, false);
357 // Send the 2nd fd request before the first one returns a temp file.
358 GET_NEXE_FD(0, 1, true, info, true);
359 content::RunAllTasksUntilIdle();
360 EXPECT_EQ(1, temp_callback_count_);
361 EXPECT_EQ(2U, host_->pending_translations());
362 content::RunAllTasksUntilIdle();
363 EXPECT_EQ(2U, host_->pending_translations());
364 EXPECT_EQ(1, temp_callback_count_);
365 host_->TranslationFinished(0, 0, true);
366 content::RunAllTasksUntilIdle();
367 EXPECT_EQ(2, temp_callback_count_);
368 EXPECT_EQ(0U, host_->pending_translations());
369 }
370
371 // Test that pexes with the no-store header do not get cached.
TEST_F(PnaclHostTest,CacheControlNoStore)372 TEST_F(PnaclHostTest, CacheControlNoStore) {
373 nacl::PnaclCacheInfo info = GetTestCacheInfo();
374 info.has_no_store_header = true;
375 GET_NEXE_FD(0, 0, false, info, false);
376 content::RunAllTasksUntilIdle();
377 EXPECT_EQ(1, temp_callback_count_);
378 host_->TranslationFinished(0, 0, true);
379 content::RunAllTasksUntilIdle();
380 EXPECT_EQ(0U, host_->pending_translations());
381 EXPECT_EQ(0, GetCacheSize());
382 }
383
384 // Test that no-store pexes do not wait, but do duplicate translations
TEST_F(PnaclHostTest,NoStoreOverlappedMiss)385 TEST_F(PnaclHostTest, NoStoreOverlappedMiss) {
386 nacl::PnaclCacheInfo info = GetTestCacheInfo();
387 info.has_no_store_header = true;
388 GET_NEXE_FD(0, 0, false, info, false);
389 GET_NEXE_FD(0, 1, false, info, false);
390 content::RunAllTasksUntilIdle();
391 // Check that both translations have returned misses, (i.e. that the
392 // second one has not blocked on the first one)
393 EXPECT_EQ(2, temp_callback_count_);
394 host_->TranslationFinished(0, 0, true);
395 host_->TranslationFinished(0, 1, true);
396 content::RunAllTasksUntilIdle();
397 EXPECT_EQ(0U, host_->pending_translations());
398
399 // Same test, but issue the 2nd request after the first has returned a miss.
400 info.abi_version = 222;
401 GET_NEXE_FD(0, 0, false, info, false);
402 content::RunAllTasksUntilIdle();
403 EXPECT_EQ(3, temp_callback_count_);
404 GET_NEXE_FD(0, 1, false, info, false);
405 content::RunAllTasksUntilIdle();
406 EXPECT_EQ(4, temp_callback_count_);
407 host_->RendererClosing(0);
408 }
409
TEST_F(PnaclHostTest,ClearTranslationCache)410 TEST_F(PnaclHostTest, ClearTranslationCache) {
411 nacl::PnaclCacheInfo info = GetTestCacheInfo();
412 // Add 2 entries in the cache
413 GET_NEXE_FD(0, 0, false, info, false);
414 info.abi_version = 222;
415 GET_NEXE_FD(0, 1, false, info, false);
416 content::RunAllTasksUntilIdle();
417 EXPECT_EQ(2, temp_callback_count_);
418 host_->TranslationFinished(0, 0, true);
419 host_->TranslationFinished(0, 1, true);
420 content::RunAllTasksUntilIdle();
421 EXPECT_EQ(0U, host_->pending_translations());
422 EXPECT_EQ(2, GetCacheSize());
423 net::TestCompletionCallback cb;
424 // Since we are using a memory backend, the clear should happen immediately.
425 host_->ClearTranslationCacheEntriesBetween(base::Time(), base::Time(),
426 base::BindOnce(cb.callback(), 0));
427 // Check that the translation cache has been cleared before flushing the
428 // queues, because the backend will be freed once it is.
429 EXPECT_EQ(0, GetCacheSize());
430 EXPECT_EQ(0, cb.GetResult(net::ERR_IO_PENDING));
431 // Call posted PnaclHost::CopyFileToBuffer() tasks.
432 base::RunLoop().RunUntilIdle();
433 // Now check that the backend has been freed.
434 EXPECT_FALSE(CacheIsInitialized());
435 }
436
437 // A version of PnaclHostTest that initializes cache on disk.
438 class PnaclHostTestDisk : public PnaclHostTest {
439 protected:
SetUp()440 void SetUp() override {
441 host_ = PnaclHost::GetInstance();
442 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
443 host_->InitForTest(temp_dir_.GetPath(), false);
444 EXPECT_EQ(PnaclHost::CacheInitializing, host_->cache_state_);
445 }
DeInit()446 void DeInit() {
447 host_->DeInitIfSafe();
448 }
449 };
TEST_F(PnaclHostTestDisk,DeInitWhileInitializing)450 TEST_F(PnaclHostTestDisk, DeInitWhileInitializing) {
451 // Since there's no easy way to pump message queues one message at a time, we
452 // have to simulate what would happen if 1 DeInitIfsafe task gets queued, then
453 // a GetNexeFd gets queued, and then another DeInitIfSafe gets queued before
454 // the first one runs. We can just shortcut and call DeInitIfSafe while the
455 // cache is still initializing.
456 DeInit();
457
458 // Now let it finish initializing. (Other tests don't need this since they
459 // use in-memory storage).
460 disk_cache::FlushCacheThreadForTesting();
461 base::RunLoop().RunUntilIdle();
462 EXPECT_TRUE(CacheIsInitialized());
463 }
464
465 } // namespace pnacl
466