Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / base / request-injection-data.cpp
blobfbb454872a05ff16190578b80968a950f2bd2d1c
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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/request-injection-data.h"
19 #include <atomic>
20 #include <cinttypes>
21 #include <string>
22 #include <limits>
24 #include <signal.h>
26 #include <boost/filesystem.hpp>
27 #include <folly/Optional.h>
28 #include <folly/Random.h>
29 #include <folly/portability/SysTime.h>
31 #include "hphp/util/logger.h"
32 #include "hphp/runtime/base/file.h"
33 #include "hphp/runtime/base/ini-setting.h"
34 #include "hphp/runtime/base/rds-header.h"
35 #include "hphp/runtime/base/runtime-option.h"
36 #include "hphp/runtime/base/thread-info.h"
37 #include "hphp/runtime/ext/string/ext_string.h"
38 #include "hphp/runtime/vm/debugger-hook.h"
39 #include "hphp/runtime/ext/std/ext_std_file.h"
41 namespace HPHP {
43 //////////////////////////////////////////////////////////////////////
45 void RequestTimer::onTimeout() {
46 m_reqInjectionData->onTimeout(this);
49 #if defined(__APPLE__)
51 RequestTimer::RequestTimer(RequestInjectionData* data)
52 : m_reqInjectionData(data)
54 // Unlike the canonical Linux implementation, this does not distinguish
55 // between whether we wanted real seconds or CPU seconds -- you always get
56 // real seconds. There isn't a nice way to get CPU seconds on OS X that I've
57 // found, outside of setitimer, which has its own configurability issues. So
58 // we just always give real seconds, which is "close enough".
60 m_timerGroup = dispatch_group_create();
63 RequestTimer::~RequestTimer() {
64 cancelTimerSource();
65 dispatch_release(m_timerGroup);
68 void RequestTimer::cancelTimerSource() {
69 if (m_timerSource) {
70 dispatch_source_cancel(m_timerSource);
71 dispatch_group_wait(m_timerGroup, DISPATCH_TIME_FOREVER);
73 // At this point it is safe to free memory, the source or even ourselves (if
74 // this is part of the destructor). See the way we set up the timer group
75 // and cancellation handler in setTimeout() below.
77 dispatch_release(m_timerSource);
78 m_timerSource = nullptr;
82 void RequestTimer::setTimeout(int seconds) {
83 m_timeoutSeconds = seconds > 0 ? seconds : 0;
85 cancelTimerSource();
87 if (!m_timeoutSeconds) {
88 return;
91 dispatch_queue_t q =
92 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
93 m_timerSource = dispatch_source_create(
94 DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, q);
96 dispatch_time_t t =
97 dispatch_time(DISPATCH_TIME_NOW, m_timeoutSeconds * NSEC_PER_SEC);
98 dispatch_source_set_timer(m_timerSource, t, DISPATCH_TIME_FOREVER, 0);
100 // Use the timer group as a semaphore. When the source is cancelled,
101 // libdispatch will make sure all pending event handlers have finished before
102 // invoking the cancel handler. This means that if we cancel the source and
103 // then wait on the timer group, when we are done waiting, we know the source
104 // is completely done and it's safe to free memory (e.g., in the destructor).
105 // See cancelTimerSource() above.
106 dispatch_group_enter(m_timerGroup);
107 dispatch_source_set_event_handler(m_timerSource, ^{
108 onTimeout();
110 // Cancelling ourselves isn't needed for correctness, but we can go ahead
111 // and do it now instead of waiting on it later, so why not. (Also,
112 // getRemainingTime does use this opportunistically, but it's best effort.)
113 dispatch_source_cancel(m_timerSource);
115 dispatch_source_set_cancel_handler(m_timerSource, ^{
116 dispatch_group_leave(m_timerGroup);
119 dispatch_resume(m_timerSource);
122 int RequestTimer::getRemainingTime() const {
123 // Unfortunately, not a good way to detect this. The best we can say is if the
124 // timer exists and fired and cancelled itself, we can clip to 0, otherwise
125 // just return the full timeout seconds. In principle, we could use the
126 // interval configuration of the timer to count down one second at a time,
127 // but that doesn't seem worth it.
128 if (m_timerSource && dispatch_source_testcancel(m_timerSource)) {
129 return 0;
132 return m_timeoutSeconds;
135 #elif defined(_MSC_VER)
137 RequestTimer::RequestTimer(RequestInjectionData* data)
138 : m_reqInjectionData(data)
141 RequestTimer::~RequestTimer() {
142 if (m_tce) {
143 m_tce->set();
144 m_tce = nullptr;
148 void RequestTimer::setTimeout(int seconds) {
149 m_timeoutSeconds = seconds > 0 ? seconds : 0;
151 if (m_tce) {
152 m_tce->set();
153 m_tce = nullptr;
156 if (m_timeoutSeconds) {
157 auto call = new concurrency::call<int>([this](int) {
158 this->onTimeout();
159 m_tce->set();
160 m_tce = nullptr;
163 auto timer = new concurrency::timer<int>(m_timeoutSeconds * 1000,
164 0, call, false);
166 concurrency::task<void> event_set(*m_tce);
167 event_set.then([call, timer]() {
168 timer->pause();
169 delete call;
170 delete timer;
173 timer->start();
177 int RequestTimer::getRemainingTime() const {
178 return m_timeoutSeconds;
181 #else
183 RequestTimer::RequestTimer(RequestInjectionData* data, clockid_t clockType)
184 : m_reqInjectionData(data)
185 , m_clockType(clockType)
186 , m_hasTimer(false)
187 , m_timerActive(false)
190 RequestTimer::~RequestTimer() {
191 if (m_hasTimer) {
192 timer_delete(m_timerId);
197 * NB: this function must be nothrow when `seconds' is zero. RPCRequestHandler
198 * makes use of this.
200 void RequestTimer::setTimeout(int seconds) {
201 m_timeoutSeconds = seconds > 0 ? seconds : 0;
202 if (!m_hasTimer) {
203 if (!m_timeoutSeconds) {
204 // we don't have a timer, and we don't have a timeout
205 return;
207 sigevent sev;
208 memset(&sev, 0, sizeof(sev));
209 sev.sigev_notify = SIGEV_SIGNAL;
210 sev.sigev_signo = SIGVTALRM;
211 sev.sigev_value.sival_ptr = this;
212 if (timer_create(m_clockType, &sev, &m_timerId)) {
213 raise_error("Failed to set timeout: %s", folly::errnoStr(errno).c_str());
215 m_hasTimer = true;
219 * There is a potential race here. Callers want to assume that
220 * if they cancel the timeout (seconds = 0), they *won't* get
221 * a signal after they call this (although they may get a signal
222 * during the call).
223 * So we need to clear the timeout, wait (if necessary) for a
224 * pending signal to be handled, and then set the new timeout
226 itimerspec ts = {};
227 itimerspec old;
228 timer_settime(m_timerId, 0, &ts, &old);
229 if (!old.it_value.tv_sec && !old.it_value.tv_nsec) {
230 // the timer has gone off...
231 if (m_timerActive.load(std::memory_order_acquire)) {
232 // but m_timerActive is still set, so we haven't processed
233 // the signal yet.
234 // spin until its done.
235 while (m_timerActive.load(std::memory_order_relaxed)) {
239 if (m_timeoutSeconds) {
240 m_timerActive.store(true, std::memory_order_relaxed);
241 ts.it_value.tv_sec = m_timeoutSeconds;
242 timer_settime(m_timerId, 0, &ts, nullptr);
243 } else {
244 m_timerActive.store(false, std::memory_order_relaxed);
248 int RequestTimer::getRemainingTime() const {
249 if (m_hasTimer) {
250 itimerspec ts;
251 if (!timer_gettime(m_timerId, &ts)) {
252 int remaining = ts.it_value.tv_sec;
253 return remaining > 1 ? remaining : 1;
256 return m_timeoutSeconds;
258 #endif
260 //////////////////////////////////////////////////////////////////////
262 bool RequestInjectionData::setAllowedDirectories(const std::string& value) {
263 // Backwards compat with ;
264 // but moving forward should use PATH_SEPARATOR
265 std::vector<std::string> boom;
266 if (value.find(";") != std::string::npos) {
267 folly::split(";", value, boom, true);
268 m_open_basedir_separator = ";";
269 } else {
270 m_open_basedir_separator =
271 s_PATH_SEPARATOR.toCppString();
272 folly::split(m_open_basedir_separator, value, boom,
273 true);
276 if (boom.empty() && m_safeFileAccess) return false;
277 for (auto& path : boom) {
278 // Canonicalise the path
279 if (!path.empty() &&
280 File::TranslatePathKeepRelative(path).empty()) {
281 return false;
284 m_safeFileAccess = !boom.empty();
285 std::string dirs;
286 folly::join(m_open_basedir_separator, boom, dirs);
287 VirtualHost::SortAllowedDirectories(boom);
288 m_allowedDirectoriesInfo.reset(
289 new AllowedDirectoriesInfo(std::move(boom), std::move(dirs)));
290 return true;
293 const std::vector<std::string>&
294 RequestInjectionData::getAllowedDirectoriesProcessed() const {
295 return m_allowedDirectoriesInfo ?
296 m_allowedDirectoriesInfo->vec : VirtualHost::GetAllowedDirectories();
299 void RequestInjectionData::threadInit() {
300 // phpinfo
302 auto setAndGetWall = IniSetting::SetAndGet<int64_t>(
303 [this](const int64_t &limit) {
304 setTimeout(limit);
305 return true;
307 [this] { return getTimeout(); }
309 auto setAndGetCPU = IniSetting::SetAndGet<int64_t>(
310 [this](const int64_t &limit) {
311 setCPUTimeout(limit);
312 return true;
314 [this] { return getCPUTimeout(); }
316 auto setAndGet = RuntimeOption::TimeoutsUseWallTime
317 ? setAndGetWall : setAndGetCPU;
318 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
319 "max_execution_time", setAndGet);
320 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
321 "maximum_execution_time", setAndGet);
322 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
323 "hhvm.max_wall_time", setAndGetWall);
324 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
325 "hhvm.max_cpu_time", setAndGetCPU);
328 // Resource Limits
329 std::string mem_def = std::to_string(RuntimeOption::RequestMemoryMaxBytes);
330 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL, "memory_limit",
331 mem_def.c_str(),
332 IniSetting::SetAndGet<std::string>(
333 [this](const std::string& value) {
334 setMemoryLimit(value);
335 return true;
337 nullptr
338 ), &m_maxMemory);
340 // Data Handling
341 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
342 "arg_separator.output", "&",
343 &m_argSeparatorOutput);
344 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
345 "arg_separator.input", "&",
346 &m_argSeparatorInput);
347 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
348 "variables_order", "EGPCS",
349 &m_variablesOrder);
350 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
351 "request_order", "",
352 &m_requestOrder);
353 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
354 "default_charset", RuntimeOption::DefaultCharsetName.c_str(),
355 &m_defaultCharset);
356 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
357 "default_mimetype", "text/html",
358 &m_defaultMimeType);
360 // Paths and Directories
361 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
362 "include_path", getDefaultIncludePath().c_str(),
363 IniSetting::SetAndGet<std::string>(
364 [this](const std::string& value) {
365 m_include_paths.clear();
366 int pos = value.find(':');
367 if (pos < 0) {
368 m_include_paths.push_back(value);
369 } else {
370 int pos0 = 0;
371 do {
372 // Check for stream wrapper
373 if (value.length() > pos + 2 &&
374 value[pos + 1] == '/' &&
375 value[pos + 2] == '/') {
376 // .:// or ..:// is not stream wrapper
377 if (((pos - pos0) >= 1 && value[pos - 1] != '.') ||
378 ((pos - pos0) >= 2 && value[pos - 2] != '.') ||
379 (pos - pos0) > 2) {
380 pos += 3;
381 continue;
384 m_include_paths.push_back(
385 value.substr(pos0, pos - pos0));
386 pos++;
387 pos0 = pos;
388 } while ((pos = value.find(':', pos)) >= 0);
390 if (pos0 <= value.length()) {
391 m_include_paths.push_back(
392 value.substr(pos0));
395 return true;
397 [this]() {
398 std::string ret;
399 folly::join(":", m_include_paths, ret);
400 return ret;
404 // Paths and Directories
405 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
406 "open_basedir",
407 IniSetting::SetAndGet<std::string>(
408 [this](const std::string& value) {
409 return setAllowedDirectories(value);
411 [this]() -> std::string {
412 if (!hasSafeFileAccess()) {
413 return "";
415 if (m_allowedDirectoriesInfo) {
416 return m_allowedDirectoriesInfo->string;
418 std::string ret;
419 folly::join(m_open_basedir_separator,
420 getAllowedDirectoriesProcessed(),
421 ret);
422 return ret;
426 // Errors and Logging Configuration Options
427 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
428 "error_reporting",
429 std::to_string(RuntimeOption::RuntimeErrorReportingLevel)
430 .c_str(),
431 &m_errorReportingLevel);
432 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
433 "track_errors", "0",
434 &m_trackErrors);
435 IniSetting::Bind(
436 IniSetting::CORE,
437 IniSetting::PHP_INI_ALL,
438 "html_errors",
439 IniSetting::SetAndGet<bool>(
440 [&] (const bool& on) {
441 m_htmlErrors = on;
442 return true;
444 [&] () { return m_htmlErrors; }
446 &m_htmlErrors
449 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
450 "log_errors",
451 IniSetting::SetAndGet<bool>(
452 [this](const bool& on) {
453 if (m_logErrors != on) {
454 if (on) {
455 if (!m_errorLog.empty()) {
456 Logger::SetThreadLog(m_errorLog.data(), true);
458 } else {
459 Logger::ClearThreadLog();
462 return true;
464 nullptr
466 &m_logErrors);
467 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
468 "error_log",
469 IniSetting::SetAndGet<std::string>(
470 [this](const std::string& value) {
471 if (m_logErrors && !value.empty()) {
472 Logger::SetThreadLog(value.data(), true);
474 return true;
476 nullptr
477 ), &m_errorLog);
479 // Filesystem and Streams Configuration Options
480 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
481 "user_agent", "", &m_userAgent);
482 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
483 "default_socket_timeout",
484 std::to_string(RuntimeOption::SocketDefaultTimeout).c_str(),
485 &m_socketDefaultTimeout);
487 // Response handling.
488 // TODO(T5601927): output_compression supports int values where the value
489 // represents the output buffer size. Also need to add a
490 // zlib.output_handler ini setting as well.
491 // http://php.net/zlib.configuration.php
492 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
493 "zlib.output_compression", &m_gzipCompression);
494 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
495 "zlib.output_compression_level", &m_gzipCompressionLevel);
497 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
498 "brotli.chunked_compression", &m_brotliChunkedEnabled);
499 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
500 "brotli.compression", &m_brotliEnabled);
501 IniSetting::Bind(
502 IniSetting::CORE,
503 IniSetting::PHP_INI_ALL,
504 "brotli.compression_quality",
505 std::to_string(RuntimeOption::BrotliCompressionQuality).c_str(),
506 &m_brotliQuality);
507 IniSetting::Bind(
508 IniSetting::CORE,
509 IniSetting::PHP_INI_ALL,
510 "brotli.compression_lgwin",
511 std::to_string(RuntimeOption::BrotliCompressionLgWindowSize).c_str(),
512 &m_brotliLgWindowSize);
514 // Assertions
515 IniSetting::Bind(IniSetting::CORE, IniSetting::PHP_INI_ALL,
516 "zend.assertions", "1",
517 IniSetting::SetAndGet<int64_t>(
518 [this](const int64_t& value) {
519 if ((value >= 0) != RuntimeOption::AssertEmitted) {
520 // Setting the option to < 0 changes a RuntimeOption which affects
521 // bytecode emission, so you can't move between < 0 and >= 0 at
522 // runtime. (This is also a restriction in PHP7 for similar reasons.)
523 raise_warning("zend.assertions may be completely enabled or "
524 "disabled only in php.ini");
525 return false;
527 m_zendAssertions = value;
528 return true;
530 [this]() {
531 return m_zendAssertions;
536 std::string RequestInjectionData::getDefaultIncludePath() {
537 std::string result;
538 folly::join(":", RuntimeOption::IncludeSearchPaths, result);
539 return result;
542 void RequestInjectionData::onSessionInit() {
543 static auto open_basedir_val = []() -> folly::Optional<std::string> {
544 Variant v;
545 if (IniSetting::GetSystem("open_basedir", v)) {
546 return { v.toString().toCppString() };
548 return {};
549 }();
551 rds::requestInit();
552 m_sflagsAndStkPtr = &rds::header()->stackLimitAndSurprise;
553 m_allowedDirectoriesInfo.reset();
554 m_open_basedir_separator = s_PATH_SEPARATOR.toCppString();
555 m_safeFileAccess = RuntimeOption::SafeFileAccess;
556 if (open_basedir_val) {
557 setAllowedDirectories(*open_basedir_val);
559 m_logFunctionCalls = RuntimeOption::EvalFunctionCallSampleRate > 0 &&
560 folly::Random::rand32(
561 RuntimeOption::EvalFunctionCallSampleRate
562 ) == 0;
563 reset();
566 void RequestInjectionData::onTimeout(RequestTimer* timer) {
567 if (timer == &m_timer) {
568 setFlag(TimedOutFlag);
569 #if !defined(__APPLE__) && !defined(_MSC_VER)
570 m_timer.m_timerActive.store(false, std::memory_order_relaxed);
571 #endif
572 } else if (timer == &m_cpuTimer) {
573 setFlag(CPUTimedOutFlag);
574 #if !defined(__APPLE__) && !defined(_MSC_VER)
575 m_cpuTimer.m_timerActive.store(false, std::memory_order_relaxed);
576 #endif
577 } else {
578 always_assert(false && "Unknown timer fired");
582 void RequestInjectionData::setTimeout(int seconds) {
583 m_timer.setTimeout(seconds);
586 void RequestInjectionData::setCPUTimeout(int seconds) {
587 m_cpuTimer.setTimeout(seconds);
590 int RequestInjectionData::getRemainingTime() const {
591 return m_timer.getRemainingTime();
594 int RequestInjectionData::getRemainingCPUTime() const {
595 return m_cpuTimer.getRemainingTime();
599 * If seconds == 0, reset the timeout to the last one set
600 * If seconds < 0, set the timeout to -seconds if there's less than
601 * -seconds remaining.
602 * If seconds > 0, set the timeout to seconds.
604 void RequestInjectionData::resetTimer(int seconds /* = 0 */) {
605 if (seconds == 0) {
606 seconds = getTimeout();
607 } else if (seconds < 0) {
608 if (!getTimeout()) return;
609 seconds = -seconds;
610 if (seconds < getRemainingTime()) return;
612 setTimeout(seconds);
613 clearFlag(TimedOutFlag);
616 void RequestInjectionData::resetCPUTimer(int seconds /* = 0 */) {
617 if (seconds == 0) {
618 seconds = getCPUTimeout();
619 } else if (seconds < 0) {
620 if (!getCPUTimeout()) return;
621 seconds = -seconds;
622 if (seconds < getRemainingCPUTime()) return;
624 setCPUTimeout(seconds);
625 clearFlag(CPUTimedOutFlag);
628 void RequestInjectionData::reset() {
629 m_sflagsAndStkPtr->fetch_and(kSurpriseFlagStackMask);
630 m_coverage = RuntimeOption::RecordCodeCoverage;
631 m_debuggerAttached = false;
632 m_debuggerIntr = false;
633 m_debuggerStepIn = false;
634 m_debuggerStepOut = StepOutState::None;
635 m_debuggerNext = false;
636 m_suppressHackArrayCompatNotices = false;
637 m_breakPointFilter.clear();
638 m_flowFilter.clear();
639 m_lineBreakPointFilter.clear();
640 m_callBreakPointFilter.clear();
641 m_retBreakPointFilter.clear();
642 while (!m_activeLineBreaks.empty()) {
643 m_activeLineBreaks.pop();
645 updateJit();
646 while (!interrupts.empty()) interrupts.pop();
649 void RequestInjectionData::updateJit() {
650 m_jit = RuntimeOption::EvalJit &&
651 !(RuntimeOption::EvalJitDisabledByHphpd && m_debuggerAttached) &&
652 !m_coverage &&
653 isStandardRequest() &&
654 !getDebuggerForceIntr();
657 void RequestInjectionData::clearFlag(SurpriseFlag flag) {
658 assertx(flag >= 1ull << 48);
659 m_sflagsAndStkPtr->fetch_and(~flag);
662 void RequestInjectionData::setFlag(SurpriseFlag flag) {
663 assertx(flag >= 1ull << 48);
664 m_sflagsAndStkPtr->fetch_or(flag);
667 void RequestInjectionData::setMemoryLimit(folly::StringPiece limit) {
668 int64_t newInt = strtoll(limit.begin(), nullptr, 10);
669 if (newInt <= 0) {
670 newInt = std::numeric_limits<int64_t>::max();
671 m_maxMemory = std::to_string(newInt);
672 } else {
673 m_maxMemory = limit.str();
674 newInt = convert_bytes_to_long(limit);
675 if (newInt <= 0) {
676 newInt = std::numeric_limits<int64_t>::max();
679 tl_heap->setMemoryLimit(newInt);
680 m_maxMemoryNumeric = newInt;