Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
  • 9 commits
  • 24 files changed
  • 0 commit comments
  • 5 contributors
Commits on Sep 28, 2012
@jdelong jdelong Use a linked list instead of a hashtable for the Sweepable set b94eb31
@scottmac scottmac Implement libxml_disable_entity_loader()
Parsing XML documents from external resources is dangerous at the moment
since they can pass us:

<!DOCTYPE scan [<!ENTITY test SYSTEM
"file:///etc/passwd">]><scan><foo>&test;</foo></scan>

to read /etc/passwd

bit of an issue is that this is an XML global, so leave it loading by
default and a function to disable it. Will test with adding it to our
standard www init stack and see if we can make it off by default.

Test Plan:
Ran unit tests and:

<?php
$string = '<!DOCTYPE scan [<!ENTITY test SYSTEM
"file:///etc/passwd">]><scan><foo>&test;</foo></scan>';
libxml_disable_entity_loader(true);
$a = simplexml_load_string($string);
var_dump($a->foo);
31afdeb
@jdelong jdelong Use smart_malloc for ExtraArgs (and use one fewer allocation)
ExtraArgs doesn't actually need to know its size, and also
doesn't need to have an additional pointer hop.  This seemed *maybe*
like a small win in perflab, but changing to smart_malloc on top of
this looks like it actually cuts instructions and even CPU noticably.
6ba41f8
@markw65 markw65 Make call_user_func_array less strict
If the called function expects a reference,
but doesnt get one, zend raises a warning,
and returns false, rather than executing the
body of the function.

A previous diff emulated that behavior, but we have
a lot of tests that rely on the function being
called anyway. This version still raises the
warning (so we'll hopefully get the bugs fixed)
but continues on and executes the function.
a4c5cf6
@sgolemon sgolemon Return 0 (false) instead of throwing an exception for get_magic_quote…
…s_runtime()

There's no point fataling on a non-fatal condition.
2e10f2e
kma Fix the build. 4fa4164
kma Huge pages for datablocks.
We seem to benefit from using huge pages to map the TC and
targetcache. This diff checks that the kernel version is greater than or
equal to 3.2.28-72-fbk12, and if so asks the kernel for large pages.
4ea9123
@markw65 markw65 Fix some static analysis issues
A recent correctness fix disabled a lot of uninit detection.
While investigating the correct fix, I noticed that many
type predictions for the base of a Vector instruction seemed
to be ineffective (we still generated a guard), and that
"lvalue" vector instructions never generated the DataType
meta-data.
ac85dff
kma Use our conditional jmp machinery to make IterInit nice.
We were emitting slightly bad code for IterInit. The same trick
we do for other conditional branches can improve it.
34bc484
View
38 src/compiler/analysis/alias_manager.cpp
@@ -3305,7 +3305,7 @@ class ConstructTagger : public DataFlowWalker {
DataFlowWalker(g), m_gidMap(gidMap) {}
void walk(MethodStatementPtr m) {
- DataFlowWalker::walk();
+ DataFlowWalker::walk(*this);
ControlBlock *b = m_graph.getDfBlock(1);
std::map<std::string,int>::iterator it = m_gidMap.find("v:this");
if (m->getOrigGeneratorFunc()) {
@@ -3344,6 +3344,9 @@ class ConstructTagger : public DataFlowWalker {
void processAccess(ExpressionPtr e) {
int id = e->getCanonID();
if (id) {
+ if (e->isThis() && e->getAssertedType()) {
+ markAvailable(e);
+ }
if (m_block->getBit(DataFlow::Available, id)) {
markAvailable(e);
e->clearAnticipated();
@@ -3414,9 +3417,11 @@ class ConstructTagger : public DataFlowWalker {
}
if (m_block->getBit(DataFlow::Referenced, id)) {
sv->setRefCounted();
+ sv->setKilled();
}
if (m_block->getBit(DataFlow::Inited, id)) {
sv->setInited();
+ sv->setKilled();
}
if (sv->hasAllContext(Expression::UnsetContext|
Expression::LValue)) {
@@ -3446,8 +3451,21 @@ class ConstructTagger : public DataFlowWalker {
if (mod || ref) {
m_block->setBit(DataFlow::Referenced, id, true);
m_block->setBit(DataFlow::Inited, id, true);
- m_block->setBit(DataFlow::Killed, id, true);
- return;
+ bool kill = true;
+
+ if (!mod) {
+ if (TypePtr act = sv->getActualType()) {
+ if (sv->hasContext(Expression::ObjectContext)) {
+ if (act->is(Type::KindOfObject)) kill = false;
+ } else if (sv->hasContext(Expression::AccessContext)) {
+ if (act->is(Type::KindOfArray)) kill = false;
+ }
+ }
+ }
+ if (kill) {
+ m_block->setBit(DataFlow::Killed, id, true);
+ return;
+ }
}
if (!sv->couldBeAliased()) {
@@ -3478,7 +3496,7 @@ class ConstructTagger : public DataFlowWalker {
}
}
- int after(ConstructPtr cp) {
+ int after(ConstructRawPtr cp) {
if (ReturnStatementPtr rs = dynamic_pointer_cast<ReturnStatement>(cp)) {
int id = m_gidMap["v:this"];
if (id && m_block->getBit(DataFlow::Available, id)) {
@@ -3498,11 +3516,16 @@ class ConstructMarker : public DataFlowWalker {
m_top(g->getMethod()->getStmts()) {}
void walk() {
- DataFlowWalker::walk();
+ DataFlowWalker::walk(*this);
}
void processAccess(ExpressionPtr e) {
- if (int id = e->getCanonID()) {
+ int id = e->getCanonID();
+ if (!id && e->is(Expression::KindOfSimpleVariable) &&
+ (e->isAnticipated() || !e->isKilled())) {
+ id = m_gidMap["v:" + static_pointer_cast<SimpleVariable>(e)->getName()];
+ }
+ if (id) {
if (!m_block->getDfn()) return;
if (e->isAnticipated() && m_block->getBit(DataFlow::AvailIn, id)) {
markAvailable(e);
@@ -3528,8 +3551,7 @@ class ConstructMarker : public DataFlowWalker {
Statement::KindOfReturnStatement)) {
if (m_block->getDfn()) {
int id = m_gidMap["v:this"];
- if (id && m_block->getBit(cp == m_top ?
- DataFlow::AvailOut : DataFlow::AvailIn, id)) {
+ if (id && m_block->getBit(DataFlow::AvailOut, id)) {
cp->setGuarded();
}
}
View
3 src/compiler/analysis/data_flow.h
@@ -93,7 +93,8 @@ class DataFlowWalker : public ControlFlowGraphWalker {
public:
DataFlowWalker(ControlFlowGraph *g) : ControlFlowGraphWalker(g) {}
- void walk() { ControlFlowGraphWalker::walk(*this); }
+ template<class T>
+ void walk(T &t) { ControlFlowGraphWalker::walk(t); }
int after(ConstructRawPtr cp);
int afterEach(ConstructRawPtr cur, int i, ConstructRawPtr kid);
View
1 src/compiler/analysis/dictionary.h
@@ -61,6 +61,7 @@ class AttributeTagger : public DataFlowWalker {
AttributeTagger(ControlFlowGraph *g, Dict &d) :
DataFlowWalker(g), m_dict(d) {}
+ void walk() { DataFlowWalker::walk(*this); }
void processAccess(ExpressionPtr e) {
m_dict.updateAccess(e);
}
View
38 src/compiler/analysis/emitter.cpp
@@ -973,12 +973,18 @@ void MetaInfoBuilder::add(int pos, Unit::MetaInfo::Kind kind,
if (arg > 127) return;
if (mVector) arg |= Unit::MetaInfo::VectorArg;
Vec& info = m_metaMap[pos];
+ int i = info.size();
if (kind == Unit::MetaInfo::NopOut) {
info.clear();
- } else if (info.size() == 1 && info[0].m_kind == Unit::MetaInfo::NopOut) {
+ } else if (i == 1 && info[0].m_kind == Unit::MetaInfo::NopOut) {
return;
+ } else if (kind == Unit::MetaInfo::DataType) {
+ // Put DataType first, because if applyInputMetaData saw Class
+ // first, it would call recordRead which mark the input as
+ // needing a guard before we saw the DataType
+ i = 0;
}
- info.push_back(Unit::MetaInfo(kind, arg, data));
+ info.insert(info.begin() + i, Unit::MetaInfo(kind, arg, data));
}
void MetaInfoBuilder::setForUnit(UnitEmitter& target) const {
@@ -1670,6 +1676,15 @@ static StringData* getClassName(ExpressionPtr e) {
ClassScopeRawPtr cls;
if (e->isThis()) {
cls = e->getOriginalClass();
+ if (TypePtr t = e->getAssertedType()) {
+ if (t->isSpecificObject()) {
+ AnalysisResultConstPtr ar = e->getScope()->getContainingProgram();
+ ClassScopeRawPtr c2 = t->getClass(ar, e->getScope());
+ if (c2 && c2->derivesFrom(ar, cls->getName(), true, false)) {
+ cls = c2;
+ }
+ }
+ }
} else if (TypePtr t = e->getActualType()) {
if (t->isSpecificObject()) {
cls = t->getClass(e->getScope()->getContainingProgram(), e->getScope());
@@ -3097,7 +3112,7 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
if (clsName) {
Id id = m_ue.mergeLitstr(clsName);
m_metaInfo.add(fpiStart, Unit::MetaInfo::Class, false,
- om->getName().empty() ? 1 : 0, id);
+ om->getName().empty() ? 1 : 0, id);
}
{
FPIRegionRecorder fpi(this, m_ue, m_evalStack, fpiStart);
@@ -3215,7 +3230,7 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
if (sv->hasContext(Expression::ObjectContext)) {
if (sv->isGuarded()) {
m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::GuardedThis,
- false, 0, 0);
+ false, 0, 0);
}
e.This();
} else {
@@ -3508,16 +3523,16 @@ void EmitterVisitor::buildVectorImm(std::vector<uchar>& vectorImm,
char sym = m_evalStack.get(iFirst);
char symFlavor = StackSym::GetSymFlavor(sym);
char marker = StackSym::GetMarker(sym);
+ DataType dt = m_evalStack.getKnownType(iFirst);
+ if (dt != KindOfUnknown) {
+ m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::DataType, true, 0, dt);
+ }
if (m_evalStack.isCls(iFirst)) {
const StringData* cls = m_evalStack.getName(iFirst);
ASSERT(cls);
Id id = m_ue.mergeLitstr(cls);
m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::Class, true, 0, id);
}
- DataType dt = m_evalStack.getKnownType(iFirst);
- if (dt != KindOfUnknown) {
- m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::DataType, true, 0, dt);
- }
switch (marker) {
case StackSym::N: {
if (symFlavor == StackSym::C) {
@@ -4851,7 +4866,7 @@ void EmitterVisitor::emitPostponedMeths() {
if ((p.m_meth->getStmts() && p.m_meth->getStmts()->isGuarded()) ||
(fe->isClosureBody() && p.m_closureUseVars->size())) {
m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::GuardedThis,
- false, 0, 0);
+ false, 0, 0);
}
e.RetC();
} // -- Method emission ends --
@@ -6132,10 +6147,7 @@ static Unit* emitHHBCUnit(AnalysisResultPtr ar, FileScopePtr fsp,
fev.emitMakeUnitFatal(emitter, ex.getMessage());
}
- if (RuntimeOption::EvalPeephole) {
- // Run the peephole optimizer.
- Peephole peephole(*ue);
- }
+ Peephole peephole(*ue);
if (commit) {
HPHP::VM::Repo::get().commitUnit(ue, unitOrigin);
View
19 src/runtime/base/execution_context.h
@@ -662,15 +662,20 @@ OPCODES
private:
void enterVMWork(VM::ActRec* enterFnAr);
void enterVM(TypedValue* retval,
- HPHP::VM::ActRec* ar,
- TypedValue* extraArgs);
+ VM::ActRec* ar,
+ VM::ExtraArgs* extraArgs);
+ void reenterVM(TypedValue* retval,
+ VM::ActRec* ar,
+ VM::ExtraArgs* extraArgs,
+ TypedValue* savedSP);
void doFPushCuf(VM::PC& pc, bool forward, bool safe);
void unwindBuiltinFrame();
template <bool reenter, bool handle_throw>
- bool prepareFuncEntry(VM::ActRec* ar, VM::PC& pc, TypedValue* extraArgs);
+ bool prepareFuncEntry(VM::ActRec* ar,
+ VM::PC& pc,
+ VM::ExtraArgs* extraArgs);
bool prepareArrayArgs(VM::ActRec* ar, ArrayData* args,
- TypedValue*& extraArgs);
- void cleanupParamsAndActRec(VM::ActRec* ar, TypedValue* extraArgs);
+ VM::ExtraArgs*& extraArgs);
void recordCodeCoverage(VM::PC pc);
int m_coverPrevLine;
HPHP::VM::Unit* m_coverPrevUnit;
@@ -679,10 +684,6 @@ OPCODES
void resetCoverageCounters();
void shuffleMagicArgs(HPHP::VM::ActRec* ar);
void syncGdbState();
- void reenterVM(TypedValue* retval,
- HPHP::VM::ActRec* ar,
- TypedValue* extraArgs,
- TypedValue* savedSP);
void invokeFunc(TypedValue* retval,
const HPHP::VM::Func* f,
CArrRef params,
View
62 src/runtime/base/memory/sweepable.cpp
@@ -21,44 +21,58 @@
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
-IMPLEMENT_THREAD_LOCAL_NO_CHECK(Sweepable::SweepData, Sweepable::s_sweep_data);
-
-void Sweepable::GetSweepData() {
- s_sweep_data.getCheck();
-}
+static __thread Sweepable* t_sweepList = 0;
void Sweepable::SweepAll() {
- s_sweep_data->sweeping = true;
- SweepableSet &sweepables = s_sweep_data->sweepables;
- SweepableSet persistentObjects;
- for (SweepableSet::iterator iter = sweepables.begin();
- iter != sweepables.end(); ++iter) {
- Sweepable *obj = *iter;
- if (obj->m_persistentCount == 0) {
- obj->sweep();
+ Sweepable* persistList = 0;
+ while (t_sweepList) {
+ Sweepable* s = t_sweepList;
+ s->unregister();
+
+ if (s->m_persistentCount == 0) {
+ s->sweep();
} else {
- persistentObjects.insert(obj);
+ if (persistList) {
+ ASSERT(persistList->m_prevSweepable = &persistList);
+ persistList->m_prevSweepable = &s->m_nextSweepable;
+ }
+ s->m_nextSweepable = persistList;
+ persistList = s;
+ s->m_prevSweepable = &persistList;
}
}
- sweepables.clear();
- if (!persistentObjects.empty()) {
- sweepables = persistentObjects;
+ t_sweepList = persistList;
+ if (persistList) {
+ ASSERT(persistList->m_prevSweepable == &persistList);
+ persistList->m_prevSweepable = &t_sweepList;
}
- s_sweep_data->sweeping = false;
}
-Sweepable::Sweepable() : m_persistentCount(0) {
- s_sweep_data->sweepables.insert(this);
+Sweepable::Sweepable()
+ : m_nextSweepable(t_sweepList)
+ , m_prevSweepable(&t_sweepList)
+ , m_persistentCount(0)
+{
+ if (t_sweepList) {
+ ASSERT(t_sweepList->m_prevSweepable == &t_sweepList);
+ t_sweepList->m_prevSweepable = &m_nextSweepable;
+ }
+ t_sweepList = this;
}
Sweepable::~Sweepable() {
- if (!s_sweep_data->sweeping) {
- s_sweep_data->sweepables.erase(this);
- }
+ unregister();
}
void Sweepable::unregister() {
- s_sweep_data->sweepables.erase(this);
+ if (m_prevSweepable) {
+ if (m_nextSweepable) {
+ m_nextSweepable->m_prevSweepable = m_prevSweepable;
+ }
+ *m_prevSweepable = m_nextSweepable;
+ m_nextSweepable = 0;
+ m_prevSweepable = 0;
+ }
}
///////////////////////////////////////////////////////////////////////////////
View
43 src/runtime/base/memory/sweepable.h
@@ -14,17 +14,19 @@
+----------------------------------------------------------------------+
*/
-#ifndef __HPHP_SWEEPABLE_H__
-#define __HPHP_SWEEPABLE_H__
+#ifndef incl_HPHP_SWEEPABLE_H_
+#define incl_HPHP_SWEEPABLE_H_
#include <util/base.h>
-#include <util/thread_local.h>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
-/**
- * Raw pointers that need to be deleted during garbage collection time.
+/*
+ * Objects that need to do special clean up at the end of the request
+ * may register themselves for this by deriving from Sweepable. After
+ * every request, Sweepable::SweepAll is called so the objects may
+ * clear out request-local allocations that are not smart-allocated.
*/
class Sweepable {
public:
@@ -34,7 +36,11 @@ class Sweepable {
Sweepable();
virtual ~Sweepable();
- virtual void sweep() { delete this;}
+ /*
+ * Default sweep behavior is to delete ourselves. Note that this is
+ * not appropriate for a smart-allocated class.
+ */
+ virtual void sweep() { delete this; }
/*
* Note: "Persistent" here means that the object will stay alive
@@ -42,32 +48,23 @@ class Sweepable {
* the same server thread gets around to handling a new request. If
* you need this you probably should be using it via PersistentObjectStore.
*/
- void incPersistent() { ++m_persistentCount;}
- void decPersistent() { --m_persistentCount;}
+ void incPersistent() { ++m_persistentCount; }
+ void decPersistent() { --m_persistentCount; }
bool isPersistent() { return m_persistentCount > 0; }
- static void GetSweepData() ATTRIBUTE_COLD;
- /**
- * Excluding this from being swept(). This is useful for child Sweepable
- * inside a parent Sweepable, when parent's destructor will delete this
- * object. For example, ZipFile containing PlainFile.
+ /*
+ * Remove this object from the sweepable list, so it won't have
+ * sweep() called at the next SweepAll.
*/
void unregister();
private:
- typedef hphp_hash_set<Sweepable*, pointer_hash<Sweepable> > SweepableSet;
- class SweepData {
- public:
- SweepData() : sweeping(false) {}
- bool sweeping;
- SweepableSet sweepables;
- };
- static DECLARE_THREAD_LOCAL_NO_CHECK(SweepData, s_sweep_data);
-
+ Sweepable* m_nextSweepable;
+ Sweepable** m_prevSweepable; // Pointer to previous next pointer.
int m_persistentCount;
};
///////////////////////////////////////////////////////////////////////////////
}
-#endif /* __HPHP_SWEEPABLE_H__ */
+#endif
View
4 src/runtime/base/runtime_option.cpp
@@ -419,7 +419,7 @@ bool RuntimeOption::EvalDumpBytecode = false;
uint32 RuntimeOption::EvalDumpIR = 0;
bool RuntimeOption::EvalDumpTC = false;
bool RuntimeOption::EvalDumpAst = false;
-bool RuntimeOption::EvalPeephole = true;
+bool RuntimeOption::EvalMapTCHuge = true;
bool RuntimeOption::RecordCodeCoverage = false;
std::string RuntimeOption::CodeCoverageOutputFile;
@@ -1200,7 +1200,7 @@ void RuntimeOption::Load(Hdf &config, StringVec *overwrites /* = NULL */,
EvalDumpIR = eval["DumpIR"].getUInt32(0);
EvalDumpTC = eval["DumpTC"].getBool(false);
EvalDumpAst = eval["DumpAst"].getBool(false);
- EvalPeephole = eval["Peephole"].getBool(true);
+ EvalMapTCHuge = eval["MapTCHuge"].getBool(true);
RecordCodeCoverage = eval["RecordCodeCoverage"].getBool();
if (EvalJit && RecordCodeCoverage) {
throw InvalidArgumentException(
View
2 src/runtime/base/runtime_option.h
@@ -424,7 +424,7 @@ class RuntimeOption {
static uint32 EvalDumpIR;
static bool EvalDumpTC;
static bool EvalDumpAst;
- static bool EvalPeephole;
+ static bool EvalMapTCHuge;
static bool RecordCodeCoverage;
static std::string CodeCoverageOutputFile;
View
1 src/runtime/base/thread_init_fini.cpp
@@ -48,7 +48,6 @@ void init_thread_locals(void *arg /* = NULL */) {
zend_get_rand_data();
get_server_note();
g_persistentObjects.getCheck();
- Sweepable::GetSweepData();
MemoryManager::TlsWrapper::getCheck();
InitAllocatorThreadLocal();
RefData::AllocatorType::getCheck();
View
10 src/runtime/ext/ext_function.cpp
@@ -311,7 +311,7 @@ Variant f_func_get_arg(int arg_num) {
return false;
}
- int numParams = ar->m_func->numParams();
+ const int numParams = ar->m_func->numParams();
if (arg_num < numParams) {
// Formal parameter. Value is on the stack.
@@ -320,10 +320,13 @@ Variant f_func_get_arg(int arg_num) {
return tvAsVariant(loc);
}
+ const int numArgs = ar->numArgs();
+ const int extraArgs = numArgs - numParams;
+
// Not a formal parameter. Value is potentially in the
// ExtraArgs/VarEnv.
- int extraArgNum = arg_num - numParams;
- if (extraArgNum < ar->numExtraArgs()) {
+ const int extraArgNum = arg_num - numParams;
+ if (extraArgNum < extraArgs) {
return tvAsVariant(ar->getExtraArg(extraArgNum));
}
@@ -369,7 +372,6 @@ Array hhvm_get_frame_args(const ActRec* ar) {
--local;
} else {
// This is not a formal parameter, so it's in the ExtraArgs.
- ASSERT(i - numParams < (int)ar->numExtraArgs());
retval->nvAppend(ar->getExtraArg(i - numParams), false);
}
}
View
2 src/runtime/ext/ext_options.cpp
@@ -174,7 +174,7 @@ int64 f_get_magic_quotes_gpc() {
}
int64 f_get_magic_quotes_runtime() {
- throw NotSupportedException(__func__, "not using magic quotes");
+ return 0;
}
Array f_get_required_files() {
View
15 src/runtime/ext/ext_simplexml.cpp
@@ -1140,6 +1140,7 @@ class LibXmlErrors : public RequestEventHandler {
virtual void requestInit() {
m_use_error = false;
m_errors.reset();
+ xmlParserInputBufferCreateFilenameDefault(NULL);
}
virtual void requestShutdown() {
m_use_error = false;
@@ -1243,8 +1244,20 @@ void f_libxml_set_streams_context(CObjRef streams_context) {
throw NotImplementedException(__func__);
}
+static xmlParserInputBufferPtr
+hphp_libxml_input_buffer_noload(const char *URI, xmlCharEncoding enc) {
+ return NULL;
+}
+
bool f_libxml_disable_entity_loader(bool disable /* = true */) {
- throw NotImplementedException(__func__);
+ xmlParserInputBufferCreateFilenameFunc old;
+
+ if (disable) {
+ old = xmlParserInputBufferCreateFilenameDefault(hphp_libxml_input_buffer_noload);
+ } else {
+ old = xmlParserInputBufferCreateFilenameDefault(NULL);
+ }
+ return (old == hphp_libxml_input_buffer_noload);
}
///////////////////////////////////////////////////////////////////////////////
View
165 src/runtime/vm/bytecode.cpp
@@ -16,6 +16,7 @@
#include <iostream>
#include <iomanip>
+#include <algorithm>
#include <boost/format.hpp>
#include <boost/utility/typed_in_place_factory.hpp>
@@ -243,13 +244,11 @@ VarEnv::VarEnv()
}
VarEnv::VarEnv(ActRec* fp, ExtraArgs* eArgs)
- : m_extraArgs(eArgs) // move
+ : m_extraArgs(eArgs)
, m_depth(1)
, m_malloced(false)
, m_cfp(fp)
{
- delete eArgs;
-
const Func* func = fp->m_func;
const Id numNames = func->numNamedLocals();
@@ -492,73 +491,53 @@ Array VarEnv::getDefinedVariables() const {
return ret;
}
-unsigned VarEnv::numExtraArgs() const {
- return m_extraArgs.numExtraArgs();
-}
-
TypedValue* VarEnv::getExtraArg(unsigned argInd) const {
- ASSERT(numExtraArgs());
- return m_extraArgs.getExtraArg(argInd);
+ return m_extraArgs->getExtraArg(argInd);
}
//=============================================================================
-// ExtraArgs.
-ExtraArgs::ExtraArgs()
- : m_extraArgs(NULL)
- , m_numExtraArgs(0)
-{}
+ExtraArgs::ExtraArgs() {}
+ExtraArgs::~ExtraArgs() {}
-ExtraArgs::ExtraArgs(ExtraArgs* o)
- : m_extraArgs(o ? o->m_extraArgs : 0)
- , m_numExtraArgs(o ? o->m_numExtraArgs : 0)
-{
- if (o) {
- o->m_extraArgs = 0;
- o->m_numExtraArgs = 0;
- }
+void* ExtraArgs::allocMem(unsigned nargs) {
+ return smart_malloc(sizeof(TypedValue) * nargs + sizeof(ExtraArgs));
}
-ExtraArgs::~ExtraArgs() {
- if (m_extraArgs != NULL) {
- for (unsigned i = 0; i < m_numExtraArgs; i++) {
- tvRefcountedDecRef(&m_extraArgs[i]);
- }
- free(m_extraArgs);
- if (debug) {
- m_extraArgs = 0;
- m_numExtraArgs = 0;
- }
- }
+ExtraArgs* ExtraArgs::allocateCopy(TypedValue* args, unsigned nargs) {
+ void* mem = allocMem(nargs);
+ ExtraArgs* ea = new (mem) ExtraArgs();
+
+ /*
+ * The stack grows downward, so the args in memory are "backward"; i.e. the
+ * leftmost (in PHP) extra arg is highest in memory.
+ */
+ std::reverse_copy(args, args + nargs, &ea->m_extraArgs[0]);
+ return ea;
}
-void ExtraArgs::setExtraArgs(TypedValue* args, unsigned nargs) {
- ASSERT(!m_extraArgs);
- m_extraArgs = args;
- m_numExtraArgs = nargs;
+ExtraArgs* ExtraArgs::allocateUninit(unsigned nargs) {
+ void* mem = ExtraArgs::allocMem(nargs);
+ return new (mem) ExtraArgs();
}
-void ExtraArgs::copyExtraArgs(TypedValue* args, unsigned nargs) {
- // The ExtraArgs takes over ownership of the args; the original copies are
- // discarded from the stack without adjusting reference counts, thus allowing
- // ExtraArgs to avoid reference count manipulation here.
- ASSERT(!m_extraArgs);
- m_extraArgs = (TypedValue*)malloc(nargs * sizeof(TypedValue));
- m_numExtraArgs = nargs;
+void ExtraArgs::deallocate(ExtraArgs* ea, unsigned nargs) {
+ ASSERT(nargs > 0);
- // The stack grows downward, so the args in memory are "backward"; i.e. the
- // leftmost (in PHP) extra arg is highest in memory. We just copy them in a
- // blob here, and compensate in getExtraArg().
- memcpy(m_extraArgs, args, nargs * sizeof(TypedValue));
+ for (unsigned i = 0; i < nargs; ++i) {
+ tvRefcountedDecRef(ea->m_extraArgs + i);
+ }
+ ea->~ExtraArgs();
+ smart_free(ea);
}
-unsigned ExtraArgs::numExtraArgs() const {
- return m_numExtraArgs;
+void ExtraArgs::deallocate(ActRec* ar) {
+ const int numExtra = ar->numArgs() - ar->m_func->numParams();
+ deallocate(ar->getExtraArgs(), numExtra);
}
TypedValue* ExtraArgs::getExtraArg(unsigned argInd) const {
- ASSERT(argInd < m_numExtraArgs);
- return &m_extraArgs[m_numExtraArgs - argInd - 1];
+ return const_cast<TypedValue*>(&m_extraArgs[argInd]);
}
//=============================================================================
@@ -1655,7 +1634,7 @@ static inline void checkStack(Stack& stk, const Func* f) {
template <bool reenter, bool handle_throw>
bool VMExecutionContext::prepareFuncEntry(ActRec *ar,
PC& pc,
- TypedValue* extraArgs) {
+ ExtraArgs* extraArgs) {
const Func* func = ar->m_func;
if (!reenter) {
// For the reenter case, intercept and magic shuffling are handled
@@ -1712,11 +1691,10 @@ bool VMExecutionContext::prepareFuncEntry(ActRec *ar,
// inheriting a VarEnv
ASSERT(!m_fp->m_varEnv);
// Extra parameters must be moved off the stack.
- m_fp->setExtraArgs(new ExtraArgs());
- int numExtras = nargs - nparams;
- m_fp->getExtraArgs()->copyExtraArgs(
+ const int numExtras = nargs - nparams;
+ m_fp->setExtraArgs(ExtraArgs::allocateCopy(
(TypedValue*)(uintptr_t(m_fp) - nargs * sizeof(TypedValue)),
- numExtras);
+ numExtras));
for (int i = 0; i < numExtras; i++) {
m_stack.discard();
}
@@ -1747,12 +1725,9 @@ bool VMExecutionContext::prepareFuncEntry(ActRec *ar,
// the VarEnv inherited from our caller to the current frame
ar->m_varEnv->attach(ar);
} else if (extraArgs) {
- // Create a new ExtraArgs structure and stash the extra args in
+ // Create an ExtraArgs structure and stash the extra args in
// there.
- int numExtras = ar->numArgs() - ar->m_func->numParams();
- ASSERT(numExtras > 0);
- ar->setExtraArgs(new ExtraArgs());
- ar->getExtraArgs()->setExtraArgs(extraArgs, numExtras);
+ ar->setExtraArgs(extraArgs);
}
}
@@ -1854,7 +1829,7 @@ static int exception_handler() {
void VMExecutionContext::enterVM(TypedValue* retval,
ActRec* ar,
- TypedValue* extraArgs) {
+ ExtraArgs* extraArgs) {
m_firstAR = ar;
ar->m_savedRip = (uintptr_t)tx64->getCallToExit();
@@ -1945,7 +1920,7 @@ void VMExecutionContext::enterVM(TypedValue* retval,
void VMExecutionContext::reenterVM(TypedValue* retval,
ActRec* ar,
- TypedValue* extraArgs,
+ ExtraArgs* extraArgs,
TypedValue* savedSP) {
ar->m_soff = 0;
ar->m_savedRbp = 0;
@@ -2061,7 +2036,7 @@ void VMExecutionContext::invokeFunc(TypedValue* retval,
#endif
HphpArray *arr = dynamic_cast<HphpArray*>(params.get());
- TypedValue* extraArgs = NULL;
+ ExtraArgs* extraArgs = nullptr;
if (isMagicCall) {
// Put the method name into the location of the first parameter. We
// are transferring ownership, so no need to incRef/decRef here.
@@ -2079,10 +2054,10 @@ void VMExecutionContext::invokeFunc(TypedValue* retval,
ASSERT(arr && IsHphpArray(arr));
}
if (arr) {
- int numParams = f->numParams();
- int numExtraArgs = arr->size() - numParams;
+ const int numParams = f->numParams();
+ const int numExtraArgs = arr->size() - numParams;
if (numExtraArgs > 0 && (f->attrs() & AttrMayUseVV)) {
- extraArgs = (TypedValue*)malloc(sizeof(TypedValue) * numExtraArgs);
+ extraArgs = ExtraArgs::allocateUninit(numExtraArgs);
}
int paramId = 0;
for (ssize_t i = arr->iter_begin();
@@ -2094,6 +2069,8 @@ void VMExecutionContext::invokeFunc(TypedValue* retval,
to = m_stack.allocTV();
} else {
if (!(f->attrs() & AttrMayUseVV)) {
+ // Discard extra arguments, since the function cannot
+ // possibly use them.
ASSERT(extraArgs == NULL);
ar->setNumArgs(numParams);
break;
@@ -2101,7 +2078,7 @@ void VMExecutionContext::invokeFunc(TypedValue* retval,
ASSERT(extraArgs != NULL && numExtraArgs > 0);
// VarEnv expects the extra args to be in "reverse" order
// (i.e. the last extra arg has the lowest address)
- to = extraArgs + (numExtraArgs - 1) - (paramId - numParams);
+ to = extraArgs->getExtraArg(paramId - numParams);
}
if (LIKELY(!f->byRef(paramId))) {
tvDup(from, to);
@@ -2387,7 +2364,6 @@ Array VMExecutionContext::debugBacktrace(bool skip /* = false */,
args.append(tvAsVariant(arg));
}
for (; i < nargs; i++) {
- ASSERT(fp->numExtraArgs());
TypedValue *arg = fp->getExtraArg(i - nparams);
args.append(tvAsVariant(arg));
}
@@ -5886,7 +5862,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopFCall(PC& pc) {
bool VMExecutionContext::prepareArrayArgs(ActRec* ar,
ArrayData* args,
- TypedValue*& extraArgs) {
+ ExtraArgs*& extraArgs) {
extraArgs = NULL;
if (UNLIKELY(ar->hasInvName())) {
m_stack.pushStringNoRc(ar->getInvName());
@@ -5908,13 +5884,21 @@ bool VMExecutionContext::prepareArrayArgs(ActRec* ar,
args->getValueRef(pos).asTypedValue());
if (UNLIKELY(f->byRef(i))) {
if (UNLIKELY(!tvAsVariant(from).isReferenced())) {
+ // TODO: #1746957
+ // we should raise a warning and bail out here. But there are
+ // lots of tests dependent on actually making the call.
+ // Hopefully the warnings will get the code base cleaned up
+ // and we'll be able to fix this painlessly
+ const bool skipCallOnInvalidParams = false;
int param = i + 1;
- while (i--) m_stack.popTV();
- m_stack.popAR();
- m_stack.pushNull();
raise_warning("Parameter %d to %s() expected to be a reference, "
"value given", param, f->name()->data());
- return false;
+ if (skipCallOnInvalidParams) {
+ while (i--) m_stack.popTV();
+ m_stack.popAR();
+ m_stack.pushNull();
+ return false;
+ }
}
tvDup(from, m_stack.allocTV());
} else {
@@ -5927,9 +5911,9 @@ bool VMExecutionContext::prepareArrayArgs(ActRec* ar,
pos = args->iter_advance(pos);
}
if (extra && (ar->m_func->attrs() & AttrMayUseVV)) {
- extraArgs = (TypedValue*)malloc(sizeof(TypedValue) * extra);
- while (extra--) {
- TypedValue* to = extraArgs + extra;
+ extraArgs = ExtraArgs::allocateUninit(extra);
+ for (int i = 0; i < extra; ++i) {
+ TypedValue* to = extraArgs->getExtraArg(i);
tvDup(args->getValueRef(pos).asTypedValue(), to);
if (to->m_type == KindOfRef && to->m_data.pref->_count == 2) {
tvUnbox(to);
@@ -5944,18 +5928,17 @@ bool VMExecutionContext::prepareArrayArgs(ActRec* ar,
return true;
}
-void VMExecutionContext::cleanupParamsAndActRec(ActRec* ar,
- TypedValue* extraArgs) {
+static void cleanupParamsAndActRec(VM::Stack& stack,
+ ActRec* ar,
+ ExtraArgs* extraArgs) {
for (int i = ar->numArgs(); i--; ) {
- m_stack.popTV();
+ stack.popTV();
}
- ASSERT(m_stack.top() == (void*)ar);
- m_stack.popAR();
+ ASSERT(stack.top() == (void*)ar);
+ stack.popAR();
if (extraArgs) {
- for (int i = ar->numArgs() - ar->m_func->numParams(); i--; ) {
- tvRefcountedDecRef(extraArgs + i);
- }
- free(extraArgs);
+ const int numExtra = ar->numArgs() - ar->m_func->numParams();
+ ExtraArgs::deallocate(extraArgs, numExtra);
}
}
@@ -5970,14 +5953,14 @@ inline void OPTBLD_INLINE VMExecutionContext::iopFCallArray(PC& pc) {
// this is what we /should/ do, but our code base depends
// on the broken behavior of casting the second arg to an
// array.
- cleanupParamsAndActRec(ar, NULL);
+ cleanupParamsAndActRec(m_stack, ar, NULL);
m_stack.pushNull();
raise_warning("call_user_func_array() expects parameter 2 to be array");
return;
}
const Func* func = ar->m_func;
- TypedValue* extraArgs = NULL;
+ ExtraArgs* extraArgs = NULL;
{
Array args(LIKELY(c1->m_type == KindOfArray) ? c1->m_data.parr :
tvAsVariant(c1).toArray().get());
@@ -6005,12 +5988,12 @@ inline void OPTBLD_INLINE VMExecutionContext::iopFCallArray(PC& pc) {
&retval, func, args,
ar->hasThis() ? ar->getThis() : NULL,
invName, h)) {
- cleanupParamsAndActRec(ar, extraArgs);
+ cleanupParamsAndActRec(m_stack, ar, extraArgs);
*m_stack.allocTV() = retval;
return;
}
} catch (...) {
- cleanupParamsAndActRec(ar, extraArgs);
+ cleanupParamsAndActRec(m_stack, ar, extraArgs);
m_stack.pushNull();
SYNC();
throw;
View
48 src/runtime/vm/bytecode.h
@@ -77,19 +77,41 @@ namespace VM {
class Func;
struct ExtraArgs : private boost::noncopyable {
-private:
- TypedValue* m_extraArgs;
- unsigned m_numExtraArgs;
+ /*
+ * Allocate an ExtraArgs structure, with arguments copied from the
+ * evaluation stack. This takes ownership of the args without
+ * adjusting reference counts, so they must be discarded from the
+ * stack.
+ */
+ static ExtraArgs* allocateCopy(TypedValue* args, unsigned nargs);
-public:
+ /*
+ * Allocate an ExtraArgs, without initializing any of the arguments.
+ * All arguments must be initialized via getExtraArg before
+ * deallocate() is called for the returned pointer.
+ */
+ static ExtraArgs* allocateUninit(unsigned nargs);
+
+ /*
+ * Deallocate an extraArgs structure. Either use the one that
+ * exists in a ActRec, or do it explicitly.
+ */
+ static void deallocate(ActRec*);
+ static void deallocate(ExtraArgs*, unsigned numArgs);
+
+ /*
+ * Get the slot for extra arg i, where i = argNum - func->numParams.
+ */
+ TypedValue* getExtraArg(unsigned argInd) const;
+
+private:
ExtraArgs();
- ExtraArgs(ExtraArgs*); // move (may take a null)
~ExtraArgs();
- void setExtraArgs(TypedValue* args, unsigned nargs);
- void copyExtraArgs(TypedValue* args, unsigned nargs);
- unsigned numExtraArgs() const;
- TypedValue* getExtraArg(unsigned argInd) const;
+ static void* allocMem(unsigned nargs);
+
+private:
+ TypedValue m_extraArgs[];
};
/*
@@ -113,7 +135,7 @@ struct ExtraArgs : private boost::noncopyable {
*/
class VarEnv {
private:
- ExtraArgs m_extraArgs;
+ ExtraArgs* m_extraArgs;
uint16_t m_depth;
bool m_malloced;
ActRec* m_cfp;
@@ -184,7 +206,6 @@ class VarEnv {
bool isGlobalScope() const { return !m_previous; }
// Access to wrapped ExtraArgs, if we have one.
- unsigned numExtraArgs() const;
TypedValue* getExtraArg(unsigned argInd) const;
};
@@ -353,11 +374,6 @@ struct ActRec {
#undef UNION_FIELD_ACCESSORS
// Accessors for extra arg queries.
- int numExtraArgs() const {
- return hasExtraArgs() ? getExtraArgs()->numExtraArgs() :
- hasVarEnv() ? getVarEnv()->numExtraArgs() :
- 0;
- }
TypedValue* getExtraArg(unsigned ind) const {
ASSERT(hasExtraArgs() || hasVarEnv());
return hasExtraArgs() ? getExtraArgs()->getExtraArg(ind) :
View
2 src/runtime/vm/runtime.h
@@ -94,7 +94,7 @@ frame_free_locals_helper_inl(ActRec* fp, int numLocals) {
}
// Free extra args
ASSERT(fp->hasExtraArgs());
- delete fp->getExtraArgs();
+ ExtraArgs::deallocate(fp);
}
// Free locals
for (int i = 0; i < numLocals; i++) {
View
12 src/runtime/vm/translator/asm-x64.cpp
@@ -25,6 +25,7 @@
#include "assert.h"
#include "asm-x64.h"
+#include "util/kernel_version.h"
#include "runtime/base/runtime_option.h"
namespace HPHP {
@@ -40,12 +41,21 @@ static void panic(const char *fmt, ...) {
Address allocSlab(size_t size) {
Address result = (Address)
- // XXX: ponder MAP_SHARED?
mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (result == MAP_FAILED) {
panic("%s:%d: (%s) map of %zu bytes failed (%s)\n",
__FILE__, __LINE__, __func__, size, strerror(errno));
}
+#ifdef MADV_HUGEPAGE
+ if (RuntimeOption::EvalMapTCHuge) {
+ static KernelVersion kv;
+ // This kernel fixed a panic when using MADV_HUGEPAGE.
+ static KernelVersion minKv("3.2.28-72_fbk12");
+ if (KernelVersion::cmp(kv, minKv) >= 0) {
+ madvise(result, size, MADV_HUGEPAGE);
+ }
+ }
+#endif
return result;
}
View
18 src/runtime/vm/translator/translator-x64.cpp
@@ -2225,14 +2225,10 @@ TranslatorX64::trimExtraArgs(ActRec* ar) {
numArgs, f->name()->data(), numParams, ar);
if (f->attrs() & AttrMayUseVV) {
- if (!ar->hasExtraArgs()) {
- ar->setExtraArgs(new ExtraArgs());
- }
- // Stash the excess args in the VarEnv attached to the ActRec. They'll be
- // decref'ed, if needed, when the VarEnv gets destructed.
- ar->getExtraArgs()->copyExtraArgs(
+ ASSERT(!ar->hasExtraArgs());
+ ar->setExtraArgs(ExtraArgs::allocateCopy(
(TypedValue*)(uintptr_t(ar) - numArgs * sizeof(TypedValue)),
- numArgs - numParams);
+ numArgs - numParams));
} else {
// Function is not marked as "MayUseVV", so discard the extra arguments
TypedValue* tv = (TypedValue*)(uintptr_t(ar) - numArgs*sizeof(TypedValue));
@@ -12622,13 +12618,7 @@ TranslatorX64::translateIterInit(const Tracelet& t,
// new_iter returns 0 if an iterator was not created, otherwise it
// returns 1
a. test_reg64_reg64(rax, rax);
- TCA toPatch = a.code.frontier;
- a. jz(a.code.frontier); // 1f
- emitBindJmp(notTaken);
- // 1:
- a.patchJcc(toPatch, a.code.frontier);
- emitBindJmp(taken);
- translator_not_reached(a);
+ emitCondJmp(taken, notTaken, CC_Z);
}
void
View
11 src/runtime/vm/translator/translator.cpp
@@ -1448,13 +1448,14 @@ bool Translator::applyInputMetaData(Unit::MetaHandle& metaHand,
continue;
}
const StringData* sd = ni->unit()->lookupLitstrId(info.m_data);
- ASSERT(!dl->rtt.valueClass() ||
- dl->rtt.valueClass()->name() == sd);
- SKTRACE(1, ni->source, "replacing input %d with a MetaInfo-supplied "
- "class; old type = %s\n",
- arg, dl->pretty().c_str());
Class* cls = Unit::lookupClass(sd);
if (cls) {
+ ASSERT(!dl->rtt.valueClass() ||
+ cls->classof(dl->rtt.valueClass()) ||
+ dl->rtt.valueClass()->classof(cls));
+ SKTRACE(1, ni->source, "replacing input %d with a MetaInfo-supplied "
+ "class; old type = %s\n",
+ arg, dl->pretty().c_str());
if (dl->rtt.isVariant()) {
dl->rtt = RuntimeType(KindOfRef, KindOfObject, cls);
} else {
View
8 src/test/test_ext_options.cpp
@@ -180,12 +180,8 @@ bool TestExtOptions::test_get_magic_quotes_gpc() {
}
bool TestExtOptions::test_get_magic_quotes_runtime() {
- try {
- f_get_magic_quotes_runtime();
- } catch (NotSupportedException e) {
- return Count(true);
- }
- return Count(false);
+ VS(f_get_magic_quotes_runtime(), 0);
+ return Count(true);
}
bool TestExtOptions::test_get_required_files() {
View
11 src/test/test_ext_simplexml.cpp
@@ -78,12 +78,11 @@ bool TestExtSimplexml::test_libxml_set_streams_context() {
}
bool TestExtSimplexml::test_libxml_disable_entity_loader() {
- try {
- f_libxml_disable_entity_loader(true);
- } catch (NotImplementedException e) {
- return Count(true);
- }
- return Count(false);
+
+ VS(f_libxml_disable_entity_loader(true), false);
+ VS(f_libxml_disable_entity_loader(true), true);
+ VS(f_libxml_disable_entity_loader(false), true);
+ return Count(true);
}
bool TestExtSimplexml::test_SimpleXMLElement() {
View
2 src/test/vm/cuf05.php.exp
@@ -27,7 +27,7 @@ array(1) {
&int(1)
}
HipHop Warning: Parameter 1 to bar() expected to be a reference, value given in src/test/vm/cuf05.php on line 13
-NULL
+int(1)
array(1) {
[0]=>
int(1)
View
46 src/util/kernel_version.cpp
@@ -0,0 +1,46 @@
+/*
+ +----------------------------------------------------------------------+
+ | HipHop for PHP |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 2010- 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 <unistd.h>
+#include <sys/utsname.h>
+#include <stdio.h>
+
+#include <util/util.h>
+#include <util/assert.h>
+#include <util/kernel_version.h>
+
+namespace HPHP {
+
+void KernelVersion::parse(const char* s) {
+ DEBUG_ONLY int numFields = sscanf(s,
+ "%d.%d.%d-%d_fbk%d_",
+ &m_major, &m_dot, &m_dotdot, &m_dash,
+ &m_fbk);
+ ASSERT(numFields >= 3);
+}
+
+KernelVersion::KernelVersion() {
+ struct utsname uts;
+ DEBUG_ONLY int err = uname(&uts);
+ ASSERT(err == 0);
+ m_major = m_dot = m_dotdot = m_dash = m_fbk = -1;
+ parse(uts.release);
+}
+
+KernelVersion::KernelVersion(const char* s) {
+ parse(s);
+}
+
+}
View
46 src/util/kernel_version.h
@@ -0,0 +1,46 @@
+/*
+ +----------------------------------------------------------------------+
+ | HipHop for PHP |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 2010- 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. |
+ +----------------------------------------------------------------------+
+*/
+#ifndef incl_KERNEL_VERSION_H_
+#define incl_KERNEL_VERSION_H_
+
+namespace HPHP {
+
+struct KernelVersion {
+ // <major>.<dot>.<dotdot>-<dash>_fbk<fbk>
+ int m_major;
+ int m_dot;
+ int m_dotdot;
+ int m_dash;
+ int m_fbk;
+ KernelVersion(); // Use uname
+ KernelVersion(const char*); // A known kernel version for cmp.
+ static int cmp(const KernelVersion& l, const KernelVersion& r) {
+#define C(field) if (l.field != r.field) return l.field - r.field;
+ C(m_major);
+ C(m_dot);
+ C(m_dotdot);
+ C(m_dash);
+ C(m_fbk);
+#undef C
+ return 0;
+ }
+ private:
+ void parse(const char* c);
+};
+
+}
+
+#endif

No commit comments for this range

Something went wrong with that request. Please try again.