hphp/runtime/vm/runtime.cpp (344 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/base/execution-context.h"
#include "hphp/runtime/base/coeffects-config.h"
#include "hphp/runtime/server/source-root-info.h"
#include "hphp/runtime/base/zend-string.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/request-info.h"
#include "hphp/runtime/base/unit-cache.h"
#include "hphp/runtime/base/vanilla-dict.h"
#include "hphp/runtime/ext/std/ext_std_closure.h"
#include "hphp/runtime/ext/generator/ext_generator.h"
#include "hphp/runtime/vm/bytecode.h"
#include "hphp/util/trace.h"
#include "hphp/util/text-util.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/runtime/base/zend-functions.h"
#include "hphp/runtime/ext/string/ext_string.h"
#include <folly/tracing/StaticTracepoint.h>
#include <folly/Random.h>
namespace HPHP {
TRACE_SET_MOD(runtime);
/**
* print_string will decRef the string
*/
void print_string(StringData* s) {
g_context->write(s->data(), s->size());
TRACE(2, "t-x64 output(str): (%p) %43s\n", s->data(),
escapeStringForCPP(s->data(), s->size()).data());
decRefStr(s);
}
void print_int(int64_t i) {
char intbuf[21];
auto const s = conv_10(i, intbuf + sizeof(intbuf));
g_context->write(s.data(), s.size());
}
void print_boolean(bool val) {
if (val) {
g_context->write("1", 1);
}
}
/**
* concat_ss will will incRef the output string
* and decref its first argument
*/
StringData* concat_ss(StringData* v1, StringData* v2) {
if (v1->cowCheck()) {
FOLLY_SDT(hhvm, hhvm_cow_concat, v1->size(), v2->size());
StringData* ret = StringData::Make(v1, v2);
// Because v1 was shared, we know this won't release the string.
v1->decRefCount();
return ret;
}
auto const rhs = v2->slice();
UNUSED auto const lsize = v1->size();
FOLLY_SDT(hhvm, hhvm_mut_concat, lsize, rhs.size());
auto const ret = v1->append(rhs);
if (UNLIKELY(ret != v1)) {
// had to realloc even though count==1
assertx(v1->hasExactlyOneRef());
v1->release();
}
return ret;
}
/**
* concat_is will incRef the output string
*/
StringData* concat_is(int64_t v1, StringData* v2) {
char intbuf[21];
// Convert the int to a string
auto const s1 = conv_10(v1, intbuf + sizeof(intbuf));
auto const s2 = v2->slice();
return StringData::Make(s1, s2);
}
/**
* concat_si will incRef the output string
* and decref its first argument
*/
StringData* concat_si(StringData* v1, int64_t v2) {
char intbuf[21];
auto const s2 = conv_10(v2, intbuf + sizeof(intbuf));
if (v1->cowCheck()) {
auto const s1 = v1->slice();
FOLLY_SDT(hhvm, hhvm_cow_concat, s1.size(), s2.size());
auto const ret = StringData::Make(s1, s2);
// Because v1 was shared, we know this won't release it.
v1->decRefCount();
return ret;
}
UNUSED auto const lsize = v1->size();
FOLLY_SDT(hhvm, hhvm_mut_concat, lsize, s2.size());
auto const ret = v1->append(s2);
if (UNLIKELY(ret != v1)) {
// had to realloc even though count==1
assertx(v1->hasExactlyOneRef());
v1->release();
}
return ret;
}
StringData* concat_s3(StringData* v1, StringData* v2, StringData* v3) {
if (v1->cowCheck()) {
auto s1 = v1->slice();
auto s2 = v2->slice();
auto s3 = v3->slice();
FOLLY_SDT(hhvm, hhvm_cow_concat, s1.size(), s2.size() + s3.size());
StringData* ret = StringData::Make(s1, s2, s3);
// Because v1 was shared, we know this won't release it.
v1->decRefCount();
return ret;
}
UNUSED auto const lsize = v1->size();
FOLLY_SDT(hhvm, hhvm_mut_concat, lsize, v2->size() + v3->size());
auto const ret = v1->append(v2->slice(), v3->slice());
if (UNLIKELY(ret != v1)) {
// had to realloc even though count==1
assertx(v1->hasExactlyOneRef());
v1->release();
}
return ret;
}
StringData* concat_s4(StringData* v1, StringData* v2,
StringData* v3, StringData* v4) {
if (v1->cowCheck()) {
auto s1 = v1->slice();
auto s2 = v2->slice();
auto s3 = v3->slice();
auto s4 = v4->slice();
FOLLY_SDT(hhvm, hhvm_cow_concat, s1.size(),
s2.size() + s3.size() + s4.size());
StringData* ret = StringData::Make(s1, s2, s3, s4);
// Because v1 was shared, we know this won't release it.
v1->decRefCount();
return ret;
}
UNUSED auto const lsize = v1->size();
FOLLY_SDT(hhvm, hhvm_mut_concat, lsize,
v2->size() + v3->size() + v4->size());
auto const ret = v1->append(v2->slice(), v3->slice(), v4->slice());
if (UNLIKELY(ret != v1)) {
// had to realloc even though count==1
assertx(v1->hasExactlyOneRef());
v1->release();
}
return ret;
}
void raiseWarning(const StringData* sd) {
raise_warning("%s", sd->data());
}
void raiseNotice(const StringData* sd) {
raise_notice("%s", sd->data());
}
void throwArrayIndexException(const ArrayData* ad, const int64_t index) {
throwOOBArrayKeyException(index, ad);
}
void throwArrayKeyException(const ArrayData* ad, const StringData* key) {
assertx(ad->isDictType());
throwOOBArrayKeyException(key, ad);
}
void throwMustBeEnclosedInReadonlyException(const Class* cls, const StringData* propName) {
throw_must_be_enclosed_in_readonly(cls->name()->data(), propName->data());
}
void throwMustBeReadonlyException(const Class* cls, const StringData* propName) {
throw_must_be_readonly(cls->name()->data(), propName->data());
}
void throwMustBeMutableException(const Class* cls, const StringData* propName) {
throw_must_be_mutable(cls->name()->data(), propName->data());
}
void throwOrWarnLocalMustBeValueTypeException(const StringData* locName) {
throw_local_must_be_value_type(locName->data());
}
void throwMustBeValueTypeException(const Class* cls, const StringData* propName) {
throw_must_be_value_type(cls->name()->data(), propName->data());
}
std::string formatParamInOutMismatch(const char* fname, uint32_t index,
bool funcByRef) {
if (funcByRef) {
return folly::sformat(
"{}() expects parameter {} to be inout, but the call was "
"not annotated with 'inout'", fname, index + 1
);
} else {
return folly::sformat(
"{}() does not expect parameter {} to be inout, but the call was "
"annotated with 'inout'", fname, index + 1
);
}
}
void throwParamInOutMismatch(const Func* func, uint32_t index) {
SystemLib::throwInvalidArgumentExceptionObject(formatParamInOutMismatch(
func->fullName()->data(), index, func->isInOut(index)));
}
void checkInOutMismatch(const Func* func, uint32_t numArgs,
const uint8_t* inoutArgs) {
assertx(numArgs == 0 || inoutArgs != nullptr);
uint8_t tmp = 0;
for (auto i = 0; i < numArgs; ++i) {
tmp = (i % 8) == 0 ? *(inoutArgs++) : tmp >> 1;
if (func->isInOut(i) != (tmp & 1)) throwParamInOutMismatch(func, i);
}
}
std::string formatParamReadonlyMismatch(const char* fname, uint32_t index) {
return folly::sformat(
"{}() does not allow for parameter {} to be passed readonly, but the argument "
"was annotated with 'readonly'", fname, index + 1
);
}
std::string formatReturnReadonlyMismatch(const char* fname) {
return folly::sformat(
"{}() returns readonly, but the caller expects a mutable value",
fname
);
}
std::string formatReadonlyThisMismatch(const char* fname) {
return folly::sformat(
"{}() modifies the instance, but the caller has a readonly instance",
fname
);
}
void throwReadonlyMismatch(const Func* func, int32_t index) {
auto const msg = [&] {
if (index == kReadonlyReturnId) {
return formatReturnReadonlyMismatch(func->fullName()->data());
}
if (index == kReadonlyThisId) {
return formatReadonlyThisMismatch(func->fullName()->data());
}
return formatParamReadonlyMismatch(func->fullName()->data(), index);
}();
SystemLib::throwReadonlyViolationExceptionObject(msg);
}
void checkReadonlyMismatch(const Func* func, uint32_t numArgs,
const uint8_t* readonlyArgs) {
assertx(numArgs == 0 || readonlyArgs != nullptr);
uint8_t tmp = 0;
for (auto i = 0; i < numArgs; ++i) {
tmp = (i % 8) == 0 ? *(readonlyArgs++) : tmp >> 1;
if ((tmp & 1) && !func->isReadonly(i)) throwReadonlyMismatch(func, i);
}
}
void throwInvalidUnpackArgs() {
SystemLib::throwInvalidArgumentExceptionObject(
"Only containers may be unpacked");
}
namespace {
std::string formatArgumentErrMsg(const Func* func, const char* amount,
uint32_t expected, uint32_t got) {
return folly::sformat(
"{}() expects {} {} parameter{}, {} given",
func->fullNameWithClosureName(),
amount,
expected,
expected == 1 ? "" : "s",
got
);
}
}
void throwMissingArgument(const Func* func, int got) {
auto const expected = func->numRequiredParams();
assertx(got < expected);
auto const amount = expected < func->numParams() ? "at least" : "exactly";
auto const errMsg = formatArgumentErrMsg(func, amount, expected, got);
SystemLib::throwRuntimeExceptionObject(Variant(errMsg));
}
void raiseTooManyArguments(const Func* func, int got) {
assertx(!func->hasVariadicCaptureParam());
if (!RuntimeOption::EvalWarnOnTooManyArguments && !func->isCPPBuiltin()) {
return;
}
auto const total = func->numNonVariadicParams();
assertx(got > total);
auto const amount = func->numRequiredParams() < total ? "at most" : "exactly";
auto const errMsg = formatArgumentErrMsg(func, amount, total, got);
if (RuntimeOption::EvalWarnOnTooManyArguments > 1 || func->isCPPBuiltin()) {
SystemLib::throwRuntimeExceptionObject(Variant(errMsg));
} else {
raise_warning(errMsg);
}
}
void raiseTooManyArgumentsPrologue(const Func* func, ArrayData* unpackArgs) {
SCOPE_EXIT { decRefArr(unpackArgs); };
if (unpackArgs->empty()) return;
auto const got = func->numNonVariadicParams() + unpackArgs->size();
raiseTooManyArguments(func, got);
}
//////////////////////////////////////////////////////////////////////
RDS_LOCAL_NO_CHECK(uint64_t, rl_num_coeffect_violations){0};
void raiseCoeffectsCallViolation(const Func* callee,
RuntimeCoeffects provided,
RuntimeCoeffects required) {
assertx(CoeffectsConfig::enabled());
auto const callerName = [&] {
VMRegAnchor _;
if (!vmfp()) {
// VM is entering to the first frame
return String{makeStaticString("[vm-entry]")};
}
String result;
walkStack([&] (const BTFrame& frm) {
assertx(frm);
auto const func = frm.func();
assertx(func);
if (func->hasCoeffectRules() &&
func->getCoeffectRules().size() == 1 &&
func->getCoeffectRules()[0].isCaller()) {
return false; // keep going
}
result = func->fullNameWithClosureName();
return true;
});
assertx(!result.isNull());
return result;
};
auto const errMsg = [&] {
return folly::sformat(
"Call to {}() requires [{}] coeffects but {}() provided [{}]",
callee->fullNameWithClosureName(),
required.toString(),
callerName(),
provided.toString()
);
};
FTRACE_MOD(Trace::coeffects, 1, "{}\n {:016b} -> {:016b}\n",
errMsg(), provided.value(), required.value());
assertx(!provided.canCall(required));
if (provided.canCallWithWarning(required)) {
if (*rl_num_coeffect_violations >= RO::EvalCoeffectViolationWarningMax) {
return;
}
(*rl_num_coeffect_violations)++;
raise_warning(errMsg());
} else {
SystemLib::throwCoeffectViolationExceptionObject(errMsg());
}
}
void raiseCoeffectsFunParamTypeViolation(TypedValue tv,
int32_t paramIdx) {
auto const errMsg =
folly::sformat("Coeffect rule requires parameter at position {} to be a "
"closure object, function/method pointer or null but "
"{} given",
paramIdx + 1, describe_actual_type(&tv));
if (CoeffectsConfig::throws()) {
SystemLib::throwInvalidArgumentExceptionObject(errMsg);
}
raise_warning(errMsg);
}
void raiseCoeffectsFunParamCoeffectRulesViolation(const Func* f) {
assertx(f);
auto const errMsg =
folly::sformat("Function/method pointer to {}() contains polymorphic "
"coeffects but is used as a coeffect rule",
f->fullNameWithClosureName());
if (CoeffectsConfig::throws()) {
SystemLib::throwInvalidArgumentExceptionObject(errMsg);
}
raise_warning(errMsg);
}
//////////////////////////////////////////////////////////////////////
int64_t zero_error_level() {
auto& id = RequestInfo::s_requestInfo.getNoCheck()->m_reqInjectionData;
auto level = id.getErrorReportingLevel();
id.setErrorReportingLevel(0);
return level;
}
void restore_error_level(int64_t oldLevel) {
auto& id = RequestInfo::s_requestInfo.getNoCheck()->m_reqInjectionData;
if (id.getErrorReportingLevel() == 0) {
id.setErrorReportingLevel(oldLevel);
}
}
//////////////////////////////////////////////////////////////////////
}