2 +----------------------------------------------------------------------+
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"
54 #include <folly/AtomicHashMap.h>
55 #include <folly/Optional.h>
56 #include <folly/portability/Fcntl.h>
57 #include <folly/portability/SysStat.h>
60 #define st_mtim st_mtimespec
61 #define st_ctim st_ctimespec
66 //////////////////////////////////////////////////////////////////////
70 //////////////////////////////////////////////////////////////////////
72 using OptLog
= folly::Optional
<StructuredLogEntry
>;
75 LogTimer(const char* name
, OptLog
& ent
)
78 , m_start(m_ent
? Timer::GetThreadCPUTimeNanos() : -1)
80 ~LogTimer() { stop(); }
83 if (m_start
== -1) return;
85 auto const elapsed
= Timer::GetThreadCPUTimeNanos() - m_start
;
86 m_ent
->setInt(m_name
, elapsed
/ 1000);
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
136 StringDataHashCompare
,
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
157 return cu
.cachedUnit();
162 * We got the lock, so we're responsible for updating the entry.
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(
178 cu
.rdsBitId
= rds::allocBit();
180 cu
.unit
.update_and_unlock(std::move(unit
));
183 s_repoUnitCache
.erase(acc
);
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
,
201 const RepoOptions
& options
203 , needsTreadmill
{needsTreadmill
}
204 , repoOptionsHash(options
.cacheKeySha1())
208 mtime
= statInfo
->st_mtime
;
210 mtime
= statInfo
->st_mtim
;
211 ctime
= statInfo
->st_ctim
;
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
; });
230 mutable time_t mtime
;
232 mutable struct timespec mtime
;
233 mutable struct timespec ctime
;
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
255 StringDataHashCompare
,
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);
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
;
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;
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
293 if (!s
) return false;
294 return !cachedUnit
||
295 cachedUnit
->cu
.unit
== nullptr ||
297 cachedUnit
->mtime
- s
->st_mtime
< 0 ||
299 timespecCompare(cachedUnit
->mtime
, s
->st_mtim
) < 0 ||
300 timespecCompare(cachedUnit
->ctime
, s
->st_ctim
) < 0 ||
302 cachedUnit
->ino
!= s
->st_ino
||
303 cachedUnit
->devId
!= s
->st_dev
||
304 cachedUnit
->repoOptionsHash
!= SHA1
{options
.cacheKeySha1()} ||
308 folly::Optional
<String
> readFileAsString(Stream::Wrapper
* w
,
309 const StringData
* path
) {
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
);
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)) {
327 auto const fd
= open(path
->data(), O_RDONLY
);
328 if (fd
< 0) return folly::none
;
329 auto file
= req::make
<PlainFile
>(fd
);
333 CachedUnit
createUnitFromString(const char* path
,
334 const String
& contents
,
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
,
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()) {
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
);
370 CachedUnit
createUnitFromUrl(const StringData
* const requestedPath
,
371 const Native::FuncTable
& nativeFuncs
,
372 FileLoadFlags
& flags
) {
373 auto const w
= Stream::getWrapperFromURI(StrNR(requestedPath
));
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
{};
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
,
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();
406 ? createUnitFromString(path
->data(), *contents
, releaseUnit
, ent
,
407 nativeFuncs
, options
, flags
, orig
)
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
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);
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.
468 return s_nonRepoUnitCache
;
471 CachedUnit
loadUnitNonRepoAuth(StringData
* requestedPath
,
472 const struct stat
* statInfo
,
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).
493 // XXX: it seems weird we have to do this even though we already ran
495 (FileUtil::isAbsolutePath(requestedPath
->toCppString())
496 ? String
{requestedPath
}
497 : String(SourceRootInfo::GetCurrentSourceRoot()) + StrNR(requestedPath
)
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
);
514 Stream::Wrapper
* w
= nullptr;
515 auto& cache
= getNonRepoCache(rpath
, w
);
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
));
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
)] () {});
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");
554 cachedUnit
.lock_for_update();
556 if (auto const tmp
= cachedUnit
.copy()) {
557 if (!isChanged(tmp
, statInfo
, options
)) {
559 flags
= FileLoadFlags::kWaited
;
560 if (ent
) ent
->setStr("type", "cache_hit_writelock");
563 if (ent
) ent
->setStr("type", "cache_stale");
565 if (ent
) ent
->setStr("type", "cache_miss");
569 auto const cu
= [&] {
570 auto tmp
= RuntimeOption::EvalCheckUnitSHA1
572 : copy_ptr
<CachedUnitWithFree
>{};
573 return createUnitFromFile(rpath
, &releaseUnit
, w
, ent
,
574 nativeFuncs
, options
, flags
, tmp
);
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
)) {
584 updateAndUnlock(cachedUnit
, p
);
592 auto const ret
= cuptr
->cu
;
594 if (!ret
.unit
|| !ret
.unit
->isICE()) {
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
));
609 CachedUnit
lookupUnitNonRepoAuth(StringData
* requestedPath
,
610 const struct stat
* statInfo
,
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
;
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.
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
);
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
);
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
;
702 if (StringUtil::IsFileUrl(file
)) {
703 return findFileWrapper(file
.substr(7), ctx
);
706 if (!w
) return false;
709 RuntimeOption::RepoAuthoritative
||
710 dynamic_cast<FileStreamWrapper
*>(w
) ||
711 !w
->isNormalFileStream()
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
;
726 if (RuntimeOption::SandboxMode
|| !RuntimeOption::AlwaysUseRelativePath
) {
727 if (findFile(translatedPath
.get(), context
->s
, context
->allow_dir
, passW
,
728 context
->nativeFuncs
)) {
729 context
->path
= translatedPath
;
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
;
751 StructuredLogEntry
& ent
,
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
;
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());
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());
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(
800 const struct stat
& statInfo
,
802 const Native::FuncTable
& nativeFuncs
,
803 FileLoadFlags
& flags
,
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?
814 s
+= (RuntimeOption::PHP7_NoHexNumerics
? '1' : '0') +
815 (RuntimeOption::PHP7_Builtins
? '1' : '0') +
816 (RuntimeOption::PHP7_Substr
? '1' : '0');
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
)
881 + mangleAllowHhas(fileName
)
882 + mangleUnitPHP7Options()
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;
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
);
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
,
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
938 Unit
* checkPhpUnits(Unit
* unit
) {
939 if (unit
&& !unit
->isHHFile()) {
940 throw PhpNotSupportedException(unit
->filepath()->data());
945 Unit
* lookupUnit(StringData
* path
, const char* currentDir
, bool* initial_opt
,
946 const Native::FuncTable
& nativeFuncs
, bool alreadyRealpath
) {
948 bool& initial
= initial_opt
? *initial_opt
: init
;
951 tracing::BlockNoTrace _
{"lookup-unit"};
954 if (!RuntimeOption::RepoAuthoritative
&&
955 StructuredLog::coinflip(RuntimeOption::EvalLogUnitLoadRate
)) {
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.
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.
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
)) {
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
),
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
));
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
;
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 //////////////////////////////////////////////////////////////////////