From fa6f3f7e12a417452e630be680c3c3df1d9e4f53 Mon Sep 17 00:00:00 2001 From: Jordan DeLong Date: Sat, 25 May 2013 11:07:16 -0700 Subject: [PATCH] Implement most of class_alias Throws the aliased class into a target cache slot for the new name. Handles errors when you try to re-alias a class, but doesn't restrict a few other cases zend does: - If you implement an interface twice, zend complains (one of the alias tests checks this). I tried turning it on, but we violate it in systemlib currently so I left it off. - class_alias_014.php does some namespace stuff I don't quite grok. (@ptarjan let me know what to do if it's easy). - inter_007.php uses class_alias, but is testing a warning that happens even with out it. (We don't raise this warning.) - zend raises a warning if you try to class_alias a non-user-defined class; I left this out. --- hphp/compiler/analysis/analysis_result.cpp | 50 +++++++++++++++++++ hphp/compiler/analysis/analysis_result.h | 17 +++++++ hphp/compiler/analysis/file_scope.h | 15 ++++++ .../expression/simple_function_call.cpp | 26 ++++++++++ .../expression/simple_function_call.h | 1 + hphp/idl/class.idl.json | 31 +++++++++++- hphp/runtime/ext/ext_class.cpp | 14 ++++++ hphp/runtime/ext/ext_class.h | 1 + hphp/runtime/vm/unit.cpp | 20 ++++++++ hphp/runtime/vm/unit.h | 1 + hphp/system/class_map.cpp | 8 +++ hphp/test/slow/warnings.opts | 1 + hphp/test/slow/zend/class_alias1.php | 4 ++ hphp/test/slow/zend/class_alias1.php.expectf | 1 + hphp/test/slow/zend/class_alias1.php.opts | 1 + hphp/test/slow/zend/class_alias2.php | 18 +++++++ hphp/test/slow/zend/class_alias2.php.expectf | 2 + hphp/test/slow/zend/class_alias2.php.opts | 1 + .../{good => bad}/zend/class_alias_009.php | 0 .../zend/class_alias_009.php.expectf | 0 .../zend/{good => bad}/zend/inter_007.php | 0 .../{good => bad}/zend/inter_007.php.expectf | 0 .../test/zend/{bad => good}/zend/bug54624.php | 0 .../{bad => good}/zend/bug54624.php.expectf | 0 .../{bad => good}/zend/class_alias_001.php | 0 .../zend/class_alias_001.php.expectf | 0 .../{bad => good}/zend/class_alias_002.php | 0 .../zend/class_alias_002.php.expectf | 0 .../{bad => good}/zend/class_alias_003.php | 0 .../zend/class_alias_003.php.expectf | 0 .../{bad => good}/zend/class_alias_004.php | 0 .../zend/class_alias_004.php.expectf | 0 .../{bad => good}/zend/class_alias_005.php | 0 .../zend/class_alias_005.php.expectf | 0 .../{bad => good}/zend/class_alias_007.php | 0 .../zend/class_alias_007.php.expectf | 0 .../{bad => good}/zend/class_alias_010.php | 0 .../zend/class_alias_010.php.expectf | 0 .../{bad => good}/zend/class_alias_011.php | 0 .../zend/class_alias_011.php.expectf | 0 .../{bad => good}/zend/class_alias_012.php | 0 .../zend/class_alias_012.php.expectf | 0 .../{bad => good}/zend/class_alias_013.php | 0 .../zend/class_alias_013.php.expectf | 0 .../{bad => good}/zend/class_alias_015.php | 0 .../zend/class_alias_015.php.expectf | 0 .../{bad => good}/zend/class_alias_016.php | 0 .../zend/class_alias_016.php.expectf | 0 .../{bad => good}/zend/class_alias_017.php | 0 .../zend/class_alias_017.php.expectf | 0 .../{bad => good}/zend/class_alias_018.php | 0 .../zend/class_alias_018.php.expectf | 0 .../{bad => good}/zend/class_alias_019.php | 0 .../zend/class_alias_019.php.expectf | 0 .../{bad => good}/zend/class_alias_020.php | 0 .../zend/class_alias_020.php.expectf | 0 .../{bad => good}/zend/class_alias_021.php | 0 .../zend/class_alias_021.php.expectf | 0 .../{bad => good}/zend/class_exists_001.php | 0 .../zend/class_exists_001.php.expectf | 0 60 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 hphp/test/slow/warnings.opts create mode 100644 hphp/test/slow/zend/class_alias1.php create mode 100644 hphp/test/slow/zend/class_alias1.php.expectf create mode 120000 hphp/test/slow/zend/class_alias1.php.opts create mode 100644 hphp/test/slow/zend/class_alias2.php create mode 100644 hphp/test/slow/zend/class_alias2.php.expectf create mode 120000 hphp/test/slow/zend/class_alias2.php.opts rename hphp/test/zend/{good => bad}/zend/class_alias_009.php (100%) rename hphp/test/zend/{good => bad}/zend/class_alias_009.php.expectf (100%) rename hphp/test/zend/{good => bad}/zend/inter_007.php (100%) rename hphp/test/zend/{good => bad}/zend/inter_007.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/bug54624.php (100%) rename hphp/test/zend/{bad => good}/zend/bug54624.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_001.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_001.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_002.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_002.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_003.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_003.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_004.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_004.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_005.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_005.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_007.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_007.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_010.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_010.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_011.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_011.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_012.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_012.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_013.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_013.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_015.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_015.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_016.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_016.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_017.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_017.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_018.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_018.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_019.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_019.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_020.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_020.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_021.php (100%) rename hphp/test/zend/{bad => good}/zend/class_alias_021.php.expectf (100%) rename hphp/test/zend/{bad => good}/zend/class_exists_001.php (100%) rename hphp/test/zend/{bad => good}/zend/class_exists_001.php.expectf (100%) diff --git a/hphp/compiler/analysis/analysis_result.cpp b/hphp/compiler/analysis/analysis_result.cpp index 2682df843..d657d119e 100644 --- a/hphp/compiler/analysis/analysis_result.cpp +++ b/hphp/compiler/analysis/analysis_result.cpp @@ -408,7 +408,9 @@ static bool by_source(const BlockScopePtr &b1, const BlockScopePtr &b2) { void AnalysisResult::canonicalizeSymbolOrder() { getConstants()->canonicalizeSymbolOrder(); getVariables()->canonicalizeSymbolOrder(); +} +void AnalysisResult::markRedeclaringClasses() { AnalysisResultPtr ar = shared_from_this(); for (StringToClassScopePtrVecMap::iterator iter = m_classDecs.begin(); iter != m_classDecs.end(); ++iter) { @@ -420,6 +422,43 @@ void AnalysisResult::canonicalizeSymbolOrder() { } } } + + /* + * In WholeProgram mode, during parse time we collected all + * class_alias calls so we can mark the targets of such calls + * redeclaring if necessary. + * + * Two cases here that definitely require this: + * + * - If an alias name has the same name as another class, we need + * to mark *that* class as redeclaring, since it may mean + * different things in different requests now. + * + * - If an alias name can refer to more than one class, each of + * those classes must be marked redeclaring. + * + * In the simple case of a unique alias name and a unique target + * name, we might be able to get away with manipulating the target + * classes' volatility. + * + * Rather than work through the various cases here, though, we've + * just decided to just play it safe and mark all the names involved + * as redeclaring for now. + */ + for (auto& kv : m_classAliases) { + auto markRedeclaring = [&] (const std::string& name) { + auto it = m_classDecs.find(name); + if (it != m_classDecs.end()) { + auto& classes = it->second; + for (unsigned int i = 0; i < classes.size(); ++i) { + classes[i]->setRedeclaring(ar, i); + } + } + }; + + markRedeclaring(Util::toLower(kv.first)); + markRedeclaring(Util::toLower(kv.second)); + } } /////////////////////////////////////////////////////////////////////////////// @@ -606,6 +645,9 @@ void AnalysisResult::collectFunctionsAndClasses(FileScopePtr fs) { ClassScopePtrVec &clsVec = m_classDecs[iter->first]; clsVec.insert(clsVec.end(), iter->second.begin(), iter->second.end()); } + + m_classAliases.insert(fs->getClassAliases().begin(), + fs->getClassAliases().end()); } static bool by_filename(const FileScopePtr &f1, const FileScopePtr &f2) { @@ -631,6 +673,8 @@ void AnalysisResult::analyzeProgram(bool system /* = false */) { // Keep generated code identical without randomness canonicalizeSymbolOrder(); + markRedeclaringClasses(); + // Analyze some special cases for (set::const_iterator it = Option::VolatileClasses.begin(); it != Option::VolatileClasses.end(); ++it) { @@ -800,8 +844,14 @@ void AnalysisResult::analyzeProgramFinal() { for (uint i = 0; i < m_fileScopes.size(); i++) { m_fileScopes[i]->analyzeProgram(ar); } + // Keep generated code identical without randomness canonicalizeSymbolOrder(); + + // XXX: this is only here because canonicalizeSymbolOrder used to do + // it---is it necessary to repeat at this phase? (Probably not ...) + markRedeclaringClasses(); + setPhase(AnalysisResult::CodeGen); } diff --git a/hphp/compiler/analysis/analysis_result.h b/hphp/compiler/analysis/analysis_result.h index e5b556a4f..50bf45691 100644 --- a/hphp/compiler/analysis/analysis_result.h +++ b/hphp/compiler/analysis/analysis_result.h @@ -300,6 +300,7 @@ public: void addNamedScalarVarArray(const std::string &s); StringToClassScopePtrVecMap getExtensionClasses(); void addInteger(int64_t n); + private: Package *m_package; bool m_parseOnDemand; @@ -320,6 +321,10 @@ private: StringToFileScopePtrMap m_constDecs; std::set m_constRedeclared; + // Map names of class aliases to the class names they will alias. + // Only in WholeProgram mode. See markRedeclaringClasses. + std::multimap m_classAliases; + bool m_classForcedVariants[2]; StatementPtrVec m_stmts; @@ -353,6 +358,11 @@ private: */ bool inParseOnDemandDirs(const std::string &filename) const; + /* + * Find the names of all functions and classes in the program; mark + * functions with duplicate names as redeclaring, but duplicate + * classes aren't yet marked. See markRedeclaringClasses. + */ void collectFunctionsAndClasses(FileScopePtr fs); /** @@ -361,6 +371,13 @@ private: */ void canonicalizeSymbolOrder(); + /* + * After all the class names have been collected and symbol order is + * canonicalized, this passes through and marks duplicate class + * names as redeclaring. + */ + void markRedeclaringClasses(); + /** * Checks circular class derivations that can cause stack overflows for * subsequent analysis. Also checks to make sure no two redundant parents. diff --git a/hphp/compiler/analysis/file_scope.h b/hphp/compiler/analysis/file_scope.h index 76e75a002..add19363c 100644 --- a/hphp/compiler/analysis/file_scope.h +++ b/hphp/compiler/analysis/file_scope.h @@ -17,6 +17,8 @@ #ifndef incl_HPHP_FILE_SCOPE_H_ #define incl_HPHP_FILE_SCOPE_H_ +#include + #include "hphp/compiler/analysis/block_scope.h" #include "hphp/compiler/analysis/function_container.h" #include "hphp/compiler/analysis/code_error.h" @@ -135,6 +137,14 @@ public: void addConstantDependency(AnalysisResultPtr ar, const std::string &decname); + void addClassAlias(const std::string& target, const std::string& alias) { + m_classAliasMap.insert(std::make_pair(target, alias)); + } + + std::multimap const& getClassAliases() const { + return m_classAliasMap; + } + /** * Called only by World */ @@ -180,6 +190,7 @@ public: return boost::static_pointer_cast (BlockScope::shared_from_this()); } + private: int m_size; MD5 m_md5; @@ -202,6 +213,10 @@ private: BlockScopeSet m_providedDefs; std::set m_redecBases; + // Map from class alias names to the class they are aliased to. + // This is only needed in WholeProgram mode. + std::multimap m_classAliasMap; + FunctionScopePtr createPseudoMain(AnalysisResultConstPtr ar); void setFileLevel(StatementListPtr stmt); }; diff --git a/hphp/compiler/expression/simple_function_call.cpp b/hphp/compiler/expression/simple_function_call.cpp index 0f5e042af..aa3cab48e 100644 --- a/hphp/compiler/expression/simple_function_call.cpp +++ b/hphp/compiler/expression/simple_function_call.cpp @@ -53,6 +53,7 @@ void SimpleFunctionCall::InitFunctionTypeMap() { if (FunctionTypeMap.empty()) { FunctionTypeMap["define"] = FunType::Define; FunctionTypeMap["create_function"] = FunType::Create; + FunctionTypeMap["class_alias"] = FunType::ClassAlias; FunctionTypeMap["func_get_arg"] = FunType::VariableArgument; FunctionTypeMap["func_get_args"] = FunType::VariableArgument; @@ -183,6 +184,7 @@ void SimpleFunctionCall::mungeIfSpecialFunction(AnalysisResultConstPtr ar, } } break; + case FunType::Create: if (Option::ParseTimeOpts && m_params->getCount() == 2 && @@ -197,6 +199,25 @@ void SimpleFunctionCall::mungeIfSpecialFunction(AnalysisResultConstPtr ar, ar->appendExtraCode(fs->getName(), code); } break; + + // The class_alias builtin can create new names for other classes; + // we need to mark some of these classes redeclaring to avoid + // making incorrect assumptions during WholeProgram mode. See + // AnalysisResult::collectFunctionsAndClasses. + case FunType::ClassAlias: + if ((m_params->getCount() == 2 || m_params->getCount() == 3) && + Option::WholeProgram) { + if (!(*m_params)[0]->isLiteralString() || + !(*m_params)[1]->isLiteralString()) { + parseTimeFatal(Compiler::NoError, + "class_alias with non-literal parameters is not allowed when " + "WholeProgram optimizations are turned on"); + } + fs->addClassAlias((*m_params)[0]->getLiteralString(), + (*m_params)[1]->getLiteralString()); + } + break; + case FunType::VariableArgument: /* Note: @@ -209,10 +230,12 @@ void SimpleFunctionCall::mungeIfSpecialFunction(AnalysisResultConstPtr ar, */ fs->setAttribute(FileScope::VariableArgument); break; + case FunType::Extract: fs->setAttribute(FileScope::ContainsLDynamicVariable); fs->setAttribute(FileScope::ContainsExtract); break; + case FunType::Compact: { // If all the parameters in the compact() call are statically known, // there is no need to create a variable table. @@ -229,13 +252,16 @@ void SimpleFunctionCall::mungeIfSpecialFunction(AnalysisResultConstPtr ar, fs->setAttribute(FileScope::ContainsCompact); break; } + case FunType::GetDefinedVars: fs->setAttribute(FileScope::ContainsDynamicVariable); fs->setAttribute(FileScope::ContainsGetDefinedVars); fs->setAttribute(FileScope::ContainsCompact); break; + case FunType::Unknown: break; + default: break; } diff --git a/hphp/compiler/expression/simple_function_call.h b/hphp/compiler/expression/simple_function_call.h index 7c76a1227..089059fe9 100644 --- a/hphp/compiler/expression/simple_function_call.h +++ b/hphp/compiler/expression/simple_function_call.h @@ -93,6 +93,7 @@ protected: GetDefinedVars, FBCallUserFuncSafe, ThrowFatal, + ClassAlias, }; static std::map FunctionTypeMap; diff --git a/hphp/idl/class.idl.json b/hphp/idl/class.idl.json index 89e3d3efc..7f3a215b4 100644 --- a/hphp/idl/class.idl.json +++ b/hphp/idl/class.idl.json @@ -42,6 +42,35 @@ "args": [ ] }, + { + "name": "class_alias", + "desc": "Creates an alias named alias based on the defined class original. The aliased class is exactly the same as the original class.", + "flags": [ + "HasDocComment" + ], + "return": { + "type": "Boolean", + "desc": "Returns TRUE on success or FALSE on failure." + }, + "args": [ + { + "name": "original", + "type": "String", + "desc": "The original class." + }, + { + "name": "alias", + "type": "String", + "desc": "The alias name for the class." + }, + { + "name": "autoload", + "type": "Boolean", + "value": "true", + "desc": "Whether do autoload if the original class is not found." + } + ] + }, { "name": "class_exists", "desc": "This function checks whether or not the given class has been defined.", @@ -383,4 +412,4 @@ ], "classes": [ ] -} \ No newline at end of file +} diff --git a/hphp/runtime/ext/ext_class.cpp b/hphp/runtime/ext/ext_class.cpp index aa696728f..c6edb7a76 100644 --- a/hphp/runtime/ext/ext_class.cpp +++ b/hphp/runtime/ext/ext_class.cpp @@ -19,6 +19,7 @@ #include "hphp/runtime/base/class_info.h" #include "hphp/runtime/vm/jit/translator.h" #include "hphp/runtime/vm/jit/translator-inline.h" +#include "hphp/runtime/vm/unit.h" #include "hphp/util/util.h" namespace HPHP { @@ -66,6 +67,19 @@ Array f_get_declared_traits() { return ClassInfo::GetTraits(); } +bool f_class_alias(CStrRef original, + CStrRef alias, + bool autoload /* = true */) { + auto const origClass = + autoload ? Unit::loadClass(original.get()) + : Unit::lookupClass(original.get()); + if (!origClass) { + raise_warning("Class %s not found", original->data()); + return false; + } + return Unit::aliasClass(origClass, alias.get()); +} + bool f_class_exists(CStrRef class_name, bool autoload /* = true */) { return Unit::classExists(class_name.get(), autoload, AttrNone); } diff --git a/hphp/runtime/ext/ext_class.h b/hphp/runtime/ext/ext_class.h index 0ee902748..6f3656836 100644 --- a/hphp/runtime/ext/ext_class.h +++ b/hphp/runtime/ext/ext_class.h @@ -26,6 +26,7 @@ namespace HPHP { Array f_get_declared_classes(); Array f_get_declared_interfaces(); Array f_get_declared_traits(); +bool f_class_alias(CStrRef original, CStrRef alias, bool autoload = true); bool f_class_exists(CStrRef class_name, bool autoload = true); bool f_interface_exists(CStrRef interface_name, bool autoload = true); bool f_trait_exists(CStrRef trait_name, bool autoload = true); diff --git a/hphp/runtime/vm/unit.cpp b/hphp/runtime/vm/unit.cpp index 4736f9609..38d1ed764 100644 --- a/hphp/runtime/vm/unit.cpp +++ b/hphp/runtime/vm/unit.cpp @@ -628,6 +628,26 @@ Class* Unit::defClass(const PreClass* preClass, } } +bool Unit::aliasClass(Class* original, const StringData* alias) { + auto const aliasNe = Unit::GetNamedEntity(alias); + + if (!aliasNe->m_cachedClassOffset) { + Lock lk(s_classesMutex); + if (!aliasNe->m_cachedClassOffset) { + aliasNe->m_cachedClassOffset = + Transl::TargetCache::allocKnownClass(alias); + } + } + + auto const aliasClass = aliasNe->getCachedClass(); + if (aliasClass) { + raise_warning("Cannot redeclare class %s", alias->data()); + return false; + } + aliasNe->setCachedClass(original); + return true; +} + void Unit::defTypedef(Id id) { assert(id < m_typedefs.size()); auto thisType = &m_typedefs[id]; diff --git a/hphp/runtime/vm/unit.h b/hphp/runtime/vm/unit.h index 2cbd119dc..2e3ebb9b8 100644 --- a/hphp/runtime/vm/unit.h +++ b/hphp/runtime/vm/unit.h @@ -470,6 +470,7 @@ struct Unit { static Class* defClass(const HPHP::PreClass* preClass, bool failIsFatal = true); + static bool aliasClass(Class* original, const StringData* alias); void defTypedef(Id id); static TypedValue* lookupCns(const StringData* cnsName); diff --git a/hphp/system/class_map.cpp b/hphp/system/class_map.cpp index 5e175495c..64e55b7ca 100644 --- a/hphp/system/class_map.cpp +++ b/hphp/system/class_map.cpp @@ -12198,6 +12198,14 @@ const char *g_class_map[] = { (const char *)0x20 /* KindOfArray */, NULL, NULL, NULL, + (const char *)0x10006040, "class_alias", "", (const char*)0, (const char*)0, + "/**\n * ( excerpt from http://php.net/manual/en/function.class-alias.php )\n *\n * Creates an alias named alias based on the defined class original. The\n * aliased class is exactly the same as the original class.\n *\n * @original string The original class.\n * @alias string The alias name for the class.\n * @autoload bool Whether do autoload if the original class is not\n * found.\n *\n * @return bool Returns TRUE on success or FALSE on failure.\n */", + (const char *)0x9 /* KindOfBoolean */, (const char *)0x2000, "original", "", (const char *)0x14 /* KindOfString */, "", (const char *)0, "", (const char *)0, NULL, + (const char *)0x2000, "alias", "", (const char *)0x14 /* KindOfString */, "", (const char *)0, "", (const char *)0, NULL, + (const char *)0x2000, "autoload", "", (const char *)0x9 /* KindOfBoolean */, "b:1;", (const char *)4, "true", (const char *)4, NULL, + NULL, + NULL, + NULL, (const char *)0x10006040, "class_exists", "", (const char*)0, (const char*)0, "/**\n * ( excerpt from http://php.net/manual/en/function.class-exists.php )\n *\n * This function checks whether or not the given class has been defined.\n *\n * @class_name string The class name. The name is matched in a\n * case-insensitive manner.\n * @autoload bool Whether or not to call __autoload by default.\n *\n * @return bool Returns TRUE if class_name is a defined class, FALSE\n * otherwise.\n */", (const char *)0x9 /* KindOfBoolean */, (const char *)0x2000, "class_name", "", (const char *)0x14 /* KindOfString */, "", (const char *)0, "", (const char *)0, NULL, diff --git a/hphp/test/slow/warnings.opts b/hphp/test/slow/warnings.opts new file mode 100644 index 000000000..a88edb39d --- /dev/null +++ b/hphp/test/slow/warnings.opts @@ -0,0 +1 @@ +-vErrorHandling.NoticeFrequency=1 -vErrorHandling.WarningFrequency=1 -vLog.RuntimeErrorReportingLevel=10 diff --git a/hphp/test/slow/zend/class_alias1.php b/hphp/test/slow/zend/class_alias1.php new file mode 100644 index 000000000..0e0ea128d --- /dev/null +++ b/hphp/test/slow/zend/class_alias1.php @@ -0,0 +1,4 @@ +