Arquivos
hhvm/hphp/tools/bootstrap/idl.cpp
T
Sara Golemon a3f507d58d Add reference to extension object to class_map
Add IDL component:
{
  "extension": {
    "symbol": "s_foo_extension"
  }
}

extension.symbol is the C++ HPHP::Extension symbol for the
extension (if one exists and is exported).
2013-06-27 10:45:56 -07:00

739 linhas
20 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 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/tools/bootstrap/idl.h"
#include <fstream>
#include <unordered_map>
#include <unordered_set>
#include "folly/Format.h"
#include "folly/json.h"
namespace HPHP { namespace IDL {
/////////////////////////////////////////////////////////////////////////////
static const std::unordered_map<fbstring, DataType> g_kindOfMap =
{
{"Boolean", KindOfBoolean},
{"Int32", KindOfInt64},
{"Int64", KindOfInt64},
{"Double", KindOfDouble},
{"String", KindOfString},
{"Int64Vec", KindOfArray},
{"StringVec", KindOfArray},
{"VariantVec", KindOfArray},
{"Int64Map", KindOfArray},
{"StringMap", KindOfArray},
{"VariantMap", KindOfArray},
{"Object", KindOfObject},
{"Resource", KindOfObject},
{"Variant", KindOfAny},
{"Numeric", KindOfAny},
{"Primitive", KindOfAny},
{"PlusOperand", KindOfAny},
{"Sequence", KindOfAny},
{"Any", KindOfAny},
};
static const std::unordered_map<int, fbstring> g_typeMap =
{
{(int)KindOfInvalid, "void"},
{(int)KindOfNull, "HPHP::Variant"},
{(int)KindOfBoolean, "bool"},
{(int)KindOfInt64, "long"},
{(int)KindOfDouble, "double"},
{(int)KindOfString, "HPHP::String"},
{(int)KindOfArray, "HPHP::Array"},
{(int)KindOfObject, "HPHP::Object"},
{(int)KindOfAny, "HPHP::Variant"},
};
static const std::unordered_map<int, fbstring> g_phpTypeMap =
{
{(int)KindOfInvalid, "void"},
{(int)KindOfNull, "void"},
{(int)KindOfBoolean, "bool"},
{(int)KindOfInt64, "long"},
{(int)KindOfDouble, "double"},
{(int)KindOfString, "String"},
{(int)KindOfArray, "Array"},
{(int)KindOfObject, "Object"},
{(int)KindOfAny, "mixed"},
};
static const std::unordered_map<fbstring, FuncFlags> g_flagsMap =
{
{"ZendParamMode", ZendParamMode},
{"IsAbstract", IsAbstract},
{"IsFinal", IsFinal},
{"IsPublic", IsPublic},
{"IsProtected", IsProtected},
{"IsPrivate", IsPrivate},
{"IgnoreRedefinition", IgnoreRedefinition},
{"IsStatic", IsStatic},
{"IsCppAbstract", IsCppAbstract},
{"IsReference", IsReference},
{"IsConstructor", IsConstructor},
{"IsNothing", IsNothing},
{"HasDocComment", HasDocComment},
{"HipHopSpecific", HipHopSpecific},
{"VariableArguments", VariableArguments},
{"RefVariableArguments", RefVariableArguments},
{"MixedVariableArguments", MixedVariableArguments},
{"FunctionIsFoldable", FunctionIsFoldable},
{"NoEffect", NoEffect},
{"NoInjection", NoInjection},
{"HasOptFunction", HasOptFunction},
{"AllowIntercept", AllowIntercept},
{"NoProfile", NoProfile},
{"ContextSensitive", ContextSensitive},
{"NoDefaultSweep", NoDefaultSweep},
{"IsSystem", IsSystem},
{"IsTrait", IsTrait},
{"NeedsActRec", NeedsActRec},
};
static const std::unordered_set<fbstring> g_knownStringConstants =
{ "k_HPHP_TRIM_CHARLIST" };
bool isKindOfIndirect(DataType kindof) {
return (kindof != KindOfBoolean) &&
(kindof != KindOfInt64) &&
(kindof != KindOfDouble) &&
(kindof != KindOfInvalid) &&
(kindof != KindOfNull);
}
// Parse type from a descriptive string, e.g. "int", "bool", etc...
static DataType kindOfFromDynamic(const folly::dynamic& t) {
if (!t.isString()) {
return KindOfInvalid;
}
auto it = g_kindOfMap.find(t.asString());
if (it == g_kindOfMap.end()) {
return KindOfObject;
}
return it->second;
}
// Infer type from an actual value, e.g. 123, "foo", null, true, etc...
static DataType kindOfFromValue(const folly::dynamic& v) {
if (v.isNull()) {
return KindOfNull;
}
if (v.isBool()) {
return KindOfBoolean;
}
if (v.isInt()) {
return KindOfInt64;
}
if (v.isDouble()) {
return KindOfDouble;
}
if (v.isString()) {
return KindOfString;
}
if (v.isArray()) {
return KindOfArray;
}
if (v.isObject()) {
return KindOfObject;
}
return KindOfInvalid;
}
static fbstring phpTypeFromDataType(DataType dt) {
auto it = g_phpTypeMap.find((int)dt);
if (it == g_phpTypeMap.end()) {
return "mixed";
}
return it->second;
}
static fbstring typeString(const folly::dynamic& typeNode, bool isReturnType) {
if (typeNode == "Int32") {
return "int";
}
DataType kindof = kindOfFromDynamic(typeNode);
auto it = g_typeMap.find((int)kindof);
assert(it != g_typeMap.end());
auto& type = it->second;
if (!isReturnType && isKindOfIndirect(kindof)) {
return type + " const&";
} else {
return type;
}
}
static unsigned long parseFlags(const folly::dynamic &flags) {
if (flags.isNull()) {
return 0;
}
if (!flags.isArray()) {
throw std::logic_error("'flags' field must be an array");
}
unsigned long ret = 0;
for (auto &flag : flags) {
auto f = g_flagsMap.find(flag.asString());
if (f == g_flagsMap.end()) {
throw std::logic_error(
folly::format("Unknown flag '{0}' specified", flag.asString()).str()
);
}
ret |= f->second;
}
return ret;
}
static const std::unordered_map<fbstring,fbstring> g_serializedDefaults = {
{"true", "b:1;"},
{"false", "b:0;"},
{"null", "N;"},
{"empty_array", "a:0:{}"},
{"null_string", "N;"},
{"null_array", "N;"},
{"null_object", "N;"},
{"null_variant", "N;"},
{"INT_MAX", "i:2147483647;"}, // (1 << 31) - 1
};
static const std::unordered_map<fbstring,fbstring> g_phpDefaults = {
{"true", "true"},
{"false", "false"},
{"null", "null"},
{"empty_array", "array()"},
{"null_string", "null"},
{"null_array", "null"},
{"null_object", "null"},
{"null_variant", "null"},
{"INT_MAX", "null"},
};
/**
* From idl/base.php:get_serialized_default()
*/
fbstring PhpParam::getDefaultSerialized() const {
auto valIt = m_param.find("value");
if (valIt == m_param.items().end()) {
return ""; // No default
}
auto dval = valIt->second;
if (!dval.isString()) {
throw std::logic_error(
folly::format("Parameter '{0}' default value is non-string",
m_name).str()
);
}
auto val = dval.asString();
if (!val.size()) {
throw std::logic_error(
folly::format("Parameter '{0}' default value malformed (empty string), "
"specify \"\" as default value for actual empty string",
m_name).str()
);
}
// Function calls "foo()" or "foo::bar()" to C/C++ functions/static methods,
// a constant, or a bitmask of constants
//
// Used by ext_reflection to resolve the value at runtime or
// represent the function/method call.
if ((val.size() > 2) &&
(!strncmp(val.c_str(), "k_", 2) ||
!strncmp(val.c_str(), "q_", 2) ||
!strcmp(val.c_str() + val.size() - 2, "()"))) {
return "\x01";
}
// Fixed substitutions
auto it = g_serializedDefaults.find(val);
if (it != g_serializedDefaults.end()) {
return it->second;
}
if (val == "RAND_MAX") {
return folly::to<fbstring>("i:", RAND_MAX, ";");
}
// Quoted string: "foo"
if ((val.size() >= 2) && (val[0] == '"') && (val[val.size()-1] == '"')) {
return phpSerialize(val.substr(1, val.size() - 2));
}
// Integers and Floats
if (strchr(val.c_str(), '.')) {
// Decimal float?
char *e = nullptr;
double dval = strtod(val.c_str(), &e);
if (e && !*e) {
return folly::to<fbstring>("d:", dval, ";");
}
}
if (val[0] == '0') {
if ((val.size() > 1) && (val[1] == 'x')) {
// Hex?
char *e = nullptr;
long lval = strtol(val.c_str() + 2, &e, 16);
if (e && !*e) {
return folly::to<fbstring>("i:", lval, ";");
}
} else {
// Octal?
char *e = nullptr;
long lval = strtol(val.c_str() + 1, &e, 8);
if (e && !*e) {
return folly::to<fbstring>("i:", lval, ";");
}
}
}
// Decimal?
char *e = nullptr;
long lval = strtol(val.c_str(), &e, 10);
if (e && !*e) {
return folly::to<fbstring>("i:", lval, ";");
}
throw std::logic_error(
folly::format("'{0}' is not a valid default arg value", val).str()
);
}
static fbstring transformConstants(const fbstring val) {
fbstring ret = val;
int i = 0;
int len = ret.size();
while (i < len) {
while ((i < len) && (ret[i] == ' ')) i++;
if ((len - i) < 2) break;
if ((ret[i+1] == '_') &&
((ret[i] == 'k') || (ret[i] == 'q'))) {
ret[i] = ret[i+1] = ' ';
}
while ((i < len) && (ret[i] != '|')) {
if (ret[i] == '$') {
ret[i] = ':';
}
i++;
}
i++;
}
return ret;
}
fbstring PhpParam::getDefaultPhp() const {
fbstring val = getDefault();
if (!val.size()) {
return "";
}
auto it = g_phpDefaults.find(val);
if (it != g_phpDefaults.end()) {
return it->second;
}
if (val == "RAND_MAX") {
return folly::to<fbstring>(RAND_MAX);
}
if ((val.size() > 2) && (val[1] == '_') &&
((val[0] == 'k') || (val[0] == 'q'))) {
return transformConstants(val);
}
return val;
}
fbstring phpSerialize(const folly::dynamic& d) {
if (d.isNull()) {
return "N;";
}
if (d.isBool()) {
return d.asBool() ? "b:1;" : "b:0;";
}
if (d.isInt()) {
return "i:" + d.asString() + ";";
}
if (d.isDouble()) {
return "d:" + d.asString() + ";";
}
if (d.isString()) {
auto str = d.asString();
return folly::to<fbstring>("s:", str.size(), ":\"", str, "\";");
}
if (d.isArray()) {
fbstring ret = folly::to<fbstring>("a:", d.size(), ":{");
int i = 0;
for (auto &v : d) {
ret += folly::to<fbstring>("i:", i, ";", phpSerialize(v));
}
return ret + "};";
}
if (d.isObject()) {
fbstring ret = folly::to<fbstring>("a:", d.size(), ":{");
int nextindex = 0;
for (auto &k : d.keys()) {
if (k.isNull()) {
ret += "i:0;";
if (nextindex <= 0) {
nextindex = 1;
}
} else if (k.isInt() || k.isDouble()) {
int i = k.asInt();
ret += folly::to<fbstring>("i:", i, ";");
if (nextindex <= i) {
nextindex = i + 1;
}
} else if (k.isString()) {
ret += folly::to<fbstring>("s:", k.size(), ":\"",
escapeCpp(k.asString()), "\";");
} else {
/* Should never be reached, but cover it to be safe */
ret += folly::to<fbstring>("i:", nextindex++, ";");
}
ret += phpSerialize(d[k]);
}
return ret + "};";
}
throw std::logic_error("Unhandled dynamic type in php serialization");
return "N;";
}
static fbstring getFollyDynamicDefaultString(const folly::dynamic& d,
const fbstring& key,
const fbstring& def) {
auto it = d.find(key);
if (it == d.items().end()) {
return def;
}
auto val = it->second;
if (val.isNull()) {
return def;
}
return val.asString();
}
/////////////////////////////////////////////////////////////////////////////
// PhpConst
bool PhpConst::parseType(const folly::dynamic& cns) {
auto it = cns.find("type");
if (it != cns.items().end()) {
m_kindOf = kindOfFromDynamic(it->second);
m_cppType = typeString(it->second, false);
return true;
}
return false;
}
bool PhpConst::inferType(const folly::dynamic& cns) {
auto it = cns.find("value");
if (it != cns.items().end()) {
m_kindOf = kindOfFromValue(it->second);
auto typeIt = g_typeMap.find((int)m_kindOf);
if (typeIt != g_typeMap.end()) {
m_cppType = typeIt->second;
return true;
}
}
return false;
}
PhpConst::PhpConst(const folly::dynamic& cns,
fbstring cls /* = "" */) :
m_constant(cns),
m_name(cns["name"].asString()),
m_className(cls) {
if (!parseType(cns) && !inferType(cns)) {
// Constant has neither explicit type nor implicit type from 'value'
assert(false);
m_kindOf = KindOfInvalid;
m_cppType = "void";
}
// Override typeString()'s selection for string values
if (m_kindOf == KindOfString) {
m_cppType = "HPHP::StaticString";
}
}
/////////////////////////////////////////////////////////////////////////////
// PhpParam
PhpParam::PhpParam(const folly::dynamic& param,
bool isMagicMethod /*= false */,
ParamMode paramMode /*= CoerceAndCall */) :
m_name(param["name"].asString()),
m_param(param),
m_desc(getFollyDynamicDefaultString(param, "desc", "")),
m_paramMode(paramMode) {
if (isMagicMethod) {
m_kindOf = KindOfAny;
m_cppType = "HPHP::Variant";
return;
}
if (isRef()) {
m_kindOf = KindOfRef;
m_cppType = "HPHP::VRefParamValue const&";
} else {
m_kindOf = kindOfFromDynamic(param["type"]);
m_cppType = typeString(param["type"], false);
}
m_phpType = phpTypeFromDataType(m_kindOf);
}
bool PhpParam::defValueNeedsVariable() const {
DataType cppKindOf = kindOf();
if (!hasDefault() || !isIndirectPass()) {
return false;
}
fbstring defVal = getDefault();
if (cppKindOf == KindOfString &&
((defVal == "empty_string") ||
(defVal == "null_string") ||
(g_knownStringConstants.count(defVal) > 0))) {
return false;
}
if ((cppKindOf == KindOfArray) && (defVal == "null_array")) {
return false;
}
if ((cppKindOf == KindOfObject) && (defVal == "null_object")) {
return false;
}
if ((cppKindOf == KindOfAny) && (defVal == "null_variant")) {
return false;
}
return true;
}
/////////////////////////////////////////////////////////////////////////////
// PhpFunc
PhpFunc::PhpFunc(const folly::dynamic& d,
const fbstring& className) :
m_name(d["name"].asString()),
m_className(className),
m_func(d),
m_desc(getFollyDynamicDefaultString(d, "desc", "")),
m_returnRef(d.getDefault("ref", "false") == "true"),
m_returnKindOf(KindOfNull),
m_returnCppType("void"),
m_returnPhpType("void"),
m_minNumParams(0),
m_numTypeChecks(0) {
auto returnIt = d.find("return");
if (returnIt != d.items().end()) {
auto retNode = returnIt->second;
auto typeIt = retNode.find("type");
if (typeIt != retNode.items().end()) {
auto type = typeIt->second;
if ((type.isString()) && (type != "void") && (type != "null")) {
m_returnKindOf = m_returnRef ? KindOfRef : kindOfFromDynamic(type);
m_returnCppType = typeString(type, true);
m_returnPhpType = phpTypeFromDataType(m_returnKindOf);
}
}
m_returnDesc = getFollyDynamicDefaultString(retNode, "desc", "");
}
auto args = d.find("args");
if (args == d.items().end() || !args->second.isArray()) {
throw std::logic_error(
folly::format("'{0}' must have an array field 'args'", name()).str()
);
}
auto ret = d.find("return");
if (ret == d.items().end() || !ret->second.isObject() ||
ret->second.find("type") == ret->second.items().end()) {
throw std::logic_error(
folly::format("'{0}' must have an array field 'return', which must have "
"a string field 'type'", name()).str()
);
}
bool magic = isMagicMethod();
m_flags = parseFlags(m_func["flags"]);
ParamMode paramMode = (m_flags & ZendParamMode) ?
ParamMode::Zend : ParamMode::CoerceAndCall;
for (auto &p : args->second) {
PhpParam param(p, magic, paramMode);
m_params.push_back(param);
if (!param.hasDefault()) {
++m_minNumParams;
}
if (param.isCheckedType()) {
++m_numTypeChecks;
}
}
}
fbstring PhpFunc::getCppSig() const {
std::ostringstream out;
fbstring nm = name();
fbstring lowername = nm;
std::transform(nm.begin(), nm.end(), lowername.begin(),
std::ptr_fun<int, int>(std::tolower));
if (!isMethod()) {
out << "HPHP::f_" << lowername << "(";
} else {
if (isStatic()) {
out << "HPHP::c_" << className() << "::ti_" << lowername << "(";
} else {
out << "HPHP::c_" << className() << "::t_" << lowername << "(";
}
}
bool firstParam = true;
if (isVarArgs()) {
if (!firstParam) {
out << ", ";
}
out << "int";
firstParam = false;
}
for (auto const& param : m_params) {
if (!firstParam) {
out << ", ";
}
out << param.getCppType();
firstParam = false;
}
if (isVarArgs()) {
assert(!firstParam);
out << ", HPHP::Array const&";
}
out << ")";
return out.str();
}
/////////////////////////////////////////////////////////////////////////////
// PhpProp
PhpProp::PhpProp(const folly::dynamic& d, fbstring cls) :
m_name(d["name"].asString()),
m_className(cls),
m_prop(d),
m_flags(parseFlags(m_prop["flags"])),
m_kindOf(kindOfFromDynamic(m_prop["type"])) {
}
/////////////////////////////////////////////////////////////////////////////
// PhpClass
PhpClass::PhpClass(const folly::dynamic &c) :
m_class(c),
m_name(c["name"].asString()),
m_flags(parseFlags(m_class["flags"])),
m_desc(getFollyDynamicDefaultString(c, "desc", "")) {
auto ifacesIt = m_class.find("ifaces");
if (ifacesIt != m_class.items().end()) {
auto ifaces = ifacesIt->second;
if (!ifaces.isArray()) {
throw std::logic_error(
folly::format("Class {0}.ifaces field must be an array", m_name).str()
);
}
for (auto &interface : ifaces) {
m_ifaces.push_back(interface.asString());
}
}
for (auto const& f : c["funcs"]) {
PhpFunc func(f, m_name);
m_methods.push_back(func);
}
if (c.find("consts") != c.items().end()) {
for (auto const& cns : c["consts"]) {
PhpConst cons(cns, m_name);
m_constants.push_back(cons);
}
}
if (c.find("properties") != c.items().end()) {
for (auto const& prp : c["properties"]) {
PhpProp prop(prp, m_name);
m_properties.push_back(prop);
}
}
}
/////////////////////////////////////////////////////////////////////////////
void parseIDL(const char* idlFilePath,
fbvector<PhpFunc>& funcVec,
fbvector<PhpClass>& classVec,
fbvector<PhpConst>& constVec,
fbvector<PhpExtension>& extVec) {
std::ostringstream jsonString;
std::ifstream infile(idlFilePath);
infile >> jsonString.rdbuf();
auto parsed = folly::parseJson(jsonString.str());
for (auto const& f : parsed["funcs"]) {
PhpFunc func(f, "");
funcVec.push_back(func);
}
for (auto const& c : parsed["classes"]) {
PhpClass klass(c);
classVec.push_back(klass);
}
for (auto const& c : parsed["consts"]) {
PhpConst cns(c);
constVec.push_back(cns);
}
auto it = parsed.find("extension");
if (it != parsed.items().end()) {
PhpExtension ext(it->second);
extVec.push_back(ext);
}
}
void parseIDL(const char* idlFilePath,
fbvector<PhpFunc>& funcVec,
fbvector<PhpClass>& classVec) {
fbvector<PhpConst> consts; // dummy
fbvector<PhpExtension> exts; // dummy
parseIDL(idlFilePath, funcVec, classVec, consts, exts);
}
/////////////////////////////////////////////////////////////////////////////
}} // namespace HPHP::IDL