/* +----------------------------------------------------------------------+ | 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 #include #include #include "folly/Format.h" #include "folly/json.h" #ifdef __APPLE__ #define INT64_TYPE "long long" #else #define INT64_TYPE "long" #endif namespace HPHP { namespace IDL { ///////////////////////////////////////////////////////////////////////////// static const std::unordered_map 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 g_typeMap = { {(int)KindOfInvalid, "void"}, {(int)KindOfNull, "HPHP::Variant"}, {(int)KindOfBoolean, "bool"}, {(int)KindOfInt64, INT64_TYPE}, {(int)KindOfDouble, "double"}, {(int)KindOfString, "HPHP::String"}, {(int)KindOfArray, "HPHP::Array"}, {(int)KindOfObject, "HPHP::Object"}, {(int)KindOfAny, "HPHP::Variant"}, }; static const std::unordered_map g_phpTypeMap = { {(int)KindOfInvalid, "void"}, {(int)KindOfNull, "void"}, {(int)KindOfBoolean, "bool"}, {(int)KindOfInt64, INT64_TYPE}, {(int)KindOfDouble, "double"}, {(int)KindOfString, "String"}, {(int)KindOfArray, "Array"}, {(int)KindOfObject, "Object"}, {(int)KindOfAny, "mixed"}, }; static const std::unordered_map 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 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 g_serializedDefaults = { {"true", "b:1;"}, {"false", "b:0;"}, {"null", "N;"}, {"empty_array", "a:0:{}"}, {"null_string", "N;"}, {"null_array", "N;"}, {"null_object", "N;"}, {"null_resource", "N;"}, {"null_variant", "N;"}, {"INT_MAX", "i:2147483647;"}, // (1 << 31) - 1 }; static const std::unordered_map g_phpDefaults = { {"true", "true"}, {"false", "false"}, {"null", "null"}, {"empty_array", "array()"}, {"null_string", "null"}, {"null_array", "null"}, {"null_object", "null"}, {"null_resource", "null"}, {"null_variant", "null"}, {"INT_MAX", "null"}, }; static fbstring unescapeString(fbstring val) { fbstring s = ""; for (int i = 0; i < val.size(); ) { int ch = val[i++]; if (ch == '\\') { if (i == val.size()) { throw std::logic_error( folly::format("Malformed string: '{0}'", val).str()); } ch = val[i++]; switch (ch) { case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case '/': case '"': case '\'': case '\\':break; case '0': ch = 0; if (i == val.size() || (!isdigit(val[i]) && val[i] != 'x' && val[i] != 'X')) { break; } // fall through default: throw std::logic_error( folly::format("Malformed string: '{0}'", val).str()); break; } } s += (char)ch; } return s; } /** * 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("i:", RAND_MAX, ";"); } // Quoted string: "foo" if ((val.size() >= 2) && (val[0] == '"') && (val[val.size()-1] == '"')) { auto s = unescapeString(val.substr(1, val.size() - 2)); return phpSerialize(s); } // 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("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("i:", lval, ";"); } } else { // Octal? char *e = nullptr; long lval = strtol(val.c_str() + 1, &e, 8); if (e && !*e) { return folly::to("i:", lval, ";"); } } } // Decimal? char *e = nullptr; long lval = strtol(val.c_str(), &e, 10); if (e && !*e) { return folly::to("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(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("s:", str.size(), ":\"", str, "\";"); } if (d.isArray()) { fbstring ret = folly::to("a:", d.size(), ":{"); int i = 0; for (auto &v : d) { ret += folly::to("i:", i, ";", phpSerialize(v)); } return ret + "};"; } if (d.isObject()) { fbstring ret = folly::to("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("i:", i, ";"); if (nextindex <= i) { nextindex = i + 1; } } else if (k.isString()) { ret += folly::to("s:", k.size(), ":\"", escapeCpp(k.asString()), "\";"); } else { /* Should never be reached, but cover it to be safe */ ret += folly::to("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" || defVal == "null_resource")) { 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(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& funcVec, fbvector& classVec, fbvector& constVec, fbvector& 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& funcVec, fbvector& classVec) { fbvector consts; // dummy fbvector exts; // dummy parseIDL(idlFilePath, funcVec, classVec, consts, exts); } ///////////////////////////////////////////////////////////////////////////// }} // namespace HPHP::IDL