8283969d81
Most CPP extension classes don’t think about serialization, and some require more initialization than the default contractor provides. The result is that on deserialization we end up with a partially initialized object, and undefined behavior after that. Modified the serializer to emit __PHP_Unserializable_Class instead, and just the class name for the debugger case. Modified the deserializer to create a __PHP_Unserializable_Class, too, if we are given a string with a CPP extension instance in it, so we're safe on both sides. I do not raise a warning; the results are pretty clear, I think, and it's also unlikely that most such warnings could (or should) be corrected. Finally, I also removed the odd behavior of emitting "dummy" classes for Closure and Continuation. I found no use of these in any source tree. Closure had no implementation at all, and Continuation had an implementation that simply raised a fatal on any method call. Thus we can safely assume that even if someone was serializing one of these types, they were receiving an object that was useless to them, or would fatal. So I believe this transformation is safe.
411 linhas
12 KiB
C++
411 linhas
12 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 <algorithm>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
#include "folly/FBString.h"
|
|
#include "folly/FBVector.h"
|
|
|
|
#include "hphp/tools/bootstrap/idl.h"
|
|
|
|
using folly::fbstring;
|
|
using folly::fbvector;
|
|
using namespace HPHP::IDL;
|
|
using namespace HPHP;
|
|
|
|
#define VISIBILITY_MASK (IsPublic|IsProtected|IsPrivate)
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Helpers
|
|
|
|
static inline fbstring castLong(unsigned long lval, bool hex = false) {
|
|
if (hex) {
|
|
char buffer[10];
|
|
snprintf(buffer, sizeof(buffer), "%lx", lval & 0xffffffff);
|
|
return folly::to<fbstring>("(const char *)0x", buffer);
|
|
}
|
|
return folly::to<fbstring>("(const char*)", lval);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Generated doc comments
|
|
|
|
static fbstring formatDocComment(const fbstring& comment) {
|
|
const char *p = comment.c_str();
|
|
const char *s;
|
|
const char *e = p + comment.size();
|
|
fbstring ret("/**\n");
|
|
while ((s = strchr(p, '\n'))) {
|
|
ret += " *";
|
|
if ((s - p) > 1) {
|
|
ret += " " + fbstring(p, (s - p));
|
|
}
|
|
ret += "\n";
|
|
p = s + 1;
|
|
}
|
|
if (p < e) {
|
|
ret += " * " + fbstring(p) + "\n";
|
|
}
|
|
return ret + " */";
|
|
}
|
|
|
|
static fbstring genDocCommentPreamble(const fbstring& name,
|
|
const fbstring& desc,
|
|
long flags,
|
|
const fbstring& classname) {
|
|
fbstring ret;
|
|
|
|
if (flags & HipHopSpecific) {
|
|
ret = "( HipHop specific )";
|
|
} else {
|
|
ret = "( excerpt from http://php.net/manual/en/";
|
|
if (classname.size()) {
|
|
ret += classname + ".";
|
|
} else {
|
|
ret += "function.";
|
|
}
|
|
ret += name + ".php )";
|
|
}
|
|
|
|
if (desc.size()) {
|
|
ret += "\n" + desc;
|
|
}
|
|
return ret + "\n\n";
|
|
}
|
|
|
|
static fbstring genDocComment(const PhpFunc& func,
|
|
const fbstring& classname) {
|
|
fbstring ret(genDocCommentPreamble(func.name(), func.getDesc(),
|
|
func.flags(), classname));
|
|
|
|
for (auto ¶m : func.params()) {
|
|
ret += "@" + param.name() + " " + param.getPhpType() + " ";
|
|
if (param.isRef()) {
|
|
ret += "(output) ";
|
|
}
|
|
ret += param.getDesc() + "\n";
|
|
}
|
|
|
|
if (func.numParams() > 0) {
|
|
ret += "\n";
|
|
}
|
|
|
|
auto rko = func.returnKindOf();
|
|
if ((rko != KindOfNull) && (rko != KindOfInvalid)) {
|
|
ret += "@return " + func.returnPhpType() + " ";
|
|
if (func.isReturnRef()) {
|
|
ret += "(output) ";
|
|
}
|
|
ret += func.returnDesc() + "\n";
|
|
}
|
|
|
|
return formatDocComment(ret);
|
|
}
|
|
|
|
static fbstring genDocComment(const PhpClass& cls) {
|
|
return formatDocComment(
|
|
genDocCommentPreamble(cls.name(), cls.getDesc(), cls.flags(), "")
|
|
);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// System constants
|
|
|
|
static void declareConstants(std::ostream &out,
|
|
const fbvector<PhpConst>& consts,
|
|
bool extrn) {
|
|
for (auto c : consts) {
|
|
if (!c.hasValue()) {
|
|
continue;
|
|
}
|
|
|
|
if (extrn) {
|
|
out << "extern const " << c.getCppType() << " " << c.varname() << ";\n";
|
|
} else if (c.kindOf() == KindOfString) {
|
|
fbstring val = c.value();
|
|
out << "extern const StaticString " << c.varname()
|
|
<< "(\"" << escapeCpp(val) << "\"," << val.size() << ");\n";
|
|
} else {
|
|
out << "const " << c.getCppType() << " " << c.varname()
|
|
<< " = " << escapeCpp(c.value()) << ";\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
static void outputConstants(const char *outputfn,
|
|
const fbvector<PhpConst>& consts) {
|
|
std::ofstream out(outputfn);
|
|
|
|
out << "// @" "generated by gen-class-map.cpp\n"
|
|
<< "#ifndef _H_SYSTEM_CONSTANTS\n"
|
|
<< "#define _H_SYSTEM_CONSTANTS\n"
|
|
<< "namespace HPHP {\n"
|
|
<< "class StaticString;\n"
|
|
<< "class Variant;\n";
|
|
declareConstants(out, consts, true);
|
|
out << "} // namespace HPHP\n"
|
|
<< "#endif // _H_SYSTEM_CONSTANTS\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Class Map
|
|
|
|
#define FUNC_FLAG_MASK (IsProtected|IsPrivate|IsPublic|\
|
|
IsAbstract|IsStatic|IsFinal|HasDocComment|\
|
|
AllowIntercept|NoProfile|ContextSensitive|\
|
|
HipHopSpecific|VariableArguments|\
|
|
RefVariableArguments|MixedVariableArguments|\
|
|
NeedsActRec|FunctionIsFoldable|\
|
|
NoInjection|NoEffect|HasOptFunction|ZendParamMode)
|
|
|
|
static void writeFunction(std::ostream& out, const PhpFunc& func) {
|
|
auto flags = (func.flags() & FUNC_FLAG_MASK) | IsSystem | IsNothing;
|
|
|
|
if (flags & RefVariableArguments) {
|
|
flags |= VariableArguments;
|
|
}
|
|
if (flags & MixedVariableArguments) {
|
|
flags |= RefVariableArguments | VariableArguments;
|
|
}
|
|
if (!func.isMethod() || !(flags & VISIBILITY_MASK)) {
|
|
flags |= IsPublic;
|
|
}
|
|
if (func.isReturnRef()) {
|
|
flags |= IsReference;
|
|
}
|
|
|
|
out << " " << castLong(flags, true) << ", \"" << func.name() << "\", "
|
|
<< "\"\", "
|
|
<< castLong(0) << ", "
|
|
<< castLong(0) << ",\n";
|
|
|
|
if (flags & HasDocComment) {
|
|
out << " \""
|
|
<< escapeCpp(genDocComment(func, func.className()))
|
|
<< "\",\n";
|
|
}
|
|
|
|
DataType rko = func.returnKindOf();
|
|
if (rko == KindOfAny) {
|
|
// ClassInfo::MethodInfo expects this for Any/Variant
|
|
// TODO: Fix that broken assumption
|
|
rko = KindOfInvalid;
|
|
}
|
|
out << " " << castLong(rko, true) << ",\n";
|
|
for (auto &p : func.params()) {
|
|
long attr = IsNothing;
|
|
DataType ko = p.kindOf();
|
|
if (p.isRef()) {
|
|
// We don't declare param type as KindOfRef
|
|
// as then the caller will try to cast it as such
|
|
attr |= IsReference;
|
|
ko = KindOfAny;
|
|
}
|
|
if (ko == KindOfAny) {
|
|
// TODO: See above
|
|
ko = KindOfInvalid;
|
|
}
|
|
out << " "
|
|
<< castLong(attr, true) << ", "
|
|
<< "\"" << escapeCpp(p.name()) << "\", \"\", "
|
|
<< castLong(ko, true) << ",\n";
|
|
auto ser = p.getDefaultSerialized();
|
|
auto val = p.getDefaultPhp();
|
|
out << " "
|
|
<< "\"" << escapeCpp(ser) << "\", " << castLong(ser.size()) << ", "
|
|
<< "\"" << escapeCpp(val) << "\", " << castLong(val.size()) << ", "
|
|
<< "NULL,\n";
|
|
}
|
|
out << " NULL,\n"
|
|
<< " NULL,\n"
|
|
<< " NULL,\n";
|
|
}
|
|
|
|
static void writeConstant(std::ostream& out, const PhpConst& cns) {
|
|
auto name = cns.name();
|
|
out << " \"" << escapeCpp(name) << "\", ";
|
|
if (cns.hasValue()) {
|
|
auto ser = cns.serialize();
|
|
out << castLong(ser.size())
|
|
<< ", \"" << escapeCpp(ser) << "\",\n";
|
|
return;
|
|
}
|
|
|
|
if (cns.isSystem()) {
|
|
// Special "magic" constants
|
|
if ((name == "SID") || (name == "PHP_SAPI") || (name == "PHP_BINARY")) {
|
|
out << "(const char *)((offsetof(EnvConstants, k_" << name << ") - "
|
|
<< "offsetof(EnvConstants, stgv_Variant)) / sizeof(Variant)), "
|
|
<< castLong(1) << ",\n";
|
|
return;
|
|
}
|
|
if ((name == "STDIN") || (name == "STDOUT") || (name == "STDERR")) {
|
|
out << "(const char *)&BuiltinFiles::Get" << name << ", NULL,\n";
|
|
return;
|
|
}
|
|
}
|
|
|
|
out << "(const char *)&" << cns.varname() << ", "
|
|
<< castLong((int)cns.kindOf() + 2) << ",\n";
|
|
}
|
|
|
|
#define CLASS_FLAG_MASK (IsAbstract|IsFinal|NoDefaultSweep|\
|
|
HipHopSpecific|HasDocComment|IsCppSerializable)
|
|
#define PROP_FLAG_MASK (IsProtected|IsPrivate|IsPublic|IsStatic)
|
|
|
|
static void writeClass(std::ostream& out, const PhpClass& cls) {
|
|
auto flags = (cls.flags() & CLASS_FLAG_MASK) | IsSystem | IsNothing;
|
|
|
|
out << " " << castLong(flags, true) << ", "
|
|
<< "\"" << escapeCpp(cls.name()) << "\", "
|
|
<< "\"" << escapeCpp(strtolower(cls.parent())) << "\", "
|
|
<< "\"\", "
|
|
<< castLong(0) << ", "
|
|
<< castLong(0) << ",\n";
|
|
|
|
if (flags & HasDocComment) {
|
|
out << " \"" << escapeCpp(genDocComment(cls)) << "\",\n";
|
|
}
|
|
|
|
out << " ";
|
|
for (auto &iface : cls.ifaces()) {
|
|
out << "\"" << escapeCpp(strtolower(iface)) << "\", ";
|
|
}
|
|
out << "NULL,\n";
|
|
|
|
for (auto &method : cls.methods()) {
|
|
writeFunction(out, method);
|
|
}
|
|
out << " NULL,\n";
|
|
|
|
for (auto &prop : cls.properties()) {
|
|
auto propflag = (prop.flags() & PROP_FLAG_MASK) | IsNothing;
|
|
if (!(propflag & VISIBILITY_MASK)) {
|
|
propflag |= IsPublic;
|
|
}
|
|
out << " " << castLong(propflag, true) << ", "
|
|
<< "\"" << escapeCpp(prop.name()) << "\", "
|
|
<< castLong((int)prop.kindOf(), true) << ",\n";
|
|
}
|
|
out << " NULL,\n";
|
|
|
|
for (auto &cns : cls.constants()) {
|
|
writeConstant(out, cns);
|
|
}
|
|
out << " NULL,\n";
|
|
|
|
out << " NULL,\n"; // no attributes
|
|
}
|
|
|
|
static void outputClassMap(const char *outputfn, const char *classmap_name,
|
|
const fbvector<PhpClass>& classes,
|
|
const fbvector<PhpFunc>& funcs,
|
|
const fbvector<PhpConst>& consts,
|
|
const fbvector<PhpExtension>& exts) {
|
|
std::ofstream out(outputfn);
|
|
|
|
out << "// @" "generated by gen-class-map.cpp\n"
|
|
<< "#include \"hphp/runtime/base/base_includes.h\"\n"
|
|
<< "#include \"hphp/runtime/ext/ext.h\"\n"
|
|
<< "namespace HPHP {\n";
|
|
declareConstants(out, consts, false);
|
|
out << "const char *" << classmap_name << "[] = {\n";
|
|
|
|
for (auto &e : exts) {
|
|
auto sym = e.symbol();
|
|
if (!sym.empty()) {
|
|
out << " (const char*)&" << sym << ",\n";
|
|
}
|
|
}
|
|
out << " NULL,\n"; // End of extensions
|
|
|
|
out << " (const char *)ClassInfo::IsSystem, NULL, "
|
|
<< "\"\", \"\", NULL, NULL, NULL,\n";
|
|
for (auto &f : funcs) {
|
|
writeFunction(out, f);
|
|
}
|
|
out << " NULL,\n" // End of functions
|
|
<< " NULL,\n"; // End of system "properties"
|
|
for (auto &c : consts) {
|
|
writeConstant(out, c);
|
|
}
|
|
out << " NULL,\n" // End of constants
|
|
<< " NULL,\n"; // End of system "attributes"
|
|
for (auto &c : classes) {
|
|
writeClass(out, c);
|
|
}
|
|
out << " NULL,\n" // End of classes
|
|
<< " NULL,\n" // End of classmap
|
|
<< "};\n"
|
|
<< "} // namespace HPHP\n";
|
|
}
|
|
|
|
void print_usage(const char* program_name) {
|
|
std::cout << "Usage:\n\n"
|
|
<< " " << program_name << "\n"
|
|
<< " --system\n"
|
|
<< " <class map output file>\n"
|
|
<< " <constants output file>\n"
|
|
<< " <*.idl.json>...\n\n"
|
|
<< " " << program_name << "\n"
|
|
<< " <class map name>\n"
|
|
<< " <class map output file>\n"
|
|
<< " <*.idl.json>...\n\n";
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
int main(int argc, const char* argv[]) {
|
|
if (argc < 3) {
|
|
print_usage(argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
bool system = !strcmp(argv[1], "--system");
|
|
if (system && argc < 4) {
|
|
print_usage(argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
fbvector<PhpFunc> funcs;
|
|
fbvector<PhpClass> classes;
|
|
fbvector<PhpConst> consts;
|
|
fbvector<PhpExtension> exts;
|
|
|
|
for (int i = (system ? 4 : 3); i < argc; ++i) {
|
|
try {
|
|
parseIDL(argv[i], funcs, classes, consts, exts);
|
|
} catch (const std::exception& exc) {
|
|
std::cerr << argv[i] << ": " << exc.what() << "\n";
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
const char* path = argv[2];
|
|
const char* name = (system ? "g_class_map" : argv[1]);
|
|
outputClassMap(path, name, classes, funcs, consts, exts);
|
|
if (system) {
|
|
path = argv[3];
|
|
outputConstants(path, consts);
|
|
}
|
|
|
|
return 0;
|
|
}
|