hphp/runtime/base/intercept.cpp (119 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/base/intercept.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/array-iterator.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/request-event-handler.h"
#include "hphp/runtime/base/req-optional.h"
#include "hphp/runtime/base/unit-cache.h"
#include "hphp/runtime/vm/debugger-hook.h"
#include "hphp/runtime/vm/jit/target-cache.h"
#include "hphp/runtime/vm/unit.h"
#include "hphp/runtime/vm/event-hook.h"
#include "hphp/util/lock.h"
#include "hphp/util/rds-local.h"
#include "hphp/util/trace.h"
///////////////////////////////////////////////////////////////////////////////
namespace HPHP {
TRACE_SET_MOD(intercept);
struct InterceptRequestData final : RequestEventHandler {
InterceptRequestData() {}
void clear() {
m_intercept_handlers.reset();
}
void requestInit() override { clear(); }
void requestShutdown() override { clear(); }
req::hash_map<const Func*, Variant>& intercept_handlers() {
if (!m_intercept_handlers) m_intercept_handlers.emplace();
return *m_intercept_handlers;
}
bool empty() const {
return !m_intercept_handlers.has_value() ||
m_intercept_handlers->empty();
}
private:
// get_intercept_handler() returns Variant* pointing into this map,
// so we need reference stability.
req::Optional<req::hash_map<const Func*, Variant>> m_intercept_handlers;
};
IMPLEMENT_STATIC_REQUEST_LOCAL(InterceptRequestData, s_intercept_data);
///////////////////////////////////////////////////////////////////////////////
bool register_intercept(const String& name, const Variant& callback) {
SCOPE_EXIT {
DEBUGGER_ATTACHED_ONLY(phpDebuggerInterceptRegisterHook(name));
};
auto const interceptedFunc = [&]() -> Func* {
auto const pos = name.find("::");
if (pos != 0 && pos != String::npos && pos + 2 < name.size()) {
auto const cls = Class::load(name.substr(0, pos).get());
if (!cls) return nullptr;
auto const meth = cls->lookupMethod(name.substr(pos + 2).get());
if (!meth || meth->cls() != cls) return nullptr;
return meth;
} else {
return Func::load(name.get());
}
}();
if (interceptedFunc == nullptr) {
// This intercept request can't possibly do anything, we are done.
// TODO: should this throw?
return true;
}
if (!callback.toBoolean()) {
if (!s_intercept_data->empty()) {
auto& handlers = s_intercept_data->intercept_handlers();
auto it = handlers.find(interceptedFunc);
if (it != handlers.end()) {
// erase the map entry before destroying the value
auto tmp = it->second;
handlers.erase(it);
}
}
// We've cleared out all the intercepts, so we don't need to pay the
// surprise flag cost anymore
if (s_intercept_data->empty()) {
EventHook::DisableIntercept();
}
return true;
}
EventHook::EnableIntercept();
auto& handlers = s_intercept_data->intercept_handlers();
handlers[interceptedFunc] = callback;
interceptedFunc->setMaybeIntercepted();
return true;
}
Variant* get_intercept_handler(const Func* func) {
FTRACE(1, "get_intercept_handler {}\n", func->fullName());
if (!s_intercept_data->empty()) {
auto& handlers = s_intercept_data->intercept_handlers();
auto iter = handlers.find(func);
if (iter != handlers.end()) {
assertx(func->maybeIntercepted());
return &iter->second;
}
}
return nullptr;
}
///////////////////////////////////////////////////////////////////////////////
// fb_rename_function()
void rename_function(const String& old_name, const String& new_name) {
auto const old = old_name.get();
auto const n3w = new_name.get();
auto const oldNe = const_cast<NamedEntity*>(NamedEntity::get(old));
auto const newNe = const_cast<NamedEntity*>(NamedEntity::get(n3w));
Func* func = Func::lookup(oldNe);
if (!func) {
// It's the caller's responsibility to ensure that the old function
// exists.
not_reached();
}
// Interceptable functions can be renamed even when
// JitEnableRenameFunction is false.
if (!(func->attrs() & AttrInterceptable)) {
if (!RuntimeOption::EvalJitEnableRenameFunction) {
// When EvalJitEnableRenameFunction is false, the translator may
// wire non-AttrInterceptable Func*'s into the TC. Don't rename
// functions.
raise_error("fb_rename_function must be explicitly enabled"
"(-v Eval.JitEnableRenameFunction=true)");
}
}
auto const fnew = Func::lookup(newNe);
if (fnew && fnew != func) {
raise_error("Function already defined: %s", n3w->data());
}
always_assert(
!rds::isPersistentHandle(oldNe->getFuncHandle(func->fullName()))
);
oldNe->setCachedFunc(nullptr);
newNe->m_cachedFunc.bind(
rds::Mode::Normal,
rds::LinkName{"NEFunc", fnew ? fnew->fullName() : makeStaticString(n3w)}
);
newNe->setCachedFunc(func);
if (RuntimeOption::EvalJit) {
jit::invalidateForRenameFunction(old);
}
}
///////////////////////////////////////////////////////////////////////////////
}