Clean up logging and switch for fixing fuzzy comparisons between Hack arrays and...
[hiphop-php.git] / hphp / runtime / base / unit-cache.cpp
blob71d70cb853fc16f40b90f9663faddc9557603e99
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/unit-cache.h"
18 #include "hphp/runtime/base/builtin-functions.h"
19 #include "hphp/runtime/base/file-stream-wrapper.h"
20 #include "hphp/runtime/base/file-util.h"
21 #include "hphp/runtime/base/plain-file.h"
22 #include "hphp/runtime/base/program-functions.h"
23 #include "hphp/runtime/base/rds.h"
24 #include "hphp/runtime/base/runtime-option.h"
25 #include "hphp/runtime/base/stat-cache.h"
26 #include "hphp/runtime/base/stream-wrapper-registry.h"
27 #include "hphp/runtime/base/string-util.h"
28 #include "hphp/runtime/base/system-profiler.h"
29 #include "hphp/runtime/base/vm-worker.h"
30 #include "hphp/runtime/base/zend-string.h"
31 #include "hphp/runtime/server/cli-server.h"
32 #include "hphp/runtime/server/source-root-info.h"
33 #include "hphp/runtime/vm/debugger-hook.h"
34 #include "hphp/runtime/vm/extern-compiler.h"
35 #include "hphp/runtime/vm/repo.h"
36 #include "hphp/runtime/vm/runtime-compiler.h"
37 #include "hphp/runtime/vm/treadmill.h"
38 #include "hphp/runtime/vm/type-profile.h"
39 #include "hphp/runtime/vm/unit-emitter.h"
41 #include "hphp/util/assertions.h"
42 #include "hphp/util/build-info.h"
43 #include "hphp/util/mutex.h"
44 #include "hphp/util/process.h"
45 #include "hphp/util/rank.h"
46 #include "hphp/util/struct-log.h"
47 #include "hphp/util/timer.h"
49 #include <cstdlib>
50 #include <memory>
51 #include <string>
52 #include <thread>
54 #include <folly/AtomicHashMap.h>
55 #include <folly/Optional.h>
56 #include <folly/portability/Fcntl.h>
57 #include <folly/portability/SysStat.h>
59 #ifdef __APPLE__
60 #define st_mtim st_mtimespec
61 #define st_ctim st_ctimespec
62 #endif
64 namespace HPHP {
66 //////////////////////////////////////////////////////////////////////
68 namespace {
70 //////////////////////////////////////////////////////////////////////
72 using OptLog = folly::Optional<StructuredLogEntry>;
74 struct LogTimer {
75 LogTimer(const char* name, OptLog& ent)
76 : m_name(name)
77 , m_ent(ent)
78 , m_start(m_ent ? Timer::GetThreadCPUTimeNanos() : -1)
80 ~LogTimer() { stop(); }
82 void stop() {
83 if (m_start == -1) return;
85 auto const elapsed = Timer::GetThreadCPUTimeNanos() - m_start;
86 m_ent->setInt(m_name, elapsed / 1000);
87 m_start = -1;
90 private:
91 const char* m_name;
92 OptLog& m_ent;
93 int64_t m_start;
96 struct CachedUnit {
97 Unit* unit{};
98 size_t rdsBitId{-1uL};
101 struct CachedUnitInternal {
102 CachedUnitInternal() = default;
103 CachedUnitInternal(const CachedUnitInternal& src) :
104 unit{src.unit.copy()},
105 rdsBitId{src.rdsBitId} {}
106 CachedUnitInternal& operator=(const CachedUnitInternal&) = delete;
108 static Unit* const Uninit;
110 CachedUnit cachedUnit() const {
111 return CachedUnit { unit.get(), rdsBitId };
114 // nullptr if there is no Unit for this path, Uninit if the CachedUnit
115 // hasn't been initialized yet.
116 mutable LockFreePtrWrapper<Unit*> unit{Uninit};
117 // id of the RDS bit for whether the Unit is included
118 mutable size_t rdsBitId{-1u};
121 Unit* const CachedUnitInternal::Uninit = reinterpret_cast<Unit*>(-8);
123 //////////////////////////////////////////////////////////////////////
124 // RepoAuthoritative mode unit caching
127 * In RepoAuthoritative mode, loaded units are never unloaded, we
128 * don't support symlink chasing, you can't include urls, and files
129 * are never changed, which makes the code here significantly simpler.
130 * Because of this it pays to keep it separate from the other cases so
131 * they don't need to be littered with RepoAuthoritative checks.
133 using RepoUnitCache = RankedCHM<
134 const StringData*, // must be static
135 CachedUnitInternal,
136 StringDataHashCompare,
137 RankUnitCache
139 RepoUnitCache s_repoUnitCache;
141 CachedUnit lookupUnitRepoAuth(const StringData* path,
142 const Native::FuncTable& nativeFuncs) {
143 tracing::BlockNoTrace _{"lookup-unit-repo-auth"};
145 path = makeStaticString(path);
147 RepoUnitCache::const_accessor acc;
148 s_repoUnitCache.insert(acc, path);
149 auto const& cu = acc->second;
151 if (cu.unit.copy() != CachedUnitInternal::Uninit) return cu.cachedUnit();
153 cu.unit.lock_for_update();
154 if (cu.unit.copy() != CachedUnitInternal::Uninit) {
155 // Someone else updated the unit while we were waiting on the lock
156 cu.unit.unlock();
157 return cu.cachedUnit();
160 try {
162 * We got the lock, so we're responsible for updating the entry.
164 SHA1 sha1;
165 if (Repo::get().findFile(path->data(),
166 RuntimeOption::SourceRoot,
167 sha1) == RepoStatus::error) {
168 cu.unit.update_and_unlock(nullptr);
169 return cu.cachedUnit();
172 auto unit = Repo::get().loadUnit(
173 path->data(),
174 sha1,
175 nativeFuncs)
176 .release();
177 if (unit) {
178 cu.rdsBitId = rds::allocBit();
180 cu.unit.update_and_unlock(std::move(unit));
181 } catch (...) {
182 cu.unit.unlock();
183 s_repoUnitCache.erase(acc);
184 throw;
186 return cu.cachedUnit();
189 //////////////////////////////////////////////////////////////////////
190 // Non-repo mode unit caching
192 struct CachedUnitWithFree {
193 CachedUnitWithFree() = delete;
194 explicit CachedUnitWithFree(const CachedUnitWithFree&) = delete;
195 CachedUnitWithFree& operator=(const CachedUnitWithFree&) = delete;
197 explicit CachedUnitWithFree(
198 const CachedUnit& src,
199 const struct stat* statInfo,
200 bool needsTreadmill,
201 const RepoOptions& options
202 ) : cu(src)
203 , needsTreadmill{needsTreadmill}
204 , repoOptionsHash(options.cacheKeySha1())
206 if (statInfo) {
207 #ifdef _MSC_VER
208 mtime = statInfo->st_mtime;
209 #else
210 mtime = statInfo->st_mtim;
211 ctime = statInfo->st_ctim;
212 #endif
213 ino = statInfo->st_ino;
214 devId = statInfo->st_dev;
217 ~CachedUnitWithFree() {
218 if (skipFree.load(std::memory_order_relaxed)) return;
219 if (auto oldUnit = cu.unit) {
220 if (needsTreadmill) {
221 Treadmill::enqueue([oldUnit] { delete oldUnit; });
222 } else {
223 delete oldUnit;
227 CachedUnit cu;
229 #ifdef _MSC_VER
230 mutable time_t mtime;
231 #else
232 mutable struct timespec mtime;
233 mutable struct timespec ctime;
234 #endif
235 mutable ino_t ino;
236 mutable dev_t devId;
237 bool needsTreadmill;
239 SHA1 repoOptionsHash;
240 mutable std::atomic<bool> skipFree{false};
243 struct CachedUnitNonRepo {
244 CachedUnitNonRepo() = default;
245 CachedUnitNonRepo(const CachedUnitNonRepo& other) :
246 cachedUnit{other.cachedUnit.copy()} {}
247 CachedUnitNonRepo& operator=(const CachedUnitNonRepo&) = delete;
249 mutable LockFreePtrWrapper<copy_ptr<CachedUnitWithFree>> cachedUnit;
252 using NonRepoUnitCache = RankedCHM<
253 const StringData*, // must be static
254 CachedUnitNonRepo,
255 StringDataHashCompare,
256 RankUnitCache
258 NonRepoUnitCache s_nonRepoUnitCache;
260 // When running in remote unix server mode with UnixServerQuarantineUnits set,
261 // we need to cache any unit generated from a file descriptor passed via the
262 // client process in a special quarantined cached. Units in this cache may only
263 // be loaded in unix server requests from the same user and are never used when
264 // handling web requests.
265 using PerUserCache = folly::AtomicHashMap<uid_t, NonRepoUnitCache*>;
266 PerUserCache s_perUserUnitCaches(10);
268 #ifndef _MSC_VER
269 int64_t timespecCompare(const struct timespec& l,
270 const struct timespec& r) {
271 if (l.tv_sec != r.tv_sec) return l.tv_sec - r.tv_sec;
272 return l.tv_nsec - r.tv_nsec;
274 #endif
276 uint64_t g_units_seen_count = 0;
278 bool stressUnitCache() {
279 if (RuntimeOption::EvalStressUnitCacheFreq <= 0) return false;
280 if (RuntimeOption::EvalStressUnitCacheFreq == 1) return true;
281 return ++g_units_seen_count % RuntimeOption::EvalStressUnitCacheFreq == 0;
284 bool isChanged(
285 copy_ptr<CachedUnitWithFree> cachedUnit,
286 const struct stat* s,
287 const RepoOptions& options
289 // If the cached unit is null, we always need to consider it out of date (in
290 // case someone created the file). This case should only happen if something
291 // successfully stat'd the file, but then it was gone by the time we tried to
292 // open() it.
293 if (!s) return false;
294 return !cachedUnit ||
295 cachedUnit->cu.unit == nullptr ||
296 #ifdef _MSC_VER
297 cachedUnit->mtime - s->st_mtime < 0 ||
298 #else
299 timespecCompare(cachedUnit->mtime, s->st_mtim) < 0 ||
300 timespecCompare(cachedUnit->ctime, s->st_ctim) < 0 ||
301 #endif
302 cachedUnit->ino != s->st_ino ||
303 cachedUnit->devId != s->st_dev ||
304 cachedUnit->repoOptionsHash != SHA1{options.cacheKeySha1()} ||
305 stressUnitCache();
308 folly::Optional<String> readFileAsString(Stream::Wrapper* w,
309 const StringData* path) {
310 tracing::Block _{
311 "read-file", [&] { return tracing::Props{}.add("path", path); }
314 // If the file is too large it may OOM the request
315 MemoryManager::SuppressOOM so(*tl_heap);
316 if (w) {
317 // Stream wrappers can reenter PHP via user defined callbacks. Roll this
318 // operation into a single event
319 rqtrace::EventGuard trace{"STREAM_WRAPPER_OPEN"};
320 rqtrace::DisableTracing disable;
322 if (const auto f = w->open(StrNR(path), "r", 0, nullptr)) {
323 return f->read();
325 return folly::none;
327 auto const fd = open(path->data(), O_RDONLY);
328 if (fd < 0) return folly::none;
329 auto file = req::make<PlainFile>(fd);
330 return file->read();
333 CachedUnit createUnitFromString(const char* path,
334 const String& contents,
335 Unit** releaseUnit,
336 OptLog& ent,
337 const Native::FuncTable& nativeFuncs,
338 const RepoOptions& options,
339 FileLoadFlags& flags,
340 copy_ptr<CachedUnitWithFree> orig = {}) {
341 LogTimer generateSha1Timer("generate_sha1_ms", ent);
342 folly::StringPiece path_sp = path;
343 auto const sha1 = SHA1{mangleUnitSha1(string_sha1(contents.slice()), path_sp,
344 options)};
345 generateSha1Timer.stop();
346 if (orig && orig->cu.unit && sha1 == orig->cu.unit->sha1()) return orig->cu;
347 auto const check = [&] (Unit* unit) {
348 if (orig && orig->cu.unit && unit &&
349 unit->bcSha1() == orig->cu.unit->bcSha1()) {
350 delete unit;
351 return orig->cu;
353 flags = FileLoadFlags::kEvicted;
354 return CachedUnit { unit, rds::allocBit() };
356 // Try the repo; if it's not already there, invoke the compiler.
357 if (auto unit = Repo::get().loadUnit(path_sp, sha1, nativeFuncs)) {
358 flags = FileLoadFlags::kHitDisk;
359 return check(unit.release());
361 LogTimer compileTimer("compile_ms", ent);
362 rqtrace::EventGuard trace{"COMPILE_UNIT"};
363 trace.annotate("file_size", folly::to<std::string>(contents.size()));
364 flags = FileLoadFlags::kCompiled;
365 auto const unit = compile_file(contents.data(), contents.size(), sha1, path,
366 nativeFuncs, options, releaseUnit);
367 return check(unit);
370 CachedUnit createUnitFromUrl(const StringData* const requestedPath,
371 const Native::FuncTable& nativeFuncs,
372 FileLoadFlags& flags) {
373 auto const w = Stream::getWrapperFromURI(StrNR(requestedPath));
374 StringBuffer sb;
376 tracing::Block _{
377 "read-url", [&] { return tracing::Props{}.add("path", requestedPath); }
380 // Stream wrappers can reenter PHP via user defined callbacks. Roll this
381 // operation into a single event
382 rqtrace::EventGuard trace{"STREAM_WRAPPER_OPEN"};
383 rqtrace::DisableTracing disable;
385 if (!w) return CachedUnit{};
386 auto const f = w->open(StrNR(requestedPath), "r", 0, nullptr);
387 if (!f) return CachedUnit{};
388 sb.read(f.get());
390 OptLog ent;
391 return createUnitFromString(requestedPath->data(), sb.detach(), nullptr, ent,
392 nativeFuncs, RepoOptions::defaults(), flags);
395 CachedUnit createUnitFromFile(const StringData* const path,
396 Unit** releaseUnit, Stream::Wrapper* w,
397 OptLog& ent,
398 const Native::FuncTable& nativeFuncs,
399 const RepoOptions& options,
400 FileLoadFlags& flags,
401 copy_ptr<CachedUnitWithFree> orig = {}) {
402 LogTimer readUnitTimer("read_unit_ms", ent);
403 auto const contents = readFileAsString(w, path);
404 readUnitTimer.stop();
405 return contents
406 ? createUnitFromString(path->data(), *contents, releaseUnit, ent,
407 nativeFuncs, options, flags, orig)
408 : CachedUnit{};
411 // When running via the CLI server special access checks may need to be
412 // performed, and in the event that the server is unable to load the file an
413 // alternative per client cache may be used.
414 NonRepoUnitCache& getNonRepoCache(const StringData* rpath,
415 Stream::Wrapper*& w) {
416 if (auto uc = get_cli_ucred()) {
417 if (!(w = Stream::getWrapperFromURI(StrNR(rpath)))) {
418 return s_nonRepoUnitCache;
421 auto unit_check_quarantine = [&] () -> NonRepoUnitCache& {
422 if (!RuntimeOption::EvalUnixServerQuarantineUnits) {
423 return s_nonRepoUnitCache;
425 auto iter = s_perUserUnitCaches.find(uc->uid);
426 if (iter != s_perUserUnitCaches.end()) return *iter->second;
427 auto cache = new NonRepoUnitCache;
428 auto res = s_perUserUnitCaches.insert(uc->uid, cache);
429 if (!res.second) delete cache;
430 return *res.first->second;
433 // If the server cannot access rpath attempt to open the unit on the
434 // client. When UnixServerQuarantineUnits is set store units opened by
435 // clients in per UID caches which are never accessible by server web
436 // requests.
437 if (access(rpath->data(), R_OK) == -1) {
438 return unit_check_quarantine();
441 // When UnixServerVerifyExeAccess is set clients may not execute units if
442 // they cannot read them, even when the server has access. To verify that
443 // clients have access they are asked to open the file for read access,
444 // and using fstat the server verifies that the file it sees is identical
445 // to the unit opened by the client.
446 if (RuntimeOption::EvalUnixServerVerifyExeAccess) {
447 // Stream wrappers can reenter PHP via user defined callbacks. Roll this
448 // operation into a single event
449 rqtrace::EventGuard trace{"STREAM_WRAPPER_OPEN"};
450 rqtrace::DisableTracing disable;
452 struct stat local, remote;
453 auto remoteFile = w->open(StrNR(rpath), "r", 0, nullptr);
454 if (!remoteFile ||
455 fcntl(remoteFile->fd(), F_GETFL) != O_RDONLY ||
456 fstat(remoteFile->fd(), &remote) != 0 ||
457 stat(rpath->data(), &local) != 0 ||
458 remote.st_dev != local.st_dev ||
459 remote.st_ino != local.st_ino) {
460 return unit_check_quarantine();
464 // When the server is able to read the file prefer to access it that way,
465 // in all modes units loaded by the server are cached for all clients.
466 w = nullptr;
468 return s_nonRepoUnitCache;
471 CachedUnit loadUnitNonRepoAuth(StringData* requestedPath,
472 const struct stat* statInfo,
473 OptLog& ent,
474 const Native::FuncTable& nativeFuncs,
475 const RepoOptions& options,
476 FileLoadFlags& flags,
477 bool alreadyRealpath) {
478 tracing::BlockNoTrace _{"load-unit-non-repo-auth"};
480 LogTimer loadTime("load_ms", ent);
481 if (strstr(requestedPath->data(), "://") != nullptr) {
482 // URL-based units are not currently cached in memory, but the Repo still
483 // caches them on disk.
484 return createUnitFromUrl(requestedPath, nativeFuncs, flags);
487 rqtrace::EventGuard trace{"WRITE_UNIT"};
489 // The string we're using as a key must be static, because we're using it as
490 // a key in the cache (across requests).
491 auto const path =
492 makeStaticString(
493 // XXX: it seems weird we have to do this even though we already ran
494 // resolveVmInclude.
495 (FileUtil::isAbsolutePath(requestedPath->toCppString())
496 ? String{requestedPath}
497 : String(SourceRootInfo::GetCurrentSourceRoot()) + StrNR(requestedPath)
498 ).get()
501 auto const rpath = [&] () -> const StringData* {
502 if (RuntimeOption::CheckSymLink && !alreadyRealpath) {
503 std::string rp = StatCache::realpath(path->data());
504 if (rp.size() != 0) {
505 if (rp.size() != path->size() ||
506 memcmp(rp.data(), path->data(), rp.size())) {
507 return makeStaticString(rp);
511 return path;
512 }();
514 Stream::Wrapper* w = nullptr;
515 auto& cache = getNonRepoCache(rpath, w);
517 assertx(
518 !w || &cache != &s_nonRepoUnitCache ||
519 !RuntimeOption::EvalUnixServerQuarantineUnits
522 // Freeing a unit while holding the tbb lock would cause a rank violation when
523 // recycle-tc is enabled as reclaiming dead functions requires that the code
524 // and metadata locks be acquired.
525 Unit* releaseUnit = nullptr;
526 SCOPE_EXIT { if (releaseUnit) delete releaseUnit; };
528 auto const updateAndUnlock = [] (auto& cachedUnit, auto p) {
529 auto newU = p->cu.unit;
530 auto old = cachedUnit.update_and_unlock(std::move(p));
531 if (old) {
532 if (old->cu.unit == newU) {
533 old->skipFree.store(true, std::memory_order_relaxed);
535 // We don't need to do anything explicitly; the copy_ptr
536 // destructor will take care of it.
537 Treadmill::enqueue([unit_to_delete = std::move(old)] () {});
541 auto cuptr = [&] {
542 NonRepoUnitCache::const_accessor rpathAcc;
544 cache.insert(rpathAcc, rpath);
545 auto& cachedUnit = rpathAcc->second.cachedUnit;
546 if (auto const tmp = cachedUnit.copy()) {
547 if (!isChanged(tmp, statInfo, options)) {
548 flags = FileLoadFlags::kHitMem;
549 if (ent) ent->setStr("type", "cache_hit_readlock");
550 return tmp;
554 cachedUnit.lock_for_update();
555 try {
556 if (auto const tmp = cachedUnit.copy()) {
557 if (!isChanged(tmp, statInfo, options)) {
558 cachedUnit.unlock();
559 flags = FileLoadFlags::kWaited;
560 if (ent) ent->setStr("type", "cache_hit_writelock");
561 return tmp;
563 if (ent) ent->setStr("type", "cache_stale");
564 } else {
565 if (ent) ent->setStr("type", "cache_miss");
568 trace.finish();
569 auto const cu = [&] {
570 auto tmp = RuntimeOption::EvalCheckUnitSHA1
571 ? cachedUnit.copy()
572 : copy_ptr<CachedUnitWithFree>{};
573 return createUnitFromFile(rpath, &releaseUnit, w, ent,
574 nativeFuncs, options, flags, tmp);
575 }();
576 auto const isICE = cu.unit && cu.unit->isICE();
577 auto p = copy_ptr<CachedUnitWithFree>(cu, statInfo, isICE, options);
578 // Don't cache the unit if it was created in response to an internal error
579 // in ExternCompiler. Such units represent transient events.
580 if (UNLIKELY(isICE)) {
581 cachedUnit.unlock();
582 return p;
584 updateAndUnlock(cachedUnit, p);
585 return p;
586 } catch (...) {
587 cachedUnit.unlock();
588 throw;
590 }();
592 auto const ret = cuptr->cu;
594 if (!ret.unit || !ret.unit->isICE()) {
595 if (path != rpath) {
596 NonRepoUnitCache::const_accessor pathAcc;
597 cache.insert(pathAcc, path);
598 if (pathAcc->second.cachedUnit.get().get() != cuptr) {
599 auto& cachedUnit = pathAcc->second.cachedUnit;
600 cachedUnit.lock_for_update();
601 updateAndUnlock(cachedUnit, std::move(cuptr));
606 return ret;
609 CachedUnit lookupUnitNonRepoAuth(StringData* requestedPath,
610 const struct stat* statInfo,
611 OptLog& ent,
612 const Native::FuncTable& nativeFuncs,
613 FileLoadFlags& flags,
614 bool alreadyRealpath) {
615 tracing::BlockNoTrace _{"lookup-unit-non-repo-auth"};
617 auto const& options = RepoOptions::forFile(requestedPath->data());
619 if (!g_context.isNull() && strncmp(requestedPath->data(), "/:", 2)) {
620 g_context->onLoadWithOptions(requestedPath->data(), options);
622 // Steady state, its probably already in the cache. Try that first
624 rqtrace::EventGuard trace{"READ_UNIT"};
625 NonRepoUnitCache::const_accessor acc;
626 if (s_nonRepoUnitCache.find(acc, requestedPath)) {
627 auto const cachedUnit = acc->second.cachedUnit.copy();
628 if (!isChanged(cachedUnit, statInfo, options)) {
629 auto const cu = cachedUnit->cu;
630 if (!cu.unit || !RuntimeOption::CheckSymLink || alreadyRealpath ||
631 !strcmp(StatCache::realpath(requestedPath->data()).c_str(),
632 cu.unit->filepath()->data())) {
633 if (ent) ent->setStr("type", "cache_hit_readlock");
634 flags = FileLoadFlags::kHitMem;
635 return cu;
640 return loadUnitNonRepoAuth(requestedPath, statInfo, ent, nativeFuncs,
641 options, flags, alreadyRealpath);
644 //////////////////////////////////////////////////////////////////////
645 // resolveVmInclude callbacks
647 struct ResolveIncludeContext {
648 struct stat* s; // stat for the file
649 bool allow_dir; // return true for dirs?
650 const Native::FuncTable& nativeFuncs;
651 String path; // translated path of the file
654 bool findFile(const StringData* path, struct stat* s, bool allow_dir,
655 Stream::Wrapper* w, const Native::FuncTable& nativeFuncs) {
656 // We rely on this side-effect in RepoAuthoritative mode right now, since the
657 // stat information is an output-param of resolveVmInclude, but we aren't
658 // really going to call stat.
659 s->st_mode = 0;
661 if (RuntimeOption::RepoAuthoritative) {
662 return lookupUnitRepoAuth(path, nativeFuncs).unit != nullptr;
665 if (StatCache::stat(path->data(), s) == 0) {
666 // The call explicitly populates the struct for dirs, but returns false for
667 // them because it is geared toward file includes.
668 return allow_dir || !S_ISDIR(s->st_mode);
671 if (w) {
672 // Stream wrappers can reenter PHP via user defined callbacks. Roll this
673 // operation into a single event
674 rqtrace::EventGuard trace{"STREAM_WRAPPER_STAT"};
675 rqtrace::DisableTracing disable;
677 if (w->stat(StrNR(path), s) == 0) {
678 return allow_dir || !S_ISDIR(s->st_mode);
681 return false;
684 bool findFileWrapper(const String& file, void* ctx) {
685 auto const context = static_cast<ResolveIncludeContext*>(ctx);
686 assertx(context->path.isNull());
688 Stream::Wrapper* w = Stream::getWrapperFromURI(file);
689 if (w && !w->isNormalFileStream()) {
690 // Stream wrappers can reenter PHP via user defined callbacks. Roll this
691 // operation into a single event
692 rqtrace::EventGuard trace{"STREAM_WRAPPER_STAT"};
693 rqtrace::DisableTracing disable;
695 if (w->stat(file, context->s) == 0) {
696 context->path = file;
697 return true;
701 // handle file://
702 if (StringUtil::IsFileUrl(file)) {
703 return findFileWrapper(file.substr(7), ctx);
706 if (!w) return false;
708 auto passW =
709 RuntimeOption::RepoAuthoritative ||
710 dynamic_cast<FileStreamWrapper*>(w) ||
711 !w->isNormalFileStream()
712 ? nullptr
713 : w;
715 // TranslatePath() will canonicalize the path and also check
716 // whether the file is in an allowed directory.
717 String translatedPath = File::TranslatePathKeepRelative(file);
718 if (!FileUtil::isAbsolutePath(file.toCppString())) {
719 if (findFile(translatedPath.get(), context->s, context->allow_dir,
720 passW, context->nativeFuncs)) {
721 context->path = translatedPath;
722 return true;
724 return false;
726 if (RuntimeOption::SandboxMode || !RuntimeOption::AlwaysUseRelativePath) {
727 if (findFile(translatedPath.get(), context->s, context->allow_dir, passW,
728 context->nativeFuncs)) {
729 context->path = translatedPath;
730 return true;
733 std::string server_root(SourceRootInfo::GetCurrentSourceRoot());
734 if (server_root.empty()) {
735 server_root = std::string(g_context->getCwd().data());
736 if (server_root.empty() ||
737 FileUtil::isDirSeparator(server_root[server_root.size() - 1])) {
738 server_root += FileUtil::getDirSeparator();
741 String rel_path(FileUtil::relativePath(server_root, translatedPath.data()));
742 if (findFile(rel_path.get(), context->s, context->allow_dir, passW,
743 context->nativeFuncs)) {
744 context->path = rel_path;
745 return true;
747 return false;
750 void logLoad(
751 StructuredLogEntry& ent,
752 StringData* path,
753 const char* cwd,
754 String rpath,
755 const CachedUnit& cu
757 ent.setStr("include_path", path->data());
758 ent.setStr("current_dir", cwd);
759 ent.setStr("resolved_path", rpath.data());
760 if (auto const u = cu.unit) {
761 const StringData* err;
762 int line;
763 if (u->compileTimeFatal(err, line)) {
764 ent.setStr("result", "compile_fatal");
765 ent.setStr("error", err->data());
766 } else if (u->parseFatal(err, line)) {
767 ent.setStr("result", "parse_fatal");
768 ent.setStr("error", err->data());
769 } else {
770 ent.setStr("result", "success");
773 ent.setStr("sha1", u->sha1().toString());
774 ent.setStr("repo_sn", folly::to<std::string>(u->sn()));
775 ent.setStr("repo_id", folly::to<std::string>(u->repoID()));
777 ent.setInt("bc_len", u->bclen());
778 ent.setInt("num_litstrs", u->numLitstrs());
779 ent.setInt("num_funcs", u->funcs().size());
780 ent.setInt("num_classes", u->preclasses().size());
781 ent.setInt("num_type_aliases", u->typeAliases().size());
782 } else {
783 ent.setStr("result", "file_not_found");
786 switch (rl_typeProfileLocals->requestKind) {
787 case RequestKind::Warmup: ent.setStr("request_kind", "warmup"); break;
788 case RequestKind::Standard: ent.setStr("request_kind", "standard"); break;
789 case RequestKind::NonVM: ent.setStr("request_kind", "nonVM"); break;
791 ent.setInt("request_count", requestCount());
793 StructuredLog::log("hhvm_unit_cache", ent);
796 //////////////////////////////////////////////////////////////////////
798 CachedUnit checkoutFile(
799 StringData* path,
800 const struct stat& statInfo,
801 OptLog& ent,
802 const Native::FuncTable& nativeFuncs,
803 FileLoadFlags& flags,
804 bool alreadyRealpath
806 return RuntimeOption::RepoAuthoritative
807 ? lookupUnitRepoAuth(path, nativeFuncs)
808 : lookupUnitNonRepoAuth(path, &statInfo, ent, nativeFuncs, flags, alreadyRealpath);
811 const std::string mangleUnitPHP7Options() {
812 // As the list of options increases, we may want to do something smarter here?
813 std::string s;
814 s += (RuntimeOption::PHP7_NoHexNumerics ? '1' : '0') +
815 (RuntimeOption::PHP7_Builtins ? '1' : '0') +
816 (RuntimeOption::PHP7_Substr ? '1' : '0');
817 return s;
820 char mangleAllowHhas(const folly::StringPiece fileName) {
821 if (!RuntimeOption::EvalAllowHhas) return '0'; // dont allow
822 auto const len = fileName.size();
823 if (len >= 5 && fileName.subpiece(len - 5) == ".hhas") return '1';
824 return '2'; // not hhas
827 //////////////////////////////////////////////////////////////////////
829 } // end empty namespace
831 //////////////////////////////////////////////////////////////////////
833 std::string mangleUnitSha1(const std::string& fileSha1,
834 const folly::StringPiece fileName,
835 const RepoOptions& opts) {
836 std::string t = fileSha1 + '\0'
837 + repoSchemaId().toString()
838 + (RuntimeOption::EnableClassLevelWhereClauses ? '1' : '0')
839 + (RuntimeOption::AssertEmitted ? '1' : '0')
840 + (RuntimeOption::EnablePocketUniverses ? '1' : '0')
841 + (RuntimeOption::EvalGenerateDocComments ? '1' : '0')
842 + (RuntimeOption::EnableXHP ? '1' : '0')
843 + (RuntimeOption::EvalEmitSwitch ? '1' : '0')
844 + (RuntimeOption::EvalEnableCallBuiltin ? '1' : '0')
845 + (RuntimeOption::EvalHackArrCompatNotices ? '1' : '0')
846 + (RuntimeOption::EvalHackArrCompatIsArrayNotices ? '1' : '0')
847 + (RuntimeOption::EvalHackArrCompatTypeHintNotices ? '1' : '0')
848 + (RuntimeOption::EvalHackArrCompatDVCmpNotices ? '1' : '0')
849 + (RuntimeOption::EvalHackArrCompatSerializeNotices ? '1' : '0')
850 + (RuntimeOption::EvalHackCompilerUseEmbedded ? '1' : '0')
851 + (RuntimeOption::EvalHackCompilerVerboseErrors ? '1' : '0')
852 + (RuntimeOption::EvalJitEnableRenameFunction ? '1' : '0')
853 + (RuntimeOption::EvalLoadFilepathFromUnitCache ? '1' : '0')
854 + std::to_string(RuntimeOption::EvalForbidDynamicCallsToFunc)
855 + std::to_string(RuntimeOption::EvalForbidDynamicCallsToClsMeth)
856 + std::to_string(RuntimeOption::EvalForbidDynamicCallsToInstMeth)
857 + std::to_string(RuntimeOption::EvalForbidDynamicConstructs)
858 + (RuntimeOption::EvalForbidDynamicCallsWithAttr ? '1' : '0')
859 + (RuntimeOption::EvalLogKnownMethodsAsDynamicCalls ? '1' : '0')
860 + (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls ? '1' : '0')
861 + (RuntimeOption::EvalHackArrDVArrs ? '1' : '0')
862 + (RuntimeOption::EvalAssemblerFoldDefaultValues ? '1' : '0')
863 + RuntimeOption::EvalHackCompilerCommand + '\0'
864 + RuntimeOption::EvalHackCompilerArgs + '\0'
865 + (needs_extended_line_table() ? '1' : '0')
866 + std::to_string(RuntimeOption::CheckIntOverflow)
867 + (RuntimeOption::DisallowExecutionOperator ? '1' : '0')
868 + (RuntimeOption::DisableNontoplevelDeclarations ? '1' : '0')
869 + (RuntimeOption::DisableStaticClosures ? '1' : '0')
870 + (RuntimeOption::EvalRxIsEnabled ? '1' : '0')
871 + (RuntimeOption::EvalEmitClsMethPointers ? '1' : '0')
872 + (RuntimeOption::EvalIsVecNotices ? '1' : '0')
873 + (RuntimeOption::EvalIsCompatibleClsMethType ? '1' : '0')
874 + (RuntimeOption::EvalHackRecords ? '1' : '0')
875 + (RuntimeOption::EvalHackRecordArrays ? '1' : '0')
876 + (RuntimeOption::EvalArrayProvenance ? '1' : '0')
877 + (RuntimeOption::EnableFirstClassFunctionPointers ? '1' : '0')
878 + std::to_string(RuntimeOption::EvalEnforceGenericsUB)
879 + std::to_string(RuntimeOption::EvalAssemblerMaxScalarSize)
880 + opts.cacheKeyRaw()
881 + mangleAllowHhas(fileName)
882 + mangleUnitPHP7Options()
883 + hackc_version();
884 return string_sha1(t);
887 size_t numLoadedUnits() {
888 if (RuntimeOption::RepoAuthoritative) {
889 return s_repoUnitCache.size();
891 return s_nonRepoUnitCache.size();
894 Unit* getLoadedUnit(StringData* path) {
895 if (!RuntimeOption::RepoAuthoritative) {
896 NonRepoUnitCache::const_accessor accessor;
897 if (s_nonRepoUnitCache.find(accessor, path) ) {
898 auto cachedUnit = accessor->second.cachedUnit.copy();
899 return cachedUnit ? cachedUnit->cu.unit : nullptr;
903 return nullptr;
907 std::vector<Unit*> loadedUnitsRepoAuth() {
908 always_assert(RuntimeOption::RepoAuthoritative);
909 std::vector<Unit*> units;
910 units.reserve(s_repoUnitCache.size());
911 for (auto const& elm : s_repoUnitCache) {
912 if (auto const unit = elm.second.unit.copy()) {
913 if (unit != CachedUnitInternal::Uninit) {
914 units.push_back(unit);
918 return units;
921 void invalidateUnit(StringData* path) {
922 always_assert(RuntimeOption::RepoAuthoritative == false);
923 s_nonRepoUnitCache.erase(path);
924 Repo::get().forgetUnit(path->data());
927 String resolveVmInclude(StringData* path,
928 const char* currentDir,
929 struct stat* s,
930 const Native::FuncTable& nativeFuncs,
931 bool allow_dir /* = false */) {
932 ResolveIncludeContext ctx{s, allow_dir, nativeFuncs};
933 resolve_include(String{path}, currentDir, findFileWrapper, &ctx);
934 // If resolve_include() could not find the file, return NULL
935 return ctx.path;
938 Unit* checkPhpUnits(Unit* unit) {
939 if (unit && !unit->isHHFile()) {
940 throw PhpNotSupportedException(unit->filepath()->data());
942 return unit;
945 Unit* lookupUnit(StringData* path, const char* currentDir, bool* initial_opt,
946 const Native::FuncTable& nativeFuncs, bool alreadyRealpath) {
947 bool init;
948 bool& initial = initial_opt ? *initial_opt : init;
949 initial = true;
951 tracing::BlockNoTrace _{"lookup-unit"};
953 OptLog ent;
954 if (!RuntimeOption::RepoAuthoritative &&
955 StructuredLog::coinflip(RuntimeOption::EvalLogUnitLoadRate)) {
956 ent.emplace();
959 rqtrace::ScopeGuard trace{"LOOKUP_UNIT"};
960 trace.annotate("path", path->data());
961 trace.annotate("pwd", currentDir);
963 LogTimer lookupTimer("lookup_ms", ent);
966 * NB: the m_evaledFiles map is only for the debugger, and could be omitted
967 * in RepoAuthoritative mode, but currently isn't.
970 struct stat s;
971 auto const spath = resolveVmInclude(path, currentDir, &s, nativeFuncs);
972 if (spath.isNull()) return nullptr;
974 auto const eContext = g_context.getNoCheck();
976 // Check if this file has already been included.
977 auto it = eContext->m_evaledFiles.find(spath.get());
978 if (it != eContext->m_evaledFiles.end()) {
979 // In RepoAuthoritative mode we assume that the files are unchanged.
980 initial = false;
981 if (RuntimeOption::RepoAuthoritative ||
982 (it->second.ts_sec > s.st_mtime) ||
983 ((it->second.ts_sec == s.st_mtime) &&
984 (it->second.ts_nsec >= s.st_mtim.tv_nsec))) {
985 return checkPhpUnits(it->second.unit);
989 FileLoadFlags flags = FileLoadFlags::kHitMem;
991 // This file hasn't been included yet, so we need to parse the file
992 auto const cunit = checkoutFile(spath.get(), s, ent, nativeFuncs, flags, alreadyRealpath);
993 if (cunit.unit && initial_opt) {
994 // if initial_opt is not set, this shouldn't be recorded as a
995 // per request fetch of the file.
996 if (rds::testAndSetBit(cunit.rdsBitId)) {
997 initial = false;
999 // if parsing was successful, update the mappings for spath and
1000 // rpath (if it exists).
1001 eContext->m_evaledFilesOrder.push_back(cunit.unit->filepath());
1002 eContext->m_evaledFiles[spath.get()] =
1003 {cunit.unit, s.st_mtime, static_cast<unsigned long>(s.st_mtim.tv_nsec),
1004 flags};
1005 spath.get()->incRefCount();
1006 if (!cunit.unit->filepath()->same(spath.get())) {
1007 eContext->m_evaledFiles[cunit.unit->filepath()] =
1008 {cunit.unit, s.st_mtime, static_cast<unsigned long>(s.st_mtim.tv_nsec),
1009 FileLoadFlags::kDup};
1011 if (g_system_profiler) {
1012 g_system_profiler->fileLoadCallBack(path->toCppString());
1014 DEBUGGER_ATTACHED_ONLY(phpDebuggerFileLoadHook(cunit.unit));
1017 lookupTimer.stop();
1018 if (ent) logLoad(*ent, path, currentDir, spath, cunit);
1019 return checkPhpUnits(cunit.unit);
1022 Unit* lookupSyslibUnit(StringData* path, const Native::FuncTable& nativeFuncs) {
1023 if (RuntimeOption::RepoAuthoritative) {
1024 return lookupUnitRepoAuth(path, nativeFuncs).unit;
1026 OptLog ent;
1027 FileLoadFlags flags;
1028 return lookupUnitNonRepoAuth(path, nullptr, ent, nativeFuncs, flags, true).unit;
1031 //////////////////////////////////////////////////////////////////////
1033 void clearUnitCacheForExit() {
1034 s_nonRepoUnitCache.clear();
1035 s_repoUnitCache.clear();
1036 s_perUserUnitCaches.clear();
1039 //////////////////////////////////////////////////////////////////////