yate/libs/yscript/javascript.cpp

4006 lines
110 KiB
C++

/**
* javascript.cpp
* Yet Another (Java)script library
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2011-2014 Null Team
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "yatescript.h"
#include <yatengine.h>
//#define STATS_TRACE "jstrace"
using namespace TelEngine;
namespace { // anonymous
class ParseNested;
class JsRunner;
class JsCodeStats;
class JsContext : public JsObject, public ScriptMutex
{
YCLASS(JsContext,JsObject)
public:
inline JsContext(unsigned int instIdx = 0, unsigned int maxInst = 1)
: JsObject("Context",0), ScriptMutex(true,"JsContext"),
m_trackObjs(0), m_trackObjsMtx(false,"JsObjTrack")
{
setMutex(this);
params().addParam(new ExpFunction("isNaN"));
params().addParam(new ExpFunction("parseInt"));
params().addParam(new ExpOperation(ExpOperation::nonInteger(),"NaN"));
m_objTrack = !!m_trackObjs;
setInstance(instIdx,maxInst);
DDebug(DebugAll,"JsContext::JsContext() [%p]",this);
}
virtual void destroyed();
virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context);
virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context);
virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context);
GenObject* resolve(ObjList& stack, String& name, GenObject* context);
bool runStringFunction(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context);
bool runStringField(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context);
void objCreated(GenObject* obj)
{ createdObj(obj); };
void objDeleted(GenObject* obj)
{ deletedObj(obj); };
void createdObj(GenObject* obj);
void deletedObj(GenObject* obj);
void trackObjs(unsigned int track = 0);
ObjList* countAllocations();
private:
GenObject* resolveTop(ObjList& stack, const String& name, GenObject* context);
HashList* m_trackObjs;
Mutex m_trackObjsMtx;
};
class JsNull : public JsObject
{
public:
inline JsNull()
: JsObject(0,"null",0,true)
{ }
};
class ExpNull : public ExpWrapper
{
public:
inline ExpNull()
: ExpWrapper(new JsNull,"null")
{ }
virtual bool valBoolean() const
{ return false; }
virtual ExpOperation* clone(const char* name) const
{ return new ExpNull(static_cast<JsNull*>(object()),name); }
virtual ExpOperation* copy(ScriptMutex* mtx) const
{ return clone(name()); }
protected:
inline ExpNull(JsNull* obj, const char* name)
: ExpWrapper(obj,name)
{ obj->ref(); }
};
class JsCodeFile : public String
{
public:
inline JsCodeFile(const String& file)
: String(file), m_fileTime(0), m_shortName(file)
{ File::getFileTime(file,m_fileTime); setShortName(); }
inline JsCodeFile(const String& file, unsigned int fTime)
: String(file), m_fileTime(fTime), m_shortName(file)
{ setShortName(); }
inline unsigned int fileTime() const
{ return m_fileTime; }
inline bool fileChanged() const
{
if (!m_fileTime)
return false;
unsigned int t = 0;
File::getFileTime(c_str(),t);
return t != m_fileTime;
}
inline const String& shortName() const
{ return m_shortName; }
private:
inline void setShortName()
{
int pos = rfind('/');
#ifdef _WINDOWS
int backPos = rfind('\\');
pos = pos > backPos ? pos : backPos;
#endif
if (pos >= 0 && (pos < (int)length() - 1))
m_shortName = substr(pos + 1);
}
unsigned int m_fileTime;
String m_shortName;
};
struct JsEntry
{
long int number;
unsigned int index;
};
class JsCode : public ScriptCode, public ExpEvaluator
{
friend class TelEngine::JsFunction;
friend class TelEngine::JsParser;
friend class ParseNested;
friend class JsRunner;
public:
enum JsOpcode {
OpcBegin = OpcPrivate + 1,
OpcEnd,
OpcFlush,
OpcIndex,
OpcEqIdentity,
OpcNeIdentity,
OpcFieldOf,
OpcTypeof,
OpcNew,
OpcDelete,
OpcFor,
OpcWhile,
OpcIf,
OpcElse,
OpcSwitch,
OpcCase,
OpcDefault,
OpcBreak,
OpcCont,
OpcIn,
OpcOf,
OpcNext,
OpcVar,
OpcWith,
OpcTry,
OpcCatch,
OpcFinally,
OpcThrow,
OpcFuncDef,
OpcReturn,
OpcJump,
OpcJumpTrue,
OpcJumpFalse,
OpcJRel,
OpcJRelTrue,
OpcJRelFalse,
OpcTrue,
OpcFalse,
OpcNull,
OpcUndefined,
OpcInclude,
OpcRequire,
OpcPragma,
};
inline JsCode()
: ExpEvaluator(C),
m_pragmas(""), m_label(0), m_depth(0), m_entries(0), m_traceable(false)
{ debugName("JsCode"); }
~JsCode();
virtual void* getObject(const String& name) const
{
if (name == YATOM("JsCode"))
return const_cast<JsCode*>(this);
if (name == YATOM("ExpEvaluator"))
return const_cast<ExpEvaluator*>((const ExpEvaluator*)this);
return ScriptCode::getObject(name);
}
virtual bool initialize(ScriptContext* context) const;
virtual bool evaluate(ScriptRun& runner, ObjList& results) const;
virtual ScriptRun* createRunner(ScriptContext* context, const char* title);
virtual bool null() const;
virtual void dump(String& res, bool loneNo = false) const;
virtual void getFileLine(unsigned int line, String& fileName, unsigned int& fileLine, bool wholePath = true)
{
fileName = getFileName(line,wholePath);
fileLine = getLineNo(line);
}
bool link();
inline bool traceable() const
{ return m_traceable; }
JsObject* parseArray(ParsePoint& expr, bool constOnly, ScriptMutex* mtx);
JsObject* parseObject(ParsePoint& expr, bool constOnly, ScriptMutex* mtx);
inline const NamedList& pragmas() const
{ return m_pragmas; }
inline static unsigned int getLineNo(unsigned int line)
{ return line & 0xffffff; }
inline static unsigned int getFileNo(unsigned int line)
{ return (line >> 24) & 0xff; }
inline unsigned int getFileCount() const
{ return m_included.length(); }
const String& getFileAt(unsigned int index, bool wholePath = true) const;
inline const String& getFileName(unsigned int line, bool wholePath = true) const
{ return getFileAt(getFileNo(line),wholePath); }
bool scriptChanged() const;
protected:
inline void trace(bool allowed)
{ m_traceable = allowed; }
void setBaseFile(const String& file);
virtual void formatLineNo(String& buf, unsigned int line) const;
virtual bool getString(ParsePoint& expr);
virtual bool getEscape(const char*& expr, String& str, char sep);
virtual bool keywordLetter(char c) const;
virtual int getKeyword(const char* str) const;
virtual char skipComments(ParsePoint& expr, GenObject* context = 0);
virtual int preProcess(ParsePoint& expr, GenObject* context = 0);
virtual bool getInstruction(ParsePoint& expr, char stop, GenObject* nested);
virtual bool getSimple(ParsePoint& expr, bool constOnly = false);
virtual Opcode getOperator(ParsePoint& expr);
virtual Opcode getUnaryOperator(ParsePoint& expr);
virtual Opcode getPostfixOperator(ParsePoint& expr, int precedence);
virtual const char* getOperator(Opcode oper) const;
virtual int getPrecedence(ExpEvaluator::Opcode oper) const;
virtual bool getSeparator(ParsePoint& expr, bool remove);
virtual bool runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) const;
private:
ObjVector m_linked;
ObjList m_included;
ObjList m_globals;
NamedList m_pragmas;
bool preProcessInclude(ParsePoint& expr, bool once, GenObject* context);
bool preProcessPragma(ParsePoint& expr, GenObject* context);
bool getOneInstruction(ParsePoint& expr, GenObject* nested);
bool parseInner(ParsePoint& expr, JsOpcode opcode, ParseNested* nested);
bool parseIf(ParsePoint& expr, GenObject* nested);
bool parseSwitch(ParsePoint& expr, GenObject* nested);
bool parseFor(ParsePoint& expr, GenObject* nested);
bool parseWhile(ParsePoint& expr, GenObject* nested);
bool parseVar(ParsePoint& expr);
bool parseTry(ParsePoint& expr, GenObject* nested);
bool parseFuncDef(ParsePoint& expr, bool publish);
bool parseSimple(ParsePoint& expr, bool constOnly, ScriptMutex* mtx = 0);
bool evalList(ObjList& stack, GenObject* context) const;
bool evalVector(ObjList& stack, GenObject* context) const;
bool jumpToLabel(long int label, GenObject* context) const;
bool jumpRelative(long int offset, GenObject* context) const;
bool jumpAbsolute(long int index, GenObject* context) const;
bool callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context,
JsFunction* func, bool constr, JsObject* thisObj = 0) const;
bool callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context,
long int retIndex, JsFunction* func, ObjList& args,
JsObject* thisObj, JsObject* scopeObj) const;
void resolveObjectParams(JsObject* obj, ObjList& stack, GenObject* context) const;
void resolveObjectParams(JsObject* object, ObjList& stack, GenObject* context, JsContext* ctxt,
JsObject* objProto, JsArray* arrayProto) const;
inline JsFunction* getGlobalFunction(const String& name) const
{ return YOBJECT(JsFunction,m_globals[name]); }
long int m_label;
int m_depth;
JsEntry* m_entries;
bool m_traceable;
};
class JsIterator : public RefObject
{
YCLASS(JsIterator,RefObject)
public:
inline JsIterator(const ExpOperation& field, JsObject* obj)
: m_field(field.clone()), m_obj(obj)
{ obj->fillFieldNames(m_keys); }
inline JsIterator(const ExpOperation& field, NamedList* lst)
: m_field(field.clone())
{ ScriptContext::fillFieldNames(m_keys,*lst); }
virtual ~JsIterator()
{ TelEngine::destruct(m_field); }
inline ExpOperation& field() const
{ return *m_field; }
inline String* get()
{ return static_cast<String*>(m_keys.remove(false)); }
inline const String& name() const
{ return m_name; }
inline void name(const char* objName)
{ m_name = objName; }
private:
ExpOperation* m_field;
RefPointer<JsObject> m_obj;
ObjList m_keys;
String m_name;
};
class JsLineStats : public GenObject
{
public:
inline JsLineStats(unsigned int lineNo, unsigned int instr, u_int64_t usec)
: lineNumber(lineNo), operations(instr), microseconds(usec), isCall(false)
{ }
unsigned int lineNumber;
unsigned int operations;
u_int64_t microseconds;
bool isCall;
};
class JsCallStats : public JsLineStats
{
public:
inline JsCallStats(const char* name, unsigned int caller, unsigned int called, unsigned int instr, u_int64_t usec)
: JsLineStats(caller,instr,usec),
funcName(name), callsCount(1), calledLine(called)
{ isCall = true; }
String funcName;
unsigned int callsCount;
unsigned int calledLine;
};
class JsFuncStats : public String
{
public:
inline JsFuncStats(const char* name, unsigned int lineNo)
: String(name), lineNumber(lineNo)
{ }
unsigned int lineNumber;
ObjList funcLines;
void updateLine(unsigned int lineNo, u_int64_t usec);
void updateCall(const char* name, unsigned int caller, unsigned int called, unsigned int instr, u_int64_t usec);
};
class JsCodeStats : public Mutex, public RefObject
{
YCLASS(JsCodeStats,RefObject)
public:
JsCodeStats(JsCode* code, const char* file = 0);
~JsCodeStats();
virtual const String& toString() const
{ return m_fileName; }
JsFuncStats* getFuncStats(const char* name, unsigned int lineNo);
void dump();
void dump(const char* file);
void dump(Stream& file);
private:
RefPointer<JsCode> m_code;
String m_fileName;
ObjList m_funcStats;
};
class JsCallInfo : public String
{
friend class JsRunner;
private:
inline JsCallInfo(JsFuncStats* stats, const char* name,
unsigned int caller, unsigned int called, unsigned int instr, u_int64_t time)
: String(name),
funcStats(stats), callerLine(caller), calledLine(called), startInstr(instr), startTime(time)
{ }
inline void traceLine(unsigned int line, u_int64_t time)
{ if (funcStats) funcStats->updateLine(line,time); }
inline void traceCall(const JsCallInfo* call, unsigned int instr, u_int64_t time)
{ if (call && funcStats) funcStats->updateCall(call->c_str(),call->callerLine,call->calledLine,instr,time); }
JsFuncStats* funcStats;
unsigned int callerLine;
unsigned int calledLine;
unsigned int startInstr;
u_int64_t startTime;
};
class JsRunner : public ScriptRun
{
YCLASS(JsRunner,ScriptRun)
friend class JsCode;
public:
inline JsRunner(ScriptCode* code, ScriptContext* context, const char* title)
: ScriptRun(code,context),
m_paused(false), m_tracing(false), m_opcode(0), m_index(0),
m_instr(0), m_lastLine(0), m_lastTime(0), m_totalTime(0), m_callInfo(0)
{ traceCheck(title); }
virtual ~JsRunner()
{ if (m_tracing) traceDump(); }
inline bool tracing() const
{ return m_tracing; }
virtual Status reset(bool init);
virtual bool pause();
virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0, ExpOperation* scopeObj = 0);
virtual bool callable(const String& name);
void traceStart(const char* title, const char* file = 0);
void tracePrep(const ExpOperation& oper);
void tracePost(const ExpOperation& oper);
void traceCall(const ExpOperation& oper, const JsFunction& func);
void traceReturn();
const ExpOperation* getCurrentOpCode() const;
virtual unsigned int currentLineNo() const;
virtual const String& currentFileName(bool wholePath = false) const;
protected:
virtual Status resume();
void traceDump();
void traceCheck(const char* title);
void traceStart(const char* title, JsCodeStats* stats);
private:
bool m_paused;
bool m_tracing;
const ObjList* m_opcode;
unsigned int m_index;
unsigned int m_instr;
unsigned int m_lastLine;
u_int64_t m_lastTime;
u_int64_t m_totalTime;
JsCallInfo* m_callInfo;
ObjList m_traceStack;
RefPointer<JsCodeStats> m_stats;
};
class ParseNested : public GenObject
{
YCLASS(ParseNested,GenObject)
public:
inline explicit ParseNested(JsCode* code, GenObject* nested,
JsCode::JsOpcode oper = (JsCode::JsOpcode)ExpEvaluator::OpcNone)
: m_code(code), m_nested(static_cast<ParseNested*>(nested)), m_opcode(oper)
{ }
inline operator GenObject*()
{ return this; }
inline operator JsCode::JsOpcode() const
{ return m_opcode; }
inline static JsCode::JsOpcode code(GenObject* nested)
{ return nested ? *static_cast<ParseNested*>(nested) :
(JsCode::JsOpcode)ExpEvaluator::OpcNone; }
inline static ParseNested* find(GenObject* nested, JsCode::JsOpcode opcode)
{ return nested ? static_cast<ParseNested*>(nested)->find(opcode) : 0; }
inline static ParseNested* findMatch(GenObject* nested, JsCode::JsOpcode opcode)
{ return nested ? static_cast<ParseNested*>(nested)->findMatch(opcode) : 0; }
static bool parseInner(GenObject* nested, JsCode::JsOpcode opcode, ParsePoint& expr)
{ ParseNested* inner = findMatch(nested,opcode);
return inner && inner->parseInner(expr,opcode); }
protected:
virtual bool isMatch(JsCode::JsOpcode opcode)
{ return false; }
inline bool parseInner(ParsePoint& expr, JsCode::JsOpcode opcode)
{ return m_code->parseInner(expr,opcode,this); }
inline ParseNested* find(JsCode::JsOpcode opcode)
{ return (opcode == m_opcode) ? this :
(m_nested ? m_nested->find(opcode) : 0); }
inline ParseNested* findMatch(JsCode::JsOpcode opcode)
{ return isMatch(opcode) ? this :
(m_nested ? m_nested->findMatch(opcode) : 0); }
private:
JsCode* m_code;
ParseNested* m_nested;
JsCode::JsOpcode m_opcode;
};
class NativeFields : public ObjList
{
public:
inline NativeFields()
{
append(new String("length"));
append(new String("charAt"));
append(new String("charCodeAt"));
append(new String("indexOf"));
append(new String("includes"));
append(new String("substr"));
append(new String("match"));
append(new String("toLowerCase"));
append(new String("toUpperCase"));
append(new String("trim"));
append(new String("sqlEscape"));
append(new String("startsWith"));
append(new String("endsWith"));
append(new String("split"));
append(new String("toString"));
append(new String("isNaN"));
append(new String("parseInt"));
append(new String("hashCode"));
}
};
#define MAKEOP(s,o) { s, JsCode::Opc ## o }
static const TokenDict s_operators[] =
{
MAKEOP("===", EqIdentity),
MAKEOP("!==", NeIdentity),
MAKEOP(".", FieldOf),
MAKEOP("in", In),
MAKEOP("of", Of),
{ 0, 0 }
};
static const TokenDict s_unaryOps[] =
{
MAKEOP("new", New),
MAKEOP("typeof", Typeof),
MAKEOP("delete", Delete),
{ 0, 0 }
};
static const TokenDict s_postfixOps[] =
{
MAKEOP("++", IncPost),
MAKEOP("--", DecPost),
{ 0, 0 }
};
static const TokenDict s_instr[] =
{
MAKEOP("function", FuncDef),
MAKEOP("for", For),
MAKEOP("while", While),
MAKEOP("if", If),
MAKEOP("else", Else),
MAKEOP("switch", Switch),
MAKEOP("case", Case),
MAKEOP("default", Default),
MAKEOP("break", Break),
MAKEOP("continue", Cont),
MAKEOP("var", Var),
MAKEOP("with", With),
MAKEOP("try", Try),
MAKEOP("catch", Catch),
MAKEOP("finally", Finally),
MAKEOP("throw", Throw),
MAKEOP("return", Return),
{ 0, 0 }
};
static const TokenDict s_constants[] =
{
MAKEOP("false", False),
MAKEOP("true", True),
MAKEOP("null", Null),
MAKEOP("undefined", Undefined),
MAKEOP("function", FuncDef),
{ 0, 0 }
};
static const TokenDict s_preProc[] =
{
MAKEOP("#include", Include),
MAKEOP("#require", Require),
MAKEOP("#pragma", Pragma),
{ 0, 0 }
};
#undef MAKEOP
#define MAKEOP(o) { "[" #o "]", JsCode::Opc ## o }
static const TokenDict s_internals[] =
{
MAKEOP(Field),
MAKEOP(Func),
MAKEOP(Push),
MAKEOP(Label),
MAKEOP(Begin),
MAKEOP(End),
MAKEOP(Flush),
MAKEOP(Jump),
MAKEOP(JumpTrue),
MAKEOP(JumpFalse),
MAKEOP(JRel),
MAKEOP(JRelTrue),
MAKEOP(JRelFalse),
{ 0, 0 }
};
#undef MAKEOP
static const ExpNull s_null;
static const String s_noFile = "[no file]";
static const NativeFields s_nativeFields;
void JsContext::destroyed()
{
params().clearParams();
TelEngine::destruct(m_trackObjs);
setMutex(0);
JsObject::destroyed();
}
GenObject* JsContext::resolveTop(ObjList& stack, const String& name, GenObject* context)
{
XDebug(DebugAll,"JsContext::resolveTop '%s'",name.c_str());
for (ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
JsObject* jso = YOBJECT(JsObject,l->get());
if (jso && jso->toString() == YSTRING("()") && jso->hasField(stack,name,context))
return jso;
}
return this;
}
GenObject* JsContext::resolve(ObjList& stack, String& name, GenObject* context)
{
GenObject* obj = 0;
if (name.find('.') < 0)
obj = resolveTop(stack,name,context);
else {
ObjList* list = name.split('.',true);
name.clear();
for (ObjList* l = list->skipNull(); l; ) {
const String* s = static_cast<const String*>(l->get());
ObjList* l2 = l->skipNext();
if (TelEngine::null(s)) {
// consecutive dots - not good
obj = 0;
break;
}
if (!obj)
obj = resolveTop(stack,*s,context);
name.append(*s,".");
if (!l2)
break;
ExpExtender* ext = YOBJECT(ExpExtender,obj);
if (ext) {
GenObject* adv = ext->getField(stack,name,context);
XDebug(DebugAll,"JsContext::resolve advanced to '%s' of %p for '%s'",
(adv ? adv->toString().c_str() : 0),ext,s->c_str());
if (adv) {
if (YOBJECT(ExpExtender,adv)) {
obj = adv;
name.clear();
}
else if (l->count() == 2) { // there is only one other field after this one
s = static_cast<const String*>(l2->get());
if (!TelEngine::null(s) && s_nativeFields.find(*s)) {
obj = adv;
name.clear();
}
}
}
}
l = l2;
}
TelEngine::destruct(list);
}
DDebug(DebugAll,"JsContext::resolve got '%s' %p for '%s'",
(obj ? obj->toString().c_str() : 0),obj,name.c_str());
return obj;
}
bool JsContext::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context)
{
XDebug(DebugAll,"JsContext::runFunction '%s' line=0x%08x [%p]",oper.name().c_str(),oper.lineNumber(),this);
String name = oper.name();
GenObject* o = resolve(stack,name,context);
if (o && o != this) {
ExpExtender* ext = YOBJECT(ExpExtender,o);
if (ext) {
ExpOperation op(oper,name);
return ext->runFunction(stack,op,context);
}
if (runStringFunction(o,name,stack,oper,context))
return true;
}
if (name == YSTRING("isNaN")) {
bool nan = true;
ExpOperation* op = popValue(stack,context);
if (op)
nan = !op->isInteger();
TelEngine::destruct(op);
ExpEvaluator::pushOne(stack,new ExpOperation(nan));
return true;
}
if (name == YSTRING("parseInt")) {
int64_t val = ExpOperation::nonInteger();
ObjList args;
extractArgs(stack,oper,context,args);
ExpOperation* op1 = static_cast<ExpOperation*>(args[0]);
if (op1) {
int base = 0;
ExpOperation* op2 = static_cast<ExpOperation*>(args[1]);
if (op2) {
base = (int)op2->valInteger();
if (base < 2 || base > 36)
base = 0;
}
val = op1->trimSpaces().toInt64(val,base);
}
ExpEvaluator::pushOne(stack,new ExpOperation(val));
return true;
}
return JsObject::runFunction(stack,oper,context);
}
bool JsContext::runField(ObjList& stack, const ExpOperation& oper, GenObject* context)
{
XDebug(DebugAll,"JsContext::runField '%s' [%p]",oper.name().c_str(),this);
String name = oper.name();
GenObject* o = resolve(stack,name,context);
if (o && o != this) {
ExpExtender* ext = YOBJECT(ExpExtender,o);
if (ext) {
ExpOperation op(oper,name);
return ext->runField(stack,op,context);
}
if (runStringField(o,name,stack,oper,context))
return true;
}
return JsObject::runField(stack,oper,context);
}
bool JsContext::runStringFunction(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context)
{
const String* str = YOBJECT(String,obj);
if (!str)
return false;
if (name == YSTRING("charAt")) {
int idx = 0;
ObjList args;
if (extractArgs(stack,oper,context,args)) {
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
if (op && op->isInteger())
idx = (int)op->number();
}
ExpEvaluator::pushOne(stack,new ExpOperation(String(str->at(idx))));
return true;
}
if (name == YSTRING("charCodeAt")) {
int idx = 0;
ObjList args;
if (extractArgs(stack,oper,context,args)) {
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
if (op && op->isInteger())
idx = (int)op->number();
}
ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)(uint8_t)str->at(idx)));
return true;
}
if ((name == YSTRING("indexOf")) || (name == YSTRING("includes"))) {
int idx = -1;
ObjList args;
if (extractArgs(stack,oper,context,args)) {
const String* what = static_cast<String*>(args[0]);
if (what) {
ExpOperation* from = static_cast<ExpOperation*>(args[1]);
int offs = (from && from->isInteger()) ? (int)from->number() : 0;
if (offs < 0)
offs = 0;
idx = str->find(*what,offs);
}
}
if (name.length() == 7)
ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)idx));
else
ExpEvaluator::pushOne(stack,new ExpOperation(idx >= 0));
return true;
}
if (name == YSTRING("substr")) {
ObjList args;
int offs = 0;
int len = -1;
if (extractArgs(stack,oper,context,args)) {
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
if (op && op->isInteger())
offs = (int)op->number();
op = static_cast<ExpOperation*>(args[1]);
if (op && op->isInteger()) {
len = (int)op->number();
if (len < 0)
len = 0;
}
}
ExpEvaluator::pushOne(stack,new ExpOperation(str->substr(offs,len)));
return true;
}
if (name == YSTRING("match")) {
ObjList args;
String buf(*str);
if (extractArgs(stack,oper,context,args)) {
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
ExpWrapper* wrap = YOBJECT(ExpWrapper,op);
JsRegExp* rexp = YOBJECT(JsRegExp,wrap);
bool ok = false;
if (rexp)
ok = buf.matches(rexp->regexp());
else if (!wrap) {
Regexp r(*static_cast<String*>(op),true);
ok = buf.matches(r);
}
if (ok) {
JsArray* jsa = new JsArray(context,oper.lineNumber(),mutex());
for (int i = 0; i <= buf.matchCount(); i++)
jsa->push(new ExpOperation(buf.matchString(i)));
jsa->params().addParam(new ExpOperation((int64_t)buf.matchOffset(),"index"));
if (rexp)
jsa->params().addParam(wrap->clone("input"));
ExpEvaluator::pushOne(stack,new ExpWrapper(jsa));
return true;
}
}
ExpEvaluator::pushOne(stack,s_null.ExpOperation::clone());
return true;
}
#define NO_PARAM_STRING_METHOD(method) \
{ \
ObjList args; \
extractArgs(stack,oper,context,args); \
String s(*str); \
ExpEvaluator::pushOne(stack,new ExpOperation(s.method())); \
}
if (name == YSTRING("toLowerCase")) {
NO_PARAM_STRING_METHOD(toLower);
return true;
}
if (name == YSTRING("toUpperCase")) {
NO_PARAM_STRING_METHOD(toUpper);
return true;
}
if (name == YSTRING("trim")) {
NO_PARAM_STRING_METHOD(trimSpaces);
return true;
}
if (name == YSTRING("sqlEscape")) {
NO_PARAM_STRING_METHOD(sqlEscape);
return true;
}
#undef NO_PARAM_STRING_METHOD
#define MAKE_WITH_METHOD \
ObjList args; \
const char* what = 0; \
int pos = 0; \
if (extractArgs(stack,oper,context,args)) { \
if (args.skipNull()) { \
String* tmp = static_cast<String*>(args.skipNull()->get()); \
if (tmp) \
what = tmp->c_str(); \
} \
if (args.count() >= 2) { \
String* tmp = static_cast<String*>(args[1]); \
if (tmp) \
pos = tmp->toInteger(0); \
} \
} \
String s(*str);
if (name == YSTRING("startsWith")) {
MAKE_WITH_METHOD;
if (pos > 0)
s = s.substr(pos);
ExpEvaluator::pushOne(stack,new ExpOperation(s.startsWith(what)));
return true;
}
if (name == YSTRING("endsWith")) {
MAKE_WITH_METHOD;
if (pos > 0)
s = s.substr(0,pos);
ExpEvaluator::pushOne(stack,new ExpOperation(s.endsWith(what)));
return true;
}
#undef MAKE_WITH_METHOD
#define SPLIT_EMPTY() do { \
array->push(new ExpOperation(*str)); \
ExpEvaluator::pushOne(stack,new ExpWrapper(array,0)); \
return true; \
} while (false);
if (name == YSTRING("split")) {
ObjList args;
JsArray* array = new JsArray(context,oper.lineNumber(),mutex());
if (!(extractArgs(stack,oper,context,args) && args.skipNull()))
SPLIT_EMPTY();
GenObject* gen = args[0];
if (!gen)
SPLIT_EMPTY();
JsRegExp* jsReg = YOBJECT(JsRegExp,gen);
ObjList* splits = jsReg ? str->split(jsReg->regexp()) :
str->split(static_cast<String*>(gen)->at(0));
unsigned int limit = 0;
if (args.count() >= 2) {
String* l = static_cast<String*>(args[1]);
if (l)
limit = l->toInteger(0xffffffff);
}
if (!limit)
limit = 0xffffffff;
for (ObjList* o = splits->skipNull(); o && limit; o = o->skipNext(), limit--)
array->push(new ExpOperation(*static_cast<String*>(o->get())));
ExpEvaluator::pushOne(stack,new ExpWrapper(array,0));
TelEngine::destruct(splits);
return true;
}
#undef SPLIT_EMPTY
if (name == YSTRING("toString")) {
ObjList args;
extractArgs(stack,oper,context,args);
ExpOperation* op = YOBJECT(ExpOperation,str);
if (op && op->isInteger()) {
if (op->isBoolean()) {
ExpEvaluator::pushOne(stack,new ExpOperation(String::boolText(op->valBoolean())));
return true;
}
ExpOperation* tmp = static_cast<ExpOperation*>(args[0]);
int radix = tmp ? (int)tmp->valInteger() : 0;
if (radix < 2 || radix > 36)
radix = 10;
static const char s_base[] = "0123456789abcdefghijklmnopqrstuvwxyz";
int64_t n = op->valInteger();
bool neg = false;
if (n < 0) {
n = -n;
neg = true;
}
String s;
char buf[2];
buf[1] = '\0';
do {
buf[0] = s_base[n % radix];
s = buf + s;
} while ((n = n / radix));
tmp = static_cast<ExpOperation*>(args[1]);
int len = tmp ? (int)tmp->valInteger() : 0;
if (len > 1) {
if (neg)
len--;
while (len > (int)s.length())
s = "0" + s;
}
if (neg)
s = "-" + s;
ExpEvaluator::pushOne(stack,new ExpOperation(s));
return true;
}
ExpEvaluator::pushOne(stack,new ExpOperation(*str));
return true;
}
if (name == YSTRING("hashCode")) {
ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)str->hash()));
return true;
}
return false;
}
bool JsContext::runStringField(GenObject* obj, const String& name, ObjList& stack, const ExpOperation& oper, GenObject* context)
{
const String* s = YOBJECT(String,obj);
if (!s)
return false;
if (name == YSTRING("length")) {
int64_t len = s->lenUtf8();
if (len < 0)
len = s->length();
ExpEvaluator::pushOne(stack,new ExpOperation(len));
return true;
}
return false;
}
bool JsContext::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context)
{
XDebug(DebugAll,"JsContext::runAssign '%s'='%s' (%s) [%p]",
oper.name().c_str(),oper.c_str(),oper.typeOf(),this);
String name = oper.name();
GenObject* o = resolve(stack,name,context);
if (o && o != this) {
ExpExtender* ext = YOBJECT(ExpExtender,o);
if (ext) {
ExpOperation* op = oper.clone(name);
bool ok = ext->runAssign(stack,*op,context);
TelEngine::destruct(op);
return ok;
}
}
return JsObject::runAssign(stack,oper,context);
}
void JsContext::createdObj(GenObject* obj)
{
if (!m_trackObjs)
return;
JsObject* jso = YOBJECT(JsObject,obj);
if (!(jso && jso != static_cast<JsObject*>(this)))
return;
XDebug(DebugInfo,"Adding object=%p created at line=%u(0x%08x)",jso,jso->lineNo(),jso->lineNo());
Lock l(m_trackObjsMtx);
ObjList* o = m_trackObjs->append(jso,hashPtr(obj));
if (o)
o->setDelete(false);
}
void JsContext::deletedObj(GenObject* obj)
{
if (!m_trackObjs)
return;
JsObject* jso = YOBJECT(JsObject,obj);
if (!jso)
return;
XDebug(DebugInfo,"Removing object=%p created at line=%u(0x%08x)",jso,jso->lineNo(),jso->lineNo());
Lock l(m_trackObjsMtx);
m_trackObjs->remove(jso,hashPtr(obj),false);
}
void JsContext::trackObjs(unsigned int track)
{
// once tracking is set per script, it cannot be reset
if (!track || m_trackObjs)
return;
m_trackObjs = new HashList(track);
m_objTrack = true;
}
ObjList* JsContext::countAllocations()
{
if (!m_trackObjs)
return 0;
ObjList* counters = new ObjList();
for (unsigned int i = 0; i < m_trackObjs->length(); i++) {
m_trackObjsMtx.lock();
ObjList* l = m_trackObjs->getList(i);
if (!l) {
m_trackObjsMtx.unlock();
continue;
}
for (ObjList* n = l->skipNull(); n; n = n->skipNext()) {
JsObject* jso = YOBJECT(JsObject,n->get());
if (!jso)
continue;
String id(jso->lineNo());
NamedCounter* count = static_cast<NamedCounter*>(counters->operator[](id));
if (!count)
counters->insert(count = new NamedCounter(id));
count->inc();
}
m_trackObjsMtx.unlock();
}
if (!counters->skipNull()) {
TelEngine::destruct(counters);
return 0;
}
return counters;
}
JsCode::~JsCode()
{
delete[] m_entries;
}
// Initialize standard globals in the execution context
bool JsCode::initialize(ScriptContext* context) const
{
if (!context)
return false;
JsObject::initialize(context);
for (ObjList* l = m_globals.skipNull(); l; l = l->skipNext()) {
ExpOperation* op = static_cast<ExpOperation*>(l->get());
if (context->params().getParam(op->name()))
continue;
const JsFunction* jf = YOBJECT(JsFunction,op);
if (jf) {
JsObject* nf = jf->copy(context->mutex(),jf->getFunc()->name(),*op);
context->params().setParam(new ExpWrapper(nf,op->name(),op->barrier()));
}
else
context->params().setParam(op->clone());
}
return true;
}
bool JsCode::evaluate(ScriptRun& runner, ObjList& results) const
{
if (null())
return false;
bool ok = m_linked.length() ? evalVector(results,&runner) : evalList(results,&runner);
if (!ok)
return false;
if (static_cast<JsRunner&>(runner).m_paused)
return true;
if (!runAllFields(results,&runner))
return gotError("Could not evaluate all fields");
return true;
}
// Convert list to vector and fix label relocations
bool JsCode::link()
{
if (!m_opcodes.skipNull())
return false;
m_linked.assign(m_opcodes);
delete[] m_entries;
m_entries = 0;
unsigned int n = m_linked.count();
if (!n)
return false;
unsigned int entries = 0;
for (unsigned int i = 0; i < n; i++) {
const ExpOperation* l = static_cast<const ExpOperation*>(m_linked[i]);
if (!l || l->opcode() != OpcLabel)
continue;
long int lbl = (long int)l->number();
if (lbl >= 0 && l->barrier())
entries++;
for (unsigned int j = 0; j < n; j++) {
const ExpOperation* jmp = static_cast<const ExpOperation*>(m_linked[j]);
if (!jmp || jmp->number() != lbl)
continue;
Opcode op = OpcNone;
switch ((int)jmp->opcode()) {
case OpcJump:
op = (Opcode)OpcJRel;
break;
case OpcJumpTrue:
op = (Opcode)OpcJRelTrue;
break;
case OpcJumpFalse:
op = (Opcode)OpcJRelFalse;
break;
default:
continue;
}
long int offs = (long int)i - j;
ExpOperation* newJump = new ExpOperation(op,0,offs,jmp->barrier());
newJump->lineNumber(jmp->lineNumber());
m_linked.set(newJump,j);
}
}
if (entries) {
m_entries = new JsEntry[entries+1];
unsigned int e = 0;
for (unsigned int j = 0; j < n; j++) {
const ExpOperation* l = static_cast<const ExpOperation*>(m_linked[j]);
if (l && l->barrier() && l->opcode() == OpcLabel && l->number() >= 0) {
m_entries[e].number = (long int)l->number();
m_entries[e++].index = j;
}
}
m_entries[entries].number = -1;
m_entries[entries].index = 0;
}
return true;
}
const String& JsCode::getFileAt(unsigned int index, bool wholePath) const
{
if (!index)
return s_noFile;
const JsCodeFile* file = static_cast<JsCodeFile*>(m_included[index - 1]);
return file ? (wholePath ? file->toString() : file->shortName()) : s_noFile;
}
void JsCode::formatLineNo(String& buf, unsigned int line) const
{
unsigned int fnum = (line >> 24) & 0xff;
if (!fnum)
return ExpEvaluator::formatLineNo(buf,line);
buf.clear();
const GenObject* file = m_included[fnum - 1];
buf << (file ? file->toString().c_str() : "???") << ":" << (line & 0xffffff);
}
bool JsCode::getString(ParsePoint& expr)
{
if (inError())
return false;
char c = skipComments(expr);
if (c != '/' && c != '%')
return ExpEvaluator::getString(expr);
String str;
if (!ExpEvaluator::getString(expr,str))
return false;
bool extended = true;
bool insensitive = false;
if (c == '%') {
// dialplan pattern - turn it into a regular expression
insensitive = true;
String tmp = str;
tmp.toUpper();
str = "^";
char last = '\0';
int count = 0;
bool esc = false;
for (unsigned int i = 0; ; i++) {
c = tmp.at(i);
if (last && c != last) {
switch (last) {
case 'X':
str << "[0-9]";
break;
case 'Z':
str << "[1-9]";
break;
case 'N':
str << "[2-9]";
break;
case '.':
str << ".+";
count = 1;
break;
}
if (count > 1)
str << "{" << count << "}";
last = '\0';
count = 0;
}
if (!c) {
str << "$";
break;
}
switch (c) {
case '.':
if (esc) {
str << c;
break;
}
// fall through
case 'X':
case 'Z':
case 'N':
last = c;
count++;
break;
case '+':
case '*':
str << "\\";
// fall through
default:
str << c;
}
esc = (c == '\\');
}
}
else {
// regexp - check for flags
do {
c = *expr;
switch (c) {
case 'i':
expr++;
insensitive = true;
break;
case 'b':
expr++;
extended = false;
break;
default:
c = 0;
}
} while (c);
}
XDebug(this,DebugInfo,"Regexp '%s' flags '%s%s'",str.c_str(),
(insensitive ? "i" : ""),(extended ? "" : "b"));
JsRegExp* obj = new JsRegExp(0,str,lineNumber(),str,insensitive,extended);
addOpcode(new ExpWrapper(ExpEvaluator::OpcCopy,obj));
return true;
}
bool JsCode::getEscape(const char*& expr, String& str, char sep)
{
if (sep != '\'' && sep != '"') {
// this is not a string but a regexp or dialplan template
char c = *expr++;
if (!c)
return false;
if (c != '\\' && c != sep)
str << '\\';
str << c;
return true;
}
switch (*expr) {
case 'u':
{
DataBlock bin;
if (bin.unHexify(expr + 1,4,'\0')) {
str = UChar((bin.at(0) << 8) + bin.at(1));
expr += 5;
return true;
}
}
break;
case 'x':
{
DataBlock bin;
if (bin.unHexify(expr + 1,2,'\0')) {
str = UChar(bin.at(0));
expr += 3;
return true;
}
}
break;
}
return ExpEvaluator::getEscape(expr,str,sep);
}
bool JsCode::keywordLetter(char c) const
{
return ExpEvaluator::keywordLetter(c) || (c == '$');
}
int JsCode::getKeyword(const char* str) const
{
int len = 0;
const char*s = str;
for (;; len++) {
char c = *s++;
if (c <= ' ')
break;
if (!len && keywordDigit(c))
return 0;
if (keywordChar(c))
continue;
if (len && (c == '.')) {
if (!keywordLetter(s[0]))
return 0;
continue;
}
break;
}
if (len > 1 && (s[-2] == '.'))
len--;
if (len && ExpEvaluator::getOperator(str,s_instr) != OpcNone)
return 0;
return len;
}
char JsCode::skipComments(ParsePoint& expr, GenObject* context)
{
char c = skipWhites(expr);
while (c == '/') {
if (expr[1] == '/') {
// comment to end of line
expr+=2;
while ((c = *expr) && (c != '\r') && (c != '\n'))
expr++;
c = skipWhites(expr);
}
else if (expr[1] == '*') {
/* comment to close */
expr++;
while ((c = skipWhites(expr)) && (c != '*' || expr[1] != '/'))
expr++;
if (c) {
expr+=2;
c = skipWhites(expr);
}
}
else
break;
}
return c;
}
void JsCode::setBaseFile(const String& file)
{
if (file.null() || m_depth || m_included.find(file))
return;
m_included.append(new JsCodeFile(file));
int idx = m_included.index(file);
m_lineNo = ((idx + 1) << 24) | 1;
}
bool JsCode::scriptChanged() const
{
for (const ObjList* l = m_included.skipNull(); l; l = l->skipNext())
if (static_cast<const JsCodeFile*>(l->get())->fileChanged())
return true;
return false;
}
bool JsCode::preProcessInclude(ParsePoint& expr, bool once, GenObject* context)
{
if (m_depth > 5)
return gotError("Possible recursive include");
JsParser* parser = YOBJECT(JsParser,context);
if (!parser)
return false;
char c = skipComments(expr);
if (c == '"' || c == '\'') {
String str;
if (ExpEvaluator::getString(expr,str)) {
DDebug(this,DebugAll,"Found include '%s'",str.safe());
parser->adjustPath(str,true);
str.trimSpaces();
bool ok = !str.null();
if (ok) {
int idx = m_included.index(str);
if (!(once && (idx >= 0))) {
if (idx < 0) {
String* s = new JsCodeFile(str);
m_included.append(s);
idx = m_included.index(s);
}
// use the upper bits of line # for file index
unsigned int savedLine = expr.m_lineNo;
expr.m_lineNo = m_lineNo = ((idx + 1) << 24) | 1;
m_depth++;
ok = parser->parseFile(str,true);
m_depth--;
expr.m_lineNo = m_lineNo = savedLine;
}
}
return ok || gotError("Failed to include " + str);
}
return false;
}
return gotError("Expecting include file",expr);
}
bool JsCode::preProcessPragma(ParsePoint& expr, GenObject* context)
{
skipComments(expr);
int len = ExpEvaluator::getKeyword(expr);
if (len <= 0)
return gotError("Expecting pragma code",expr);
ParsePoint str = expr;
str += len;
char c = skipComments(str);
if (c == '"' || c == '\'') {
String val;
if (ExpEvaluator::getString(str,val)) {
m_pragmas.setParam(String(expr,len),val);
expr = str;
return true;
}
return gotError("Expecting pragma value",expr);
}
return gotError("Expecting pragma string",expr);
}
int JsCode::preProcess(ParsePoint& expr, GenObject* context)
{
int rval = -1;
for (;;) {
skipComments(expr);
JsOpcode opc = (JsOpcode)ExpEvaluator::getOperator(expr,s_preProc);
switch (opc) {
case OpcInclude:
case OpcRequire:
if (preProcessInclude(expr,(OpcRequire == opc),context)) {
if (rval < 0)
rval = 1;
else
rval++;
}
else
return -1;
break;
case OpcPragma:
if (!preProcessPragma(expr,context))
return -1;
break;
default:
return rval;
}
}
}
bool JsCode::getOneInstruction(ParsePoint& expr, GenObject* nested)
{
if (inError())
return false;
XDebug(this,DebugAll,"JsCode::getOneInstruction %p '%.30s'",nested,(const char*)expr);
const char* savedSeps = expr.m_searchedSeps;
unsigned int count = expr.m_count;
if (skipComments(expr) == '{') {
expr.m_searchedSeps = "}";
if (!getInstruction(expr,0,nested))
return false;
}
else {
expr.m_searchedSeps = ";}";
expr.m_count = 0;
if (!runCompile(expr,";}",nested))
return false;
if (skipComments(expr) == ';') {
expr.m_foundSep = ';';
expr++;
}
}
expr.m_searchedSeps = savedSeps;
if (!expr.m_searchedSeps || expr.m_count)
expr.m_foundSep = 0;
expr.m_count = count;
return true;
}
bool JsCode::getInstruction(ParsePoint& expr, char stop, GenObject* nested)
{
if (inError())
return false;
XDebug(this,DebugAll,"JsCode::getInstruction %p '%.1s' 'separators=%s' 'count=%u' '%.30s'",nested,&stop,expr.m_searchedSeps,
expr.m_count,(const char*)expr);
if (skipComments(expr) == '{') {
if (stop == ')')
return false;
expr++;
expr.m_count++;
for (;;) {
if (!runCompile(expr,'}',nested))
return false;
bool sep = false;
while (skipComments(expr) && getSeparator(expr,true))
sep = true;
if (*expr.m_expr == '}' || !sep)
break;
}
if (*expr != '}')
return gotError("Expecting '}'",expr);
expr++;
expr.m_foundSep = '}';
if (expr.m_count > 0)
expr.m_count--;
return true;
}
else if (*expr == ';') {
expr++;
expr.m_foundSep = ';';
return true;
}
expr.m_foundSep = 0;
ParsePoint saved = expr;
Opcode op = ExpEvaluator::getOperator(expr,s_instr);
switch ((int)op) {
case OpcNone:
return false;
case OpcThrow:
if (!runCompile(expr))
return false;
addOpcode(op);
break;
case OpcReturn:
{
int64_t pop = ExpOperation::nonInteger();
switch (skipComments(expr)) {
case ';':
case '}':
break;
case '{':
{
saved = expr;
JsObject* jso = parseObject(expr,false,0);
if (!jso)
return gotError("Expecting valid object",saved);
if (skipComments(expr) != ';') {
TelEngine::destruct(jso);
return gotError("Expecting ';'",expr);
}
addOpcode(new ExpWrapper(ExpEvaluator::OpcCopy,jso));
pop = 1;
}
break;
default:
if (!runCompile(expr,';'))
return false;
pop = 1;
if ((skipComments(expr) != ';') && (*expr != '}'))
return gotError("Expecting ';' or '}'",expr);
}
addOpcode(op,pop);
break;
}
case OpcIf:
return parseIf(expr,nested);
case OpcElse:
expr = saved;
return false;
case OpcSwitch:
return parseSwitch(expr,nested);
case OpcFor:
return parseFor(expr,nested);
case OpcWhile:
return parseWhile(expr,nested);
case OpcCase:
if (!ParseNested::parseInner(nested,OpcCase,expr)) {
expr.m_lineNo = saved.m_lineNo;
return gotError("case not inside switch",saved);
}
if (skipComments(expr) != ':')
return gotError("Expecting ':'",expr);
expr++;
break;
case OpcDefault:
if (!ParseNested::parseInner(nested,OpcDefault,expr)) {
expr.m_lineNo = saved.m_lineNo;
return gotError("Unexpected default instruction",saved);
}
if (skipComments(expr) != ':')
return gotError("Expecting ':'",expr);
expr++;
break;
case OpcBreak:
if (!ParseNested::parseInner(nested,OpcBreak,expr)) {
expr.m_lineNo = saved.m_lineNo;
return gotError("Unexpected break instruction",saved);
}
if (skipComments(expr) != ';')
return gotError("Expecting ';'",expr);
break;
case OpcCont:
if (!ParseNested::parseInner(nested,OpcCont,expr)) {
expr.m_lineNo = saved.m_lineNo;
return gotError("Unexpected continue instruction",saved);
}
if (skipComments(expr) != ';')
return gotError("Expecting ';'",expr);
break;
case OpcVar:
return parseVar(expr);
case OpcTry:
return parseTry(expr,nested);
case OpcFuncDef:
return parseFuncDef(expr,!nested);
}
return true;
}
class ParseLoop : public ParseNested
{
friend class JsCode;
public:
inline ParseLoop(JsCode* code, GenObject* nested, JsCode::JsOpcode oper, int64_t lblCont, int64_t lblBreak)
: ParseNested(code,nested,oper),
m_lblCont(lblCont), m_lblBreak(lblBreak)
{ }
protected:
virtual bool isMatch(JsCode::JsOpcode opcode)
{ return JsCode::OpcBreak == opcode || JsCode::OpcCont == opcode; }
private:
int64_t m_lblCont;
int64_t m_lblBreak;
};
class ParseSwitch : public ParseNested
{
friend class JsCode;
public:
enum SwitchState {
Before,
InCase,
InDefault
};
inline ParseSwitch(JsCode* code, GenObject* nested, long int lblBreak)
: ParseNested(code,nested,JsCode::OpcSwitch),
m_lblBreak(lblBreak), m_lblDefault(0), m_state(Before)
{ }
inline SwitchState state() const
{ return m_state; }
protected:
virtual bool isMatch(JsCode::JsOpcode opcode)
{ return JsCode::OpcCase == opcode || JsCode::OpcDefault == opcode ||
JsCode::OpcBreak == opcode; }
private:
int64_t m_lblBreak;
int64_t m_lblDefault;
SwitchState m_state;
ObjList m_cases;
};
// Parse keywords inner to specific instructions
bool JsCode::parseInner(ParsePoint& expr, JsOpcode opcode, ParseNested* nested)
{
switch (*nested) {
case OpcFor:
case OpcWhile:
{
ParseLoop* block = static_cast<ParseLoop*>(nested);
switch (opcode) {
case OpcBreak:
XDebug(this,DebugAll,"Parsing loop:break '%.30s'",(const char*)expr);
addOpcode((Opcode)OpcJump,block->m_lblBreak);
break;
case OpcCont:
XDebug(this,DebugAll,"Parsing loop:continue '%.30s'",(const char*)expr);
addOpcode((Opcode)OpcJump,block->m_lblCont);
break;
default:
return false;
}
}
break;
case OpcSwitch:
{
ParseSwitch* block = static_cast<ParseSwitch*>(nested);
switch (opcode) {
case OpcCase:
if (block->state() == ParseSwitch::InDefault)
return gotError("Encountered case after default",expr);
if (!getSimple(expr,true))
return gotError("Expecting case constant",expr);
XDebug(this,DebugAll,"Parsing switch:case: '%.30s'",(const char*)expr);
block->m_state = ParseSwitch::InCase;
block->m_cases.append(popOpcode());
addOpcode(OpcLabel,(int64_t)++m_label);
block->m_cases.append(new ExpOperation((Opcode)OpcJumpTrue,0,m_label));
break;
case OpcDefault:
if (block->state() == ParseSwitch::InDefault)
return gotError("Duplicate default case",expr);
XDebug(this,DebugAll,"Parsing switch:default: '%.30s'",(const char*)expr);
block->m_state = ParseSwitch::InDefault;
block->m_lblDefault = ++m_label;
addOpcode(OpcLabel,block->m_lblDefault);
break;
case OpcBreak:
XDebug(this,DebugAll,"Parsing switch:break '%.30s'",(const char*)expr);
addOpcode((Opcode)OpcJump,static_cast<ParseSwitch*>(nested)->m_lblBreak);
break;
default:
return false;
}
}
break;
default:
return false;
}
return true;
}
bool JsCode::parseIf(ParsePoint& expr, GenObject* nested)
{
XDebug(this,DebugAll,"JsCode::parseIf() '%.30s'",(const char*)expr);
if (skipComments(expr) != '(')
return gotError("Expecting '('",expr);
if (!runCompile(++expr,')'))
return false;
if (skipComments(expr) != ')')
return gotError("Expecting ')'",expr);
ExpOperation* cond = addOpcode((Opcode)OpcJumpFalse,(int64_t)++m_label);
expr++;
if (!getOneInstruction(expr,nested))
return false;
skipComments(expr);
ParsePoint save = expr;
if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcElse) {
ExpOperation* jump = addOpcode((Opcode)OpcJump,(int64_t)++m_label);
addOpcode(OpcLabel,cond->number());
if (!getOneInstruction(expr,nested))
return false;
addOpcode(OpcLabel,jump->number());
}
else {
expr = save;
addOpcode(OpcLabel,cond->number());
}
return true;
}
bool JsCode::parseSwitch(ParsePoint& expr, GenObject* nested)
{
if (skipComments(expr) != '(')
return gotError("Expecting '('",expr);
addOpcode((Opcode)OpcBegin);
if (!runCompile(++expr,')'))
return false;
if (skipComments(expr) != ')')
return gotError("Expecting ')'",expr);
if (skipComments(++expr) != '{')
return gotError("Expecting '{'",expr);
expr++;
const char* savedSeps = expr.m_searchedSeps;
expr.m_searchedSeps = "";
ExpOperation* jump = addOpcode((Opcode)OpcJump,(int64_t)++m_label);
ParseSwitch parseStack(this,nested,++m_label);
for (;;) {
if (!runCompile(expr,'}',parseStack))
return false;
bool sep = false;
while (skipComments(expr) && getSeparator(expr,true))
sep = true;
if (*expr == '}' || !sep)
break;
}
if (*expr != '}')
return gotError("Expecting '}'",expr);
expr++;
expr.m_searchedSeps = savedSeps;
if (!expr.m_searchedSeps || expr.m_count)
expr.m_foundSep = 0;
// implicit break at end
addOpcode((Opcode)OpcJump,parseStack.m_lblBreak);
addOpcode(OpcLabel,jump->number());
while (ExpOperation* c = static_cast<ExpOperation*>(parseStack.m_cases.remove(false))) {
ExpOperation* j = static_cast<ExpOperation*>(parseStack.m_cases.remove(false));
if (!j)
break;
addOpcode(c,c->lineNumber());
addOpcode((Opcode)OpcCase);
addOpcode(j,c->lineNumber());
}
// if no case matched drop the expression
addOpcode(OpcDrop);
if (parseStack.m_lblDefault)
addOpcode((Opcode)OpcJump,parseStack.m_lblDefault);
addOpcode(OpcLabel,parseStack.m_lblBreak);
addOpcode((Opcode)OpcFlush);
return true;
}
bool JsCode::parseFor(ParsePoint& expr, GenObject* nested)
{
if (skipComments(expr) != '(')
return gotError("Expecting '('",expr);
addOpcode((Opcode)OpcBegin);
if ((skipComments(++expr) != ';') && !runCompile(expr,')'))
return false;
bool iter = false;
int64_t cont = 0;
int64_t jump = ++m_label;
int64_t body = ++m_label;
// parse initializer
if (skipComments(expr) == ';') {
int64_t check = body;
if (skipComments(++expr) != ';') {
check = ++m_label;
addOpcode(OpcLabel,check);
addOpcode((Opcode)OpcBegin);
// parse condition
if (!runCompile(expr))
return false;
if (skipComments(expr) != ';')
return gotError("Expecting ';'",expr);
addOpcode((Opcode)OpcEnd);
addOpcode((Opcode)OpcJumpFalse,jump);
}
addOpcode((Opcode)OpcJump,body);
if (skipComments(++expr) == ')')
cont = check;
else {
cont = ++m_label;
addOpcode(OpcLabel,cont);
addOpcode((Opcode)OpcBegin);
// parse increment
if (!runCompile(expr,')'))
return false;
addOpcode((Opcode)OpcFlush);
addOpcode((Opcode)OpcJump,check);
}
}
else {
iter = true;
cont = ++m_label;
addOpcode(OpcLabel,cont);
addOpcode((Opcode)OpcNext);
addOpcode((Opcode)OpcJumpFalse,jump);
}
if (skipComments(expr) != ')')
return gotError("Expecting ')'",expr);
ParseLoop parseStack(this,nested,OpcFor,cont,jump);
addOpcode(OpcLabel,body);
if (!iter) {
// non-iterative version needs to avoid stack build-up
addOpcode((Opcode)OpcFlush);
addOpcode((Opcode)OpcBegin);
}
if (!getOneInstruction(++expr,parseStack))
return false;
addOpcode((Opcode)OpcJump,cont);
addOpcode(OpcLabel,jump);
addOpcode((Opcode)OpcFlush);
return true;
}
bool JsCode::parseWhile(ParsePoint& expr, GenObject* nested)
{
if (skipComments(expr) != '(')
return gotError("Expecting '('",expr);
addOpcode((Opcode)OpcBegin);
int64_t cont = ++m_label;
addOpcode(OpcLabel,cont);
if (!runCompile(++expr,')'))
return false;
if (skipComments(expr) != ')')
return gotError("Expecting ')'",expr);
int64_t jump = ++m_label;
addOpcode((Opcode)OpcJumpFalse,jump);
ParseLoop parseStack(this,nested,OpcWhile,cont,jump);
addOpcode((Opcode)OpcFlush);
addOpcode((Opcode)OpcBegin);
if (!getOneInstruction(++expr,parseStack))
return false;
addOpcode((Opcode)OpcJump,cont);
addOpcode(OpcLabel,jump);
addOpcode((Opcode)OpcFlush);
return true;
}
bool JsCode::parseVar(ParsePoint& expr)
{
if (inError())
return false;
XDebug(this,DebugAll,"parseVar '%.30s'",(const char*)expr);
skipComments(expr);
int len = ExpEvaluator::getKeyword(expr);
if (len <= 0 || expr[len] == '(')
return gotError("Expecting variable name",expr);
String str(expr,len);
if (str.toInteger(s_instr,-1) >= 0 || str.toInteger(s_constants,-1) >= 0)
return gotError("Not a valid variable name",expr);
DDebug(this,DebugAll,"Found variable '%s'",str.safe());
addOpcode((Opcode)OpcVar,str);
return true;
}
bool JsCode::parseTry(ParsePoint& expr, GenObject* nested)
{
addOpcode((Opcode)OpcTry);
ParseNested parseStack(this,nested,OpcTry);
if (!runCompile(expr,(const char*)0,parseStack))
return false;
skipComments(expr);
if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcCatch) {
if (skipComments(expr) != '(')
return gotError("Expecting '('",expr);
if (!getField(++expr))
return gotError("Expecting formal argument",expr);
if (skipComments(expr) != ')')
return gotError("Expecting ')'",expr);
if (!runCompile(++expr))
return false;
}
skipComments(expr);
if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcFinally) {
if (!runCompile(expr))
return false;
}
return true;
}
bool JsCode::parseFuncDef(ParsePoint& expr, bool publish)
{
XDebug(this,DebugAll,"JsCode::parseFuncDef '%.30s'",(const char*)expr);
skipComments(expr);
int len = getKeyword(expr);
String name;
if (len > 0) {
name.assign(expr,len);
expr += len;
}
if (skipComments(expr) != '(')
return gotError("Expecting '('",expr);
expr++;
ObjList args;
while (skipComments(expr) != ')') {
len = getKeyword(expr);
if (len > 0) {
args.append(new String(expr,len));
expr += len;
}
else
return gotError("Expecting formal argument",expr);
if ((skipComments(expr) == ',') && (skipComments(++expr) == ')'))
return gotError("Expecting formal argument",expr);
}
if (skipComments(++expr) != '{')
return gotError("Expecting '{'",expr);
expr++;
ExpOperation* jump = addOpcode((Opcode)OpcJump,(int64_t)++m_label);
ExpOperation* lbl = addOpcode(OpcLabel,(int64_t)++m_label,true);
for (;;) {
if (!runCompile(expr,'}'))
return false;
bool sep = false;
while (skipComments(expr) && getSeparator(expr,true))
sep = true;
if (*expr == '}' || !sep)
break;
}
if (*expr != '}')
return gotError("Expecting '}'",expr);
expr++;
// Add the implicit "return undefined" at end of function
addOpcode((Opcode)OpcReturn);
addOpcode(OpcLabel,jump->number());
JsFunction* obj = new JsFunction(0,name,lineNumber(),&args,(long int)lbl->number(),this);
ExpWrapper* exp = new ExpWrapper(obj,name);
exp->lineNumber(lineNumber());
addOpcode(exp);
if (publish && name && obj->ref()) {
exp = new ExpWrapper(obj,name);
exp->lineNumber(lineNumber());
m_globals.append(exp);
}
return true;
}
ExpEvaluator::Opcode JsCode::getOperator(ParsePoint& expr)
{
if (inError())
return OpcNone;
XDebug(this,DebugAll,"JsCode::getOperator line=0x%X '%.30s'",lineNumber(),(const char*)expr);
skipComments(expr);
Opcode op = ExpEvaluator::getOperator(expr,s_operators);
if (OpcNone != op)
return op;
return ExpEvaluator::getOperator(expr);
}
ExpEvaluator::Opcode JsCode::getUnaryOperator(ParsePoint& expr)
{
if (inError())
return OpcNone;
XDebug(this,DebugAll,"JsCode::getUnaryOperator line=0x%X '%.30s'",lineNumber(),(const char*)expr);
skipComments(expr);
Opcode op = ExpEvaluator::getOperator(expr,s_unaryOps);
if (OpcNone != op)
return op;
return ExpEvaluator::getUnaryOperator(expr);
}
ExpEvaluator::Opcode JsCode::getPostfixOperator(ParsePoint& expr, int precedence)
{
if (inError())
return OpcNone;
XDebug(this,DebugAll,"JsCode::getPostfixOperator line=0x%X '%.30s'",lineNumber(),(const char*)expr);
if (skipComments(expr) == '[') {
// The Indexing operator has maximum priority!
// No need to check it.
if (!runCompile(++expr,']'))
return OpcNone;
if (skipComments(expr) != ']') {
gotError("Expecting ']'",expr);
return OpcNone;
}
expr++;
return (Opcode)OpcIndex;
}
skipComments(expr);
ParsePoint save = expr;
Opcode op = ExpEvaluator::getOperator(expr,s_postfixOps);
if (OpcNone != op) {
if (getPrecedence(op) >= precedence)
return op;
expr = save;
return OpcNone;
}
return ExpEvaluator::getPostfixOperator(expr,precedence);
}
const char* JsCode::getOperator(Opcode oper) const
{
if ((int)oper == (int)OpcIndex)
return "[]";
const char* tmp = ExpEvaluator::getOperator(oper);
if (!tmp) {
tmp = lookup(oper,s_operators);
if (!tmp) {
tmp = lookup(oper,s_unaryOps);
if (!tmp) {
tmp = lookup(oper,s_postfixOps);
if (!tmp) {
tmp = lookup(oper,s_instr);
if (!tmp)
tmp = lookup(oper,s_internals);
}
}
}
}
return tmp;
}
int JsCode::getPrecedence(ExpEvaluator::Opcode oper) const
{
switch ((int)oper) {
case OpcEqIdentity:
case OpcNeIdentity:
return 110;
case OpcDelete:
case OpcNew:
case OpcTypeof:
return 170;
case OpcFieldOf:
case OpcIndex:
return 200;
default:
return ExpEvaluator::getPrecedence(oper);
}
}
bool JsCode::getSeparator(ParsePoint& expr, bool remove)
{
if (inError())
return false;
switch (skipComments(expr)) {
case ';':
expr.m_foundSep =';';
case ']':
if (remove)
expr++;
return true;
}
return ExpEvaluator::getSeparator(expr,remove);
}
bool JsCode::getSimple(ParsePoint& expr, bool constOnly)
{
return parseSimple(expr,constOnly);
}
bool JsCode::parseSimple(ParsePoint& expr, bool constOnly, ScriptMutex* mtx)
{
if (inError())
return false;
XDebug(this,DebugAll,"JsCode::parseSimple(%s) '%.30s'",String::boolText(constOnly),(const char*)expr);
skipComments(expr);
ParsePoint save = expr;
switch ((JsOpcode)ExpEvaluator::getOperator(expr,s_constants)) {
case OpcFalse:
addOpcode(false);
return true;
case OpcTrue:
addOpcode(true);
return true;
case OpcNull:
addOpcode(s_null.ExpOperation::clone());
return true;
case OpcUndefined:
addOpcode(new ExpWrapper(0,"undefined"));
return true;
case OpcFuncDef:
if (constOnly) {
expr = save;
return false;
}
return parseFuncDef(expr,false);
default:
break;
}
JsObject* jso = parseArray(expr,constOnly,mtx);
if (!jso)
jso = parseObject(expr,constOnly,mtx);
if (!jso)
return ExpEvaluator::getSimple(expr,constOnly);
addOpcode(new ExpWrapper(ExpEvaluator::OpcCopy,jso));
return true;
}
// Parse an inline Javascript Array: [ item1, item2, ... ]
JsObject* JsCode::parseArray(ParsePoint& expr, bool constOnly, ScriptMutex* mtx)
{
if (skipComments(expr) != '[')
return 0;
expr++;
JsArray* jsa = new JsArray(mtx,"[object Array]",lineNumber());
for (bool first = true; ; first = false) {
if (skipComments(expr) == ']') {
expr++;
break;
}
if (first) {
if (*expr == ',') {
ParsePoint next = expr;
next++;
// A construct like [,] creates an empty Array
if (skipComments(next) == ']') {
next++;
expr = next;
break;
}
}
}
else {
if (*expr != ',') {
TelEngine::destruct(jsa);
break;
}
expr++;
}
// Swallow the single comma allowed after last item
if (skipComments(expr) == ']') {
expr++;
break;
}
// Successive commas insert an undefined between them
if (skipComments(expr) == ',') {
jsa->push(new ExpWrapper(0,"undefined"));
continue;
}
bool ok = constOnly ? parseSimple(expr,true,mtx) : getOperand(expr,false);
if (!ok) {
TelEngine::destruct(jsa);
break;
}
ExpOperation* oper = popOpcode();
if (oper && oper->opcode() == OpcField)
oper->assign(oper->name());
jsa->push(oper);
}
return jsa;
}
// Parse an inline Javascript Object: { prop1: value1, "prop 2": value2, ... }
JsObject* JsCode::parseObject(ParsePoint& expr, bool constOnly, ScriptMutex* mtx)
{
if (skipComments(expr) != '{')
return 0;
expr++;
JsObject* jso = new JsObject(mtx,"[object Object]",lineNumber());
for (bool first = true; ; first = false) {
if (skipComments(expr) == '}') {
expr++;
break;
}
if (!first) {
if (*expr != ',') {
TelEngine::destruct(jso);
break;
}
expr++;
// A single comma is allowed after last property
if (skipComments(expr) == '}') {
expr++;
break;
}
}
char c = skipComments(expr);
String name;
int len = getKeyword(expr);
if (len > 0) {
name.assign(expr,len);
expr += len;
}
else if ((c != '"' && c != '\'') || !ExpEvaluator::getString(expr,name)) {
TelEngine::destruct(jso);
break;
}
if (skipComments(expr) != ':') {
TelEngine::destruct(jso);
break;
}
expr++;
bool ok = constOnly ? parseSimple(expr,true,mtx) : getOperand(expr,false);
if (!ok) {
TelEngine::destruct(jso);
break;
}
ExpOperation* op = popOpcode();
if (!op) {
TelEngine::destruct(jso);
break;
}
if (op->opcode() == OpcField)
op->assign(op->name());
const_cast<String&>(op->name()) = name;
jso->params().setParam(op);
}
return jso;
}
bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const
{
DDebug(this,DebugAll,"JsCode::runOperation(%p,%u,%p) %s line=0x%08x",&stack,oper.opcode(),context,
getOperator(oper.opcode()),oper.lineNumber());
JsRunner* sr = static_cast<JsRunner*>(context);
if (sr && sr->tracing())
sr->tracePrep(oper);
switch ((int)oper.opcode()) {
case OpcEqIdentity:
case OpcNeIdentity:
{
ExpOperation* op2 = popValue(stack,context);
ExpOperation* op1 = popValue(stack,context);
if (!op1 || !op2) {
TelEngine::destruct(op1);
TelEngine::destruct(op2);
return gotError("ExpEvaluator stack underflow",oper.lineNumber());
}
bool eq = (op1->opcode() == op2->opcode());
if (eq) {
ExpWrapper* w1 = YOBJECT(ExpWrapper,op1);
ExpWrapper* w2 = YOBJECT(ExpWrapper,op2);
if (w1 || w2)
eq = w1 && w2 && w1->object() == w2->object();
else
eq = (op1->number() == op2->number()) && (*op1 == *op2);
}
TelEngine::destruct(op1);
TelEngine::destruct(op2);
if ((JsOpcode)oper.opcode() == OpcNeIdentity)
eq = !eq;
pushOne(stack,new ExpOperation(eq));
}
break;
case OpcBegin:
pushOne(stack,new ExpOperation((Opcode)OpcBegin));
break;
case OpcEnd:
case OpcFlush:
{
ExpOperation* op = 0;
if ((JsOpcode)oper.opcode() == OpcEnd) {
op = popOne(stack);
if (op && (op->opcode() == (Opcode)OpcBegin)) {
TelEngine::destruct(op);
break;
}
}
bool done = false;
ExpOperation* o;
while ((o = static_cast<ExpOperation*>(stack.remove(false)))) {
done = (o->opcode() == (Opcode)OpcBegin);
TelEngine::destruct(o);
if (done)
break;
}
if (!done)
return gotError("ExpEvaluator stack underflow",oper.lineNumber());
if (op)
pushOne(stack,op);
}
break;
case OpcIndex:
{
ExpOperation* op2 = popValue(stack,context);
ExpOperation* op1 = popOne(stack);
if (!op1 || !op2) {
TelEngine::destruct(op1);
TelEngine::destruct(op2);
return gotError("Stack underflow",oper.lineNumber());
}
if (op1->opcode() != OpcField) {
ScriptContext* ctx = YOBJECT(ScriptContext,op1);
if (ctx) {
ExpOperation fld(OpcField,*op2);
if (ctx->runField(stack,fld,context)) {
TelEngine::destruct(op1);
TelEngine::destruct(op2);
break;
}
}
TelEngine::destruct(op1);
TelEngine::destruct(op2);
return gotError("Expecting field name",oper.lineNumber());
}
pushOne(stack,new ExpOperation(OpcField,op1->name() + "." + *op2));
TelEngine::destruct(op1);
TelEngine::destruct(op2);
}
break;
case OpcFieldOf:
{
ExpOperation* op2 = popOne(stack);
ExpOperation* op1 = popOne(stack);
if (!op1 || !op2) {
TelEngine::destruct(op1);
TelEngine::destruct(op2);
return gotError("Stack underflow",oper.lineNumber());
}
if (op2->opcode() != OpcField) {
TelEngine::destruct(op1);
TelEngine::destruct(op2);
return gotError("Expecting field names",oper.lineNumber());
}
if (op1->opcode() != OpcField) {
// try to obtain an object on which to run the field
ScriptContext* ctx = YOBJECT(ScriptContext,op1);
if (ctx && ctx->runField(stack,*op2,context)) {
TelEngine::destruct(op1);
TelEngine::destruct(op2);
break;
}
else {
// op1 is not an object, it's a string
JsContext* jsCtx = 0;
if (sr)
jsCtx = static_cast<JsContext*>(sr->context());
if (jsCtx && jsCtx->runStringField(op1,op2->name(),stack,*op2,context)) {
TelEngine::destruct(op1);
TelEngine::destruct(op2);
break;
}
}
TelEngine::destruct(op1);
TelEngine::destruct(op2);
return gotError("Expecting field names",oper.lineNumber());
}
pushOne(stack,new ExpOperation(OpcField,op1->name() + "." + op2->name()));
TelEngine::destruct(op1);
TelEngine::destruct(op2);
}
break;
case OpcTypeof:
{
ExpOperation* op = popValue(stack,context);
if (!op)
return gotError("Stack underflow",oper.lineNumber());
pushOne(stack,new ExpOperation(op->typeOf()));
TelEngine::destruct(op);
}
break;
case OpcVar:
{
for (ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
JsObject* jso = YOBJECT(JsObject,l->get());
if (jso && jso->toString() == YSTRING("()")) {
if (!jso->hasField(stack,oper.name(),context)) {
XDebug(this,DebugInfo,"Creating variable '%s' in scope",
oper.name().c_str());
jso->params().setParam(new ExpWrapper(0,oper.name()));
}
break;
}
}
}
break;
case OpcNew:
{
ExpOperation* op = popOne(stack);
if (!op)
return gotError("Stack underflow",oper.lineNumber());
switch (op->opcode()) {
case OpcField:
break;
case OpcPush:
{
ExpWrapper* w = YOBJECT(ExpWrapper,op);
if (w && w->object()) {
pushOne(stack,op);
op = 0;
break;
}
}
// fall through
default:
TelEngine::destruct(op);
return gotError("Expecting class name",oper.lineNumber());
}
if (!op)
break;
ExpFunction ctr(op->name(),(long int)op->number());
ctr.lineNumber(oper.lineNumber());
TelEngine::destruct(op);
if (!runOperation(stack,ctr,context))
return false;
}
break;
case OpcThrow:
{
ExpOperation* op = popOne(stack);
if (!op)
return gotError("Stack underflow",oper.lineNumber());
bool ok = false;
while (ExpOperation* drop = popAny(stack)) {
JsOpcode c = (JsOpcode)drop->opcode();
TelEngine::destruct(drop);
if (c == OpcTry) {
ok = true;
break;
}
}
if (!ok)
return gotError("Uncaught exception: " + *op,oper.lineNumber());
pushOne(stack,op);
}
break;
case OpcReturn:
{
ExpOperation* op = oper.valInteger() ? popValue(stack,context) : new ExpWrapper(0,"undefined");
ExpOperation* thisObj = 0;
bool ok = false;
while (ExpOperation* drop = popAny(stack)) {
ok = drop->barrier() && (drop->opcode() == OpcFunc);
long int lbl = (long int)drop->number();
if (ok && (lbl < -1)) {
lbl = -lbl;
XDebug(this,DebugInfo,"Returning this=%p from constructor '%s'",
thisObj,drop->name().c_str());
if (thisObj) {
TelEngine::destruct(op);
op = thisObj;
thisObj = 0;
}
}
if (drop->opcode() == OpcPush) {
ExpWrapper* wrap = YOBJECT(ExpWrapper,drop);
if (wrap && wrap->name() == YSTRING("()")) {
JsObject* jso = YOBJECT(JsObject,wrap->object());
if (jso) {
wrap = YOBJECT(ExpWrapper,jso->params().getParam(YSTRING("this")));
if (wrap) {
TelEngine::destruct(thisObj);
thisObj = wrap->clone(wrap->name());
}
}
}
}
TelEngine::destruct(drop);
if (ok) {
ok = jumpAbsolute(lbl,context);
break;
}
}
TelEngine::destruct(thisObj);
if (!ok) {
TelEngine::destruct(op);
return gotError("Return outside function call",oper.lineNumber());
}
if (op)
pushOne(stack,op);
if (sr && sr->tracing())
sr->traceReturn();
}
break;
case OpcIn:
case OpcOf:
{
ExpOperation* obj = popOne(stack);
ExpOperation* fld = popOne(stack);
String name;
if (obj && obj->opcode() == OpcField) {
name = obj->name();
bool ok = runField(stack,*obj,context);
TelEngine::destruct(obj);
obj = ok ? popOne(stack) : 0;
}
if (!fld || !obj) {
TelEngine::destruct(fld);
TelEngine::destruct(obj);
return gotError("Stack underflow",oper.lineNumber());
}
if (fld->opcode() != OpcField) {
TelEngine::destruct(fld);
TelEngine::destruct(obj);
return gotError("Expecting field name",oper.lineNumber());
}
bool isOf = ((JsOpcode)oper.opcode() == OpcOf);
JsIterator* iter = 0;
JsObject* jso = YOBJECT(JsObject,obj);
if (jso)
iter = new JsIterator(*fld,jso);
else {
NamedList* lst = YOBJECT(NamedList,obj);
if (lst)
iter = new JsIterator(*fld,lst);
}
ExpWrapper* wrap = 0;
if (iter) {
if (isOf)
iter->name(name ? name : obj->name());
wrap = new ExpWrapper(iter);
#ifdef DEBUG
*wrap << fld->name() << (isOf ? " of " : " in ") << obj->name();
Debug(this,DebugInfo,"Created iterator: '%s'",wrap->c_str());
#endif
}
TelEngine::destruct(fld);
TelEngine::destruct(obj);
if (wrap)
pushOne(stack,wrap);
else
return gotError("Expecting iterable object",oper.lineNumber());
}
break;
case OpcNext:
{
JsIterator* iter = 0;
ExpOperation* op;
while (!iter) {
op = popValue(stack,context);
if (!op)
return gotError("Stack underflow",oper.lineNumber());
iter = YOBJECT(JsIterator,op);
if (!iter)
TelEngine::destruct(op);
}
bool ok = false;
String* n = iter->get();
if (n) {
XDebug(DebugInfo,"Iterator got item: '%s'",n->c_str());
ExpOperation assign(OpcAssign);
assign.lineNumber(oper.lineNumber());
pushOne(stack,iter->field().clone());
if (iter->name())
pushOne(stack,new ExpOperation(OpcField,iter->name() + "." + *n));
else
pushOne(stack,new ExpOperation(*n));
TelEngine::destruct(n);
ok = runOperation(stack,assign,context);
}
if (ok) {
// assign pushes the value back on stack
TelEngine::destruct(popOne(stack));
pushOne(stack,op);
}
else
TelEngine::destruct(op);
pushOne(stack,new ExpOperation(ok));
}
break;
case OpcCase:
{
ExpOperation* cons = popValue(stack,context);
ExpOperation* expr = popValue(stack,context);
if (!cons || !expr) {
TelEngine::destruct(cons);
TelEngine::destruct(expr);
return gotError("ExpEvaluator stack underflow",oper.lineNumber());
}
bool eq = false;
JsRegExp* rex = YOBJECT(JsRegExp,cons);
if (rex)
eq = rex->regexp().matches(*expr);
else if (expr->opcode() == cons->opcode()) {
ExpWrapper* w1 = YOBJECT(ExpWrapper,expr);
ExpWrapper* w2 = YOBJECT(ExpWrapper,cons);
if (w1 || w2)
eq = w1 && w2 && w1->object() == w2->object();
else
eq = (expr->number() == cons->number()) && (*expr == *cons);
}
if (!eq) {
// put expression back on stack, we'll need it later
pushOne(stack,expr);
expr = 0;
}
TelEngine::destruct(cons);
TelEngine::destruct(expr);
pushOne(stack,new ExpOperation(eq));
}
break;
case OpcJumpTrue:
case OpcJumpFalse:
case OpcJRelTrue:
case OpcJRelFalse:
{
ExpOperation* op = popValue(stack,context);
if (!op)
return gotError("Stack underflow",oper.lineNumber());
bool val = op->valBoolean();
TelEngine::destruct(op);
switch ((JsOpcode)oper.opcode()) {
case OpcJumpTrue:
case OpcJRelTrue:
val = !val;
default:
break;
}
if (val)
break;
}
// fall through
case OpcJump:
case OpcJRel:
switch ((JsOpcode)oper.opcode()) {
case OpcJump:
case OpcJumpTrue:
case OpcJumpFalse:
if (!jumpToLabel((long int)oper.number(),context))
return gotError("Label not found",oper.lineNumber());
break;
case OpcJRel:
case OpcJRelTrue:
case OpcJRelFalse:
if (!jumpRelative((long int)oper.number(),context))
return gotError("Relative jump failed",oper.lineNumber());
break;
default:
return gotError("Internal error",oper.lineNumber());
}
break;
case OpcDelete:
{
ExpOperation* op = popOne(stack);
if (!op)
return gotError("Stack underflow",oper.lineNumber());
if (op->opcode() != OpcField) {
pushOne(stack,new ExpOperation(true));
TelEngine::destruct(op);
break;
}
JsObject* obj = 0;
String name = op->name();
TelEngine::destruct(op);
JsContext* ctx = YOBJECT(JsContext,sr->context());
if (ctx)
obj = YOBJECT(JsObject,ctx->resolve(stack,name,context));
bool ret = false;
if (obj && (!obj->frozen() || !obj->hasField(stack,name,context))
&& obj->toString() != YSTRING("()")) {
obj->clearField(name);
ret = true;
}
DDebug(DebugAll,"Deleted '%s' : %s",name.c_str(),String::boolText(ret));
pushOne(stack,new ExpOperation(ret));
}
break;
case OpcCopy:
if (!ExpEvaluator::runOperation(stack,oper,context))
return false;
resolveObjectParams(YOBJECT(JsObject,stack.get()),stack,context);
break;
default:
if (!ExpEvaluator::runOperation(stack,oper,context))
return false;
}
if (sr && sr->tracing())
sr->tracePost(oper);
return true;
}
void JsCode::resolveObjectParams(JsObject* object, ObjList& stack, GenObject* context, JsContext* ctxt, JsObject* objProto, JsArray* arrayProto) const
{
DDebug(this,DebugAll,"JsCode::resolveObjectParams(%p,%p,%p,%p,%p,%p)",
object,&stack,context,ctxt,objProto,arrayProto);
for (unsigned int i = 0;i < object->params().length();i++) {
String* param = object->params().getParam(i);
JsObject* tmpObj = YOBJECT(JsObject,param);
if (tmpObj) {
resolveObjectParams(tmpObj,stack,context,ctxt,objProto,arrayProto);
continue;
}
ExpOperation* op = YOBJECT(ExpOperation,param);
if (!op || op->opcode() != OpcField)
continue;
String name = *op;
JsObject* jsobj = YOBJECT(JsObject,ctxt->resolve(stack,name,context));
if (!jsobj) {
object->params().setParam(new ExpWrapper(0,op->name()));
continue;
}
NamedString* ns = jsobj->getField(stack,name,context);
if (!ns) {
object->params().setParam(new ExpWrapper(0,op->name()));
continue;
}
ExpOperation* objOper = YOBJECT(ExpOperation,ns);
NamedString* temp = 0;
if (objOper)
temp = objOper->clone(op->name());
else
temp = new NamedString(op->name(),*ns);
object->params().setParam(temp);
}
if (object->frozen() || object->params().getParam(JsObject::protoName()))
return;
JsArray* arr = YOBJECT(JsArray,object);
if (arr) {
if (arrayProto && arrayProto->ref())
object->params().addParam(new ExpWrapper(arrayProto,JsObject::protoName()));
}
else if (objProto && objProto->ref())
object->params().addParam(new ExpWrapper(objProto,JsObject::protoName()));
}
void JsCode::resolveObjectParams(JsObject* object, ObjList& stack, GenObject* context) const
{
if (!(object && context))
return;
ScriptRun* sr = static_cast<ScriptRun*>(context);
JsContext* ctx = YOBJECT(JsContext,sr->context());
if (!ctx)
return;
JsFunction* objCtr = 0;
// check first for regexp built /expr/ syntax
JsRegExp* reg = YOBJECT(JsRegExp,object);
if (reg) {
objCtr = YOBJECT(JsFunction,ctx->params().getParam(YSTRING("RegExp")));
if (objCtr) {
JsRegExp* regexpProto = YOBJECT(JsRegExp,objCtr->params().getParam(YSTRING("prototype")));
if (regexpProto && regexpProto->ref())
object->params().addParam(new ExpWrapper(regexpProto,JsObject::protoName()));
}
return;
}
JsObject* objProto = 0;
objCtr = YOBJECT(JsFunction,ctx->params().getParam(YSTRING("Object")));
if (objCtr)
objProto = YOBJECT(JsObject,objCtr->params().getParam(YSTRING("prototype")));
JsArray* arrayProto = 0;
objCtr = YOBJECT(JsFunction,ctx->params().getParam(YSTRING("Array")));
if (objCtr)
arrayProto = YOBJECT(JsArray,objCtr->params().getParam(YSTRING("prototype")));
resolveObjectParams(object,stack,context,ctx,objProto,arrayProto);
}
bool JsCode::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const
{
DDebug(this,DebugAll,"JsCode::runFunction(%p,'%s' " FMT64 ", %p) ext=%p",
&stack,oper.name().c_str(),oper.number(),context,extender());
if (context) {
ScriptRun* sr = static_cast<ScriptRun*>(context);
if (sr->context()->runFunction(stack,oper,context))
return true;
}
return extender() && extender()->runFunction(stack,oper,context);
}
bool JsCode::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) const
{
DDebug(this,DebugAll,"JsCode::runField(%p,'%s',%p) ext=%p",
&stack,oper.name().c_str(),context,extender());
if (context) {
ScriptRun* sr = static_cast<ScriptRun*>(context);
if (sr->context()->runField(stack,oper,context))
return true;
}
return extender() && extender()->runField(stack,oper,context);
}
bool JsCode::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) const
{
DDebug(this,DebugAll,"JsCode::runAssign('%s'='%s',%p) ext=%p",
oper.name().c_str(),oper.c_str(),context,extender());
if (context) {
ScriptRun* sr = static_cast<ScriptRun*>(context);
if (sr->context()->runAssign(stack,oper,context))
return true;
}
return extender() && extender()->runAssign(stack,oper,context);
}
bool JsCode::evalList(ObjList& stack, GenObject* context) const
{
XDebug(this,DebugInfo,"JsCode::evalList(%p,%p)",&stack,context);
JsRunner* runner = static_cast<JsRunner*>(context);
const ObjList* (& opcode) = runner->m_opcode;
while (opcode) {
const ExpOperation* o = static_cast<const ExpOperation*>(opcode->get());
opcode = opcode->skipNext();
if (!runOperation(stack,*o,context))
return false;
if (runner->m_paused)
break;
}
return true;
}
bool JsCode::evalVector(ObjList& stack, GenObject* context) const
{
XDebug(this,DebugInfo,"JsCode::evalVector(%p,%p)",&stack,context);
JsRunner* runner = static_cast<JsRunner*>(context);
unsigned int& index = runner->m_index;
while (index < m_linked.length()) {
const ExpOperation* o = static_cast<const ExpOperation*>(m_linked[index++]);
if (o && !runOperation(stack,*o,context))
return false;
if (runner->m_paused)
break;
}
return true;
}
bool JsCode::jumpToLabel(long int label, GenObject* context) const
{
if (!context)
return false;
JsRunner* runner = static_cast<JsRunner*>(context);
if (m_opcodes.skipNull()) {
for (ObjList* l = m_opcodes.skipNull(); l; l = l->skipNext()) {
const ExpOperation* o = static_cast<const ExpOperation*>(l->get());
if (o->opcode() == OpcLabel && o->number() == label) {
runner->m_opcode = l->skipNext();
XDebug(this,DebugInfo,"Jumped to label %ld",label);
return true;
}
}
}
else {
if (m_entries) {
for (const JsEntry* e = m_entries; e->number >= 0; e++) {
if (e->number == label) {
runner->m_index = e->index;
XDebug(this,DebugInfo,"Fast jumped to index %u",e->index);
return true;
}
}
}
unsigned int n = m_linked.length();
if (!n)
return false;
for (unsigned int i = 0; i < n; i++) {
const ExpOperation* o = static_cast<const ExpOperation*>(m_linked[i]);
if (o && o->opcode() == OpcLabel && o->number() == label) {
runner->m_index = i;
XDebug(this,DebugInfo,"Jumped to index %u",i);
return true;
}
}
}
return false;
}
bool JsCode::jumpRelative(long int offset, GenObject* context) const
{
if (!context)
return false;
unsigned int& index = static_cast<JsRunner*>(context)->m_index;
long int i = index + offset;
if (i < 0 || i > (long int)m_linked.length())
return false;
index = i;
XDebug(this,DebugInfo,"Jumped relative %+ld to index %ld",offset,i);
return true;
}
bool JsCode::jumpAbsolute(long int index, GenObject* context) const
{
if (!context)
return false;
JsRunner* runner = static_cast<JsRunner*>(context);
if (m_linked.length()) {
if (index < 0) {
runner->m_index = m_linked.length();
return true;
}
if (index > (long int)m_linked.length())
return false;
runner->m_index = index;
}
else {
if (index < 0) {
runner->m_opcode = 0;
return true;
}
long int i = 0;
for (const ObjList* l = &m_opcodes; ; l = l->next(), i++) {
if (i == index) {
runner->m_opcode = l;
break;
}
if (!l)
break;
}
if (i != index)
return false;
}
XDebug(this,DebugInfo,"Jumped absolute to index %ld",index);
return true;
}
bool JsCode::callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context,
JsFunction* func, bool constr, JsObject* thisObj) const
{
if (!(func && context))
return false;
XDebug(this,DebugInfo,"JsCode::callFunction(%p," FMT64 ",%p) in %s'%s' this=%p",
&stack,oper.number(),context,(constr ? "constructor " : ""),
func->toString().c_str(),thisObj);
JsRunner* runner = static_cast<JsRunner*>(context);
long int index = runner->m_index;
if (!m_linked.length()) {
const ObjList* o = runner->m_opcode;
index = -1;
long int i = 0;
for (const ObjList* l = &m_opcodes; ; l = l->next(), i++) {
if (l == o) {
index = i;
break;
}
if (!l)
break;
}
}
if (index < 0) {
Debug(this,DebugWarn,"Oops! Could not find return point!");
return false;
}
ExpOperation* op = 0;
if (constr) {
index = -index;
op = popOne(stack);
if (op && !thisObj)
thisObj = YOBJECT(JsObject,op);
}
if (thisObj && !thisObj->ref())
thisObj = 0;
TelEngine::destruct(op);
ObjList args;
JsObject::extractArgs(func,stack,oper,context,args);
return callFunction(stack,oper,context,index,func,args,thisObj,0);
}
bool JsCode::callFunction(ObjList& stack, const ExpOperation& oper, GenObject* context,
long int retIndex, JsFunction* func, ObjList& args,
JsObject* thisObj, JsObject* scopeObj) const
{
ExpOperation* ret = new ExpOperation(OpcFunc,oper.name(),retIndex,true);
ret->lineNumber(oper.lineNumber());
pushOne(stack,ret);
if (scopeObj)
pushOne(stack,new ExpWrapper(scopeObj,"()"));
JsObject* arguments = new JsObject("Arguments",func->mutex());
JsObject* ctxt = JsObject::buildCallContext(func->mutex(),thisObj);
int64_t cnt = 0;
for (unsigned int idx = 0; ; idx++) {
const String* name = func->formalName(idx);
if (!(name || arguments))
break;
ExpOperation* param = static_cast<ExpOperation*>(args.remove(false));
if (name) {
if (param)
ctxt->params().setParam(param->clone(*name));
else
ctxt->params().setParam(new ExpWrapper(0,*name));
// don't overwrite an explicit "arguments" argument
if (*name == YSTRING("arguments"))
TelEngine::destruct(arguments);
}
else if (!param)
break;
if (arguments && param)
arguments->params().setParam(param->clone(String(cnt++)));
TelEngine::destruct(param);
}
if (arguments) {
arguments->params().setParam(new ExpOperation(cnt,"length"));
arguments->params().setParam(new ExpWrapper(func,"callee"));
func->ref();
ctxt->params().setParam(new ExpWrapper(arguments,"arguments"));
}
pushOne(stack,new ExpWrapper(ctxt,ctxt->toString(),true));
if (!jumpToLabel(func->label(),context))
return false;
JsRunner* jsr = static_cast<JsRunner*>(context);
if (jsr && jsr->tracing())
jsr->traceCall(oper,*func);
return true;
}
ScriptRun* JsCode::createRunner(ScriptContext* context, const char* title)
{
if (!context)
return 0;
return new JsRunner(this,context,title);
}
bool JsCode::null() const
{
return m_linked.null() && !m_opcodes.skipNull();
}
void JsCode::dump(String& res, bool lineNo) const
{
if (m_linked.null())
return ExpEvaluator::dump(res,lineNo);
for (unsigned int i = 0; i < m_linked.length(); i++) {
const ExpOperation* o = static_cast<const ExpOperation*>(m_linked[i]);
if (!o)
continue;
if (res)
res << " ";
ExpEvaluator::dump(*o,res,lineNo);
}
}
ScriptRun::Status JsRunner::reset(bool init)
{
Status s = ScriptRun::reset(init);
m_opcode = code() ? static_cast<const JsCode*>(code())->m_opcodes.skipNull() : 0;
m_index = 0;
return s;
}
ScriptRun::Status JsRunner::resume()
{
Lock mylock(this);
if (Running != state())
return state();
RefPointer<ScriptCode> c = code();
if (!(c && context()))
return Invalid;
m_paused = false;
mylock.drop();
mylock.acquire(context()->mutex());
if (!c->evaluate(*this,stack()))
return Failed;
return m_paused ? Incomplete : Succeeded;
}
bool JsRunner::pause()
{
Lock mylock(this);
if (m_paused)
return true;
switch (state()) {
case Running:
case Incomplete:
DDebug(DebugAll,"Pausing Javascript runner [%p]",this);
m_paused = true;
return true;
default:
return false;
}
}
ScriptRun::Status JsRunner::call(const String& name, ObjList& args,
ExpOperation* thisObj, ExpOperation* scopeObj)
{
Lock mylock(this);
if (Invalid == state()) {
TelEngine::destruct(thisObj);
TelEngine::destruct(scopeObj);
return Invalid;
}
const JsCode* c = static_cast<const JsCode*>(code());
if (!(c && context())) {
TelEngine::destruct(thisObj);
TelEngine::destruct(scopeObj);
return Invalid;
}
JsFunction* func = c->getGlobalFunction(name);
if (!func) {
JsContext* ctx = YOBJECT(JsContext,context());
if (ctx)
func = YOBJECT(JsFunction,ctx->getField(stack(),name,this));
}
if (!func) {
TelEngine::destruct(thisObj);
TelEngine::destruct(scopeObj);
return Failed;
}
JsObject* jsThis = YOBJECT(JsObject,thisObj);
if (jsThis && !jsThis->ref())
jsThis = 0;
JsObject* jsScope = YOBJECT(JsObject,scopeObj);
if (jsScope && !jsScope->ref())
jsScope = 0;
TelEngine::destruct(thisObj);
TelEngine::destruct(scopeObj);
reset(false);
// prepare a function call stack
ExpOperation oper(ExpEvaluator::OpcFunc,name,args.count());
if (!c->callFunction(stack(),oper,this,-1,func,args,jsThis,jsScope))
return Failed;
mylock.drop();
// continue normal execution like in run()
ScriptRun::Status s = state();
while (Incomplete == s)
s = execute();
return s;
}
bool JsRunner::callable(const String& name)
{
Lock mylock(this);
if (Invalid == state())
return false;
const JsCode* c = static_cast<const JsCode*>(code());
return (c && context() && c->getGlobalFunction(name));
}
void JsRunner::traceStart(const char* title, const char* file)
{
if (m_tracing)
return;
m_tracing = true;
if (TelEngine::null(file) || !code())
return;
Debug(DebugInfo,"Preparing Javascript trace file '%s'",file);
JsCodeStats* stats = new JsCodeStats(static_cast<JsCode*>(code()),file);
traceStart(title,stats);
TelEngine::destruct(stats);
}
void JsRunner::traceStart(const char* title, JsCodeStats* stats)
{
m_stats = stats;
if (m_stats) {
m_tracing = true;
if (!m_callInfo) {
if (TelEngine::null(title))
title = "[main flow]";
m_stats->lock();
JsFuncStats* fs = m_stats->getFuncStats(title,0);
m_stats->unlock();
m_traceStack.insert(m_callInfo = new JsCallInfo(fs,title,0,0,0,0));
}
}
}
void JsRunner::traceDump()
{
if (!m_stats)
Debug(DebugNote,"Executed %u operations in " FMT64U " usec",m_instr,m_totalTime);
}
void JsRunner::traceCheck(const char* title)
{
if (!(code()))
return;
JsCode* c = static_cast<JsCode*>(code());
if (!c->traceable())
return;
static const String s_tracingPragma = "trace";
const NamedString* ns = c->pragmas().getParam(s_tracingPragma);
if (!(ns && ns->toBoolean(true)))
return;
if (ns->toBoolean(false) || !context()) {
traceStart(title);
return;
}
static const String s_tracingObj = "__trace__";
NamedString* obj = context()->params().getParam(s_tracingObj);
ExpWrapper* w = YOBJECT(ExpWrapper,obj);
if (w) {
JsCodeStats* stats = YOBJECT(JsCodeStats,w->object());
if (stats) {
DDebug(DebugInfo,"Using shared trace file '%s'",stats->toString().c_str());
traceStart(title,stats);
}
return;
}
else if (obj) {
traceStart(title);
return;
}
String filename = ns->c_str();
if (context()->instanceIndex())
filename << "_" << context()->instanceIndex();
traceStart(title,filename);
if (m_stats) {
m_stats->ref();
context()->params().setParam(new ExpWrapper(m_stats,s_tracingObj));
}
}
void JsRunner::tracePrep(const ExpOperation& oper)
{
if (!m_lastTime)
m_lastTime = Time::now();
m_lastLine = oper.lineNumber();
m_instr++;
}
void JsRunner::tracePost(const ExpOperation& oper)
{
u_int64_t time = Time::now();
u_int64_t diff = 0;
if (m_lastTime) {
diff = time - m_lastTime;
m_totalTime += diff;
}
m_lastTime = m_paused ? 0 : time;
if (diff && m_callInfo && m_stats) {
#ifdef STATS_TRACE
String line;
static_cast<const JsCode*>(code())->formatLineNo(line,m_lastLine);
Debug(STATS_TRACE,DebugNote,"Operation %u %s @ %s %s took " FMT64U " usec",
oper.opcode(),static_cast<const JsCode*>(code())->getOperator(oper.opcode()),
line.c_str(),m_callInfo->c_str(),diff);
#endif
m_stats->lock();
m_callInfo->traceLine(m_lastLine,diff);
m_stats->unlock();
}
}
void JsRunner::traceCall(const ExpOperation& oper, const JsFunction& func)
{
const ExpOperation* o = 0;
if (m_opcode)
o = static_cast<const ExpOperation*>(m_opcode->get());
if (!o)
o = static_cast<const ExpOperation*>(static_cast<const JsCode*>(code())->m_linked[m_index]);
if (!o) {
String str;
static_cast<const JsCode*>(code())->formatLineNo(str,m_lastLine);
Debug(DebugWarn,"Current operation unavailable in %s [%p]",str.c_str(),this);
return;
}
const String& name = func.getFunc()->name();
JsFuncStats* fs = 0;
if (m_stats) {
m_stats->lock();
if (m_lastTime) {
u_int64_t diff = Time::now() - m_lastTime;
m_totalTime += diff;
m_lastTime = 0;
if (m_callInfo)
m_callInfo->traceLine(m_lastLine,diff);
}
fs = m_stats->getFuncStats(name,o->lineNumber());
m_stats->unlock();
}
#ifdef STATS_TRACE
String caller,called;
static_cast<const JsCode*>(code())->formatLineNo(caller,m_lastLine);
static_cast<const JsCode*>(code())->formatLineNo(called,o->lineNumber());
Debug(STATS_TRACE,DebugCall,"Call %s %s -> %s, instr=%u",
name.c_str(),caller.c_str(),called.c_str(),m_instr);
#endif
m_traceStack.insert(m_callInfo = new JsCallInfo(fs,name,m_lastLine,o->lineNumber(),m_instr,m_totalTime));
}
void JsRunner::traceReturn()
{
JsCallInfo* info = static_cast<JsCallInfo*>(m_traceStack.remove(false));
if (!info) {
String str;
static_cast<const JsCode*>(code())->formatLineNo(str,m_lastLine);
Debug(DebugWarn,"Stats stack underflow in %s [%p]",str.c_str(),this);
return;
}
m_callInfo = static_cast<JsCallInfo*>(m_traceStack.get());
unsigned int instr = m_instr - info->startInstr;
u_int64_t time = Time::now();
u_int64_t timeInstr = 0;
if (m_lastTime) {
timeInstr = time - m_lastTime;
m_totalTime += timeInstr;
m_lastTime = 0;
}
u_int64_t timeCall = m_totalTime - info->startTime;
if (m_stats && timeInstr) {
m_stats->lock();
info->traceLine(m_lastLine,timeInstr);
m_stats->unlock();
}
if (m_callInfo && m_stats) {
#ifdef STATS_TRACE
String caller,called;
static_cast<const JsCode*>(code())->formatLineNo(caller,info->callerLine);
static_cast<const JsCode*>(code())->formatLineNo(called,info->calledLine);
Debug(STATS_TRACE,DebugCall,"Ret %s %s -> %s, %u oper / " FMT64U " usec",
info->c_str(),caller.c_str(),called.c_str(),instr,timeCall);
#endif
m_stats->lock();
m_callInfo->traceCall(info,instr,timeCall);
m_stats->unlock();
}
else {
String caller,called;
static_cast<const JsCode*>(code())->formatLineNo(caller,info->callerLine);
static_cast<const JsCode*>(code())->formatLineNo(called,info->calledLine);
Debug(DebugNote,"Function '%s' %s -> %s took %u operations / " FMT64U " usec",
info->c_str(),caller.c_str(),called.c_str(),instr,timeCall);
}
TelEngine::destruct(info);
}
void JsFuncStats::updateLine(unsigned int lineNo, u_int64_t usec)
{
if (!lineNumber)
lineNumber = lineNo;
#ifdef STATS_TRACE
Debug(STATS_TRACE,DebugAll,"Updating %u:%u in %s [%u:%u] with " FMT64U " usec",
JsCode::getFileNo(lineNo),JsCode::getLineNo(lineNo),c_str(),
JsCode::getFileNo(lineNumber),JsCode::getLineNo(lineNumber),usec);
#endif
ObjList* l = &funcLines;
while (l) {
JsLineStats* s = static_cast<JsLineStats*>(l->get());
if (s) {
if (s->lineNumber == lineNo && !s->isCall) {
s->operations++;
s->microseconds += usec;
return;
}
if (s->lineNumber > lineNo)
break;
}
ObjList* ln = l->next();
if (ln) {
l = ln;
continue;
}
l->append(new JsLineStats(lineNo,1,usec));
return;
}
l->insert(new JsLineStats(lineNo,1,usec));
}
void JsFuncStats::updateCall(const char* name, unsigned int caller, unsigned int called, unsigned int instr, u_int64_t usec)
{
ObjList* l = &funcLines;
while (l) {
JsLineStats* s = static_cast<JsLineStats*>(l->get());
if (s) {
if (s->lineNumber == caller && s->isCall) {
JsCallStats* cs = static_cast<JsCallStats*>(s);
if (cs->calledLine == called) {
cs->operations += instr;
cs->microseconds += usec;
cs->callsCount++;
return;
}
}
if (s->lineNumber > caller)
break;
}
ObjList* ln = l->next();
if (ln) {
l = ln;
continue;
}
#ifndef TRACE_RAW_NAME
String tmp(name);
if (called)
tmp << " [" << JsCode::getFileNo(called) << ":" << JsCode::getLineNo(called) << "]";
name = tmp;
#endif
l->append(new JsCallStats(name,caller,called,instr,usec));
return;
}
#ifndef TRACE_RAW_NAME
String tmp(name);
if (called)
tmp << " [" << JsCode::getFileNo(called) << ":" << JsCode::getLineNo(called) << "]";
name = tmp;
#endif
l->insert(new JsCallStats(name,caller,called,instr,usec));
}
const ExpOperation* JsRunner::getCurrentOpCode() const
{
const ExpOperation* o = 0;
if (m_opcode)
o = static_cast<const ExpOperation*>(m_opcode->get());
if (!o)
o = static_cast<const ExpOperation*>(static_cast<const JsCode*>(code())->m_linked[m_index]);
if (!o) {
String str;
static_cast<const JsCode*>(code())->formatLineNo(str,m_lastLine);
Debug(DebugWarn,"Current operation unavailable in %s [%p]",str.c_str(),this);
return 0;
}
return o;
}
unsigned int JsRunner::currentLineNo() const
{
const ExpOperation* o = getCurrentOpCode();
return o ? JsCode::getLineNo(o->lineNumber()) : 0;
}
const String& JsRunner::currentFileName(bool wholePath) const
{
static const String s_unk("???");
const ExpOperation* o = getCurrentOpCode();
if (!(o && code()))
return s_unk;
return (static_cast<const JsCode*>(code()))->getFileName(o->lineNumber(),wholePath);
}
JsCodeStats::JsCodeStats(JsCode* code, const char* file)
: Mutex(false,"JsCodeStats"),
m_fileName(file)
{
m_code = code;
if (!code)
return;
}
JsCodeStats::~JsCodeStats()
{
dump();
}
JsFuncStats* JsCodeStats::getFuncStats(const char* name, unsigned int lineNo)
{
String tmp(name);
#ifndef TRACE_RAW_NAME
if (lineNo)
tmp << " [" << JsCode::getFileNo(lineNo) << ":" << JsCode::getLineNo(lineNo) << "]";
#endif
ObjList* l = &m_funcStats;
while (l) {
JsFuncStats* s = static_cast<JsFuncStats*>(l->get());
if (s) {
if (s->lineNumber == lineNo && tmp == *s)
return s;
if (s->lineNumber > lineNo)
break;
}
ObjList* ln = l->next();
if (ln) {
l = ln;
continue;
}
s = new JsFuncStats(tmp,lineNo);
l->append(s);
return s;
}
JsFuncStats* s = new JsFuncStats(tmp,lineNo);
l->insert(s);
return s;
}
void JsCodeStats::dump()
{
dump(m_fileName);
m_fileName.clear();
}
void JsCodeStats::dump(const char* file)
{
File f;
if (file && m_code && f.openPath(file,true,false,true)) {
Debug(DebugInfo,"Writing trace file '%s'",file);
dump(f);
}
}
void JsCodeStats::dump(Stream& file)
{
if (!m_code)
return;
String fl,fn,cfn,cfl;
NamedList lMap(""), nMap("");
int ifl = 1, ifn = 1;
file.writeData("events: Operations Microseconds\n");
for (ObjList* f = m_funcStats.skipNull(); f; f = f->skipNext()) {
String str("\n");
const JsFuncStats* fs = static_cast<const JsFuncStats*>(f->get());
String tmp = m_code->getFileName(fs->lineNumber);
if (fl != tmp) {
fl = tmp;
tmp = lMap[fl];
if (tmp.null()) {
tmp << "(" << ifl++ << ")";
lMap.addParam(fl,tmp);
tmp << " " << fl;
}
str << "fl=" << tmp << "\n";
}
if (fn != *fs) {
fn = *fs;
tmp = nMap[fn];
if (tmp.null()) {
tmp << "(" << ifn++ << ")";
nMap.addParam(fn,tmp);
tmp << " " << fn;
}
str << "fn=" << tmp << "\n";
}
for (ObjList* l = fs->funcLines.skipNull(); l; l = l->skipNext()) {
const JsLineStats* ls = static_cast<const JsLineStats*>(l->get());
tmp = m_code->getFileName(ls->lineNumber);
if (fl != tmp) {
fl = tmp;
tmp = lMap[fl];
if (tmp.null()) {
tmp << "(" << ifl++ << ")";
lMap.addParam(fl,tmp);
tmp << " " << fl;
}
str << "fl=" << tmp << "\n";
}
if (ls->isCall) {
const JsCallStats* cs = static_cast<const JsCallStats*>(ls);
tmp = m_code->getFileName(cs->calledLine);
if (cfl != tmp) {
cfl = tmp;
tmp = lMap[cfl];
if (tmp.null()) {
tmp << "(" << ifl++ << ")";
lMap.addParam(cfl,tmp);
tmp << " " << cfl;
}
str << "cfl=" << tmp << "\n";
}
if (cfn != cs->funcName) {
cfn = cs->funcName;
tmp = nMap[cfn];
if (tmp.null()) {
tmp << "(" << ifn++ << ")";
nMap.addParam(cfn,tmp);
tmp << " " << cfn;
}
str << "cfn=" << tmp << "\n";
}
str << "calls=" << cs->callsCount << " "
<< JsCode::getLineNo(cs->calledLine) << "\n";
}
str << JsCode::getLineNo(ls->lineNumber) << " " << ls->operations << " "
<< ls->microseconds << "\n";
}
file.writeData(str);
}
}
}; // anonymous namespace
JsFunction::JsFunction(ScriptMutex* mtx)
: JsObject("Function",mtx,true),
m_label(0), m_code(0), m_func("")
{
init();
}
JsFunction::JsFunction(ScriptMutex* mtx, const char* name, unsigned int line, ObjList* args, long int lbl, ScriptCode* code)
: JsObject(mtx,String("[function ") + name + "()]",line,false),
m_label(lbl), m_code(code), m_func(name)
{
init();
if (args) {
while (GenObject* arg = args->remove(false))
m_formal.append(arg);
}
unsigned int argc = m_formal.count();
static_cast<ExpOperation&>(m_func) = argc;
if (name)
params().addParam("name",name);
params().addParam("length",String(argc));
}
JsObject* JsFunction::copy(ScriptMutex* mtx, const char* name, const ExpOperation& oper) const
{
ObjList args;
for (ObjList* l = m_formal.skipNull(); l; l = l->skipNext())
args.append(new String(l->get()->toString()));
return new JsFunction(mtx,name,oper.lineNumber(),&args,label(),m_code);
}
JsFunction* JsFunction::cloneFunction(const ExpOperation& oper, ScriptMutex* mtx)
{
XDebug(DebugInfo,"JsFunction::cloneFunction(mtx=%p) '%s'='%s' (%s) in '%s' [%p]",
mtx,oper.name().c_str(),oper.c_str(),oper.typeOf(),toString().c_str(),this);
if (mutex() && ref()) {
XDebug(DebugAll,"JsFunction::cloneFunction() Function '%s' already cloned [%p]",toString().c_str(),this);
return this;
}
return static_cast<JsFunction*>(copy(mtx,m_func.toString(),oper));
}
void JsFunction::init()
{
params().addParam(new ExpFunction("apply"));
params().addParam(new ExpFunction("call"));
}
void JsFunction::initConstructor(JsFunction* construct)
{
construct->params().addParam(new ExpFunction("context"));
construct->params().addParam(new ExpFunction("stack"));
}
bool JsFunction::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context)
{
XDebug(DebugAll,"JsFunction::runNative() '%s' in '%s' [%p]",
oper.name().c_str(),toString().c_str(),this);
if (oper.name() == YSTRING("apply")) {
// func.apply(new_this)
// func.apply(new_this,["array","of","params",...])
switch (oper.number()) {
case 1:
case 2:
break;
default:
return false;
}
ObjList args;
extractArgs(this,stack,oper,context,args);
JsObject* thisObj = YOBJECT(JsObject,args[0]);
JsArray* callArgs = YOBJECT(JsArray,args[1]);
int argc = 0;
if (callArgs) {
int32_t len = callArgs->length();
for (int32_t i = 0; i < len; i++)
if (callArgs->runField(stack,ExpOperation((int64_t)i,String(i)),context))
argc++;
}
ExpFunction func(toString(),argc);
return runDefined(stack,func,context,thisObj);
}
else if (oper.name() == YSTRING("call")) {
// func.call(new_this)
// func.call(new_this,param1,param2,...)
if (!oper.number())
return false;
ObjList args;
extractArgs(this,stack,oper,context,args);
JsObject* thisObj = YOBJECT(JsObject,args[0]);
int argc = 0;
ObjList* l = args.next();
if (l) {
while (ExpOperation* op = static_cast<ExpOperation*>(l->remove(false))) {
ExpEvaluator::pushOne(stack,op);
argc++;
}
}
ExpFunction func(toString(),argc);
return runDefined(stack,func,context,thisObj);
}
else if (oper.name() == YSTRING("context")) {
// Function.context([skipContexts])
// return the current function context
int skip = 0;
ObjList args;
switch (extractArgs(this,stack,oper,context,args)) {
case 1:
skip = static_cast<ExpOperation*>(args[0])->valInteger();
if (skip < 0)
skip = 0;
break;
case 0:
break;
default:
return false;
}
for (const ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
GenObject* o = l->get();
if (static_cast<ExpOperation*>(o)->opcode() == ExpEvaluator::OpcFunc) {
if (skip--)
continue;
break;
}
if (skip)
continue;
ExpWrapper* wrap = YOBJECT(ExpWrapper,o);
if (!(wrap && wrap->name() == YSTRING("()")))
continue;
JsObject* obj = YOBJECT(JsObject,wrap->object());
if (!obj)
continue;
if (!obj->ref())
break;
ExpEvaluator::pushOne(stack,new ExpWrapper(obj,oper.name()));
return true;
}
ExpEvaluator::pushOne(stack,JsParser::nullClone());
}
else if (oper.name() == YSTRING("stack")) {
// Function.stack([fullPath[,stringSeparator]])
// return the function call stack
bool full = false;
String sep;
ObjList args;
switch (extractArgs(this,stack,oper,context,args)) {
case 2:
sep = *static_cast<ExpOperation*>(args[1]);
// fall through
case 1:
full = static_cast<ExpOperation*>(args[0])->valBoolean(full);
break;
case 0:
break;
default:
return false;
}
ScriptRun* runner = YOBJECT(ScriptRun,context);
JsArray* jsa = new JsArray(context,oper.lineNumber(),mutex());
if (runner) {
String file;
unsigned int line;
runner->code()->getFileLine(oper.lineNumber(),file,line,full);
file << ":" << line;
ExpOperation* last = new ExpOperation(file);
jsa->push(last);
for (const ObjList* l = stack.skipNull(); l; l = l->skipNext()) {
GenObject* o = l->get();
const ExpOperation* op = static_cast<const ExpOperation*>(o);
if ((op->barrier() && op->opcode() == ExpEvaluator::OpcFunc)) {
runner->code()->getFileLine(op->lineNumber(),file,line,full);
file << ":" << line;
last = new ExpOperation(file);
jsa->push(last);
}
else if (last) {
ExpWrapper* wrap = YOBJECT(ExpWrapper,o);
if (!(wrap && wrap->name() == YSTRING("()")))
continue;
JsObject* obj = YOBJECT(JsObject,wrap->object());
if (!obj)
continue;
wrap = YOBJECT(ExpWrapper,obj->params().getParam(YSTRING("arguments")));
if (!wrap)
continue;
obj = YOBJECT(JsObject,wrap->object());
if (!obj)
continue;
wrap = YOBJECT(ExpWrapper,obj->params().getParam(YSTRING("callee")));
if (!wrap)
continue;
obj = YOBJECT(JsObject,wrap->object());
if (!obj)
continue;
*last << " " << obj->params();
last = 0;
}
}
}
if (sep) {
String res;
for (int32_t i = 0; i < jsa->length(); i++)
res.append(jsa->params()[String(i)],sep);
TelEngine::destruct(jsa);
ExpEvaluator::pushOne(stack,new ExpOperation(res,oper.name()));
}
else
ExpEvaluator::pushOne(stack,new ExpWrapper(jsa,oper.name()));
}
else {
JsObject* obj = YOBJECT(JsObject,params().getParam(YSTRING("prototype")));
return obj ? obj->runNative(stack,oper,context) : JsObject::runNative(stack,oper,context);
}
return true;
}
bool JsFunction::runDefined(ObjList& stack, const ExpOperation& oper, GenObject* context, JsObject* thisObj)
{
XDebug(DebugAll,"JsFunction::runDefined() in '%s' this=%p [%p]",
toString().c_str(),thisObj,this);
JsObject* newObj = 0;
JsObject* proto = YOBJECT(JsObject,getField(stack,"prototype",context));
if (proto) {
// found prototype, build object
newObj = proto->runConstructor(stack,oper,context);
if (!newObj)
return false;
ExpEvaluator::pushOne(stack,new ExpWrapper(newObj,oper.name()));
thisObj = newObj;
}
JsCode* code = YOBJECT(JsCode,m_code);
XDebug(DebugAll,"JsFunction::runDefined code=%p proto=%p %s=%p [%p]",
code,proto,(newObj ? "new" : "this"),thisObj,this);
if (code) {
if (!code->callFunction(stack,oper,context,this,(proto != 0),thisObj))
return false;
if (newObj && newObj->ref())
ExpEvaluator::pushOne(stack,new ExpWrapper(newObj,oper.name()));
return true;
}
return proto || runNative(stack,oper,context);
}
// Adjust a script file include path
void JsParser::adjustPath(String& script, bool extraInc) const
{
if (script.null() || script.startsWith(Engine::pathSeparator()))
return;
if (extraInc && m_includePath && File::exists(m_includePath + script))
script = m_includePath + script;
else
script = m_basePath + script;
}
// Create Javascript context
ScriptContext* JsParser::createContext(unsigned int instIdx, unsigned int maxInst) const
{
return new JsContext(instIdx,maxInst);
}
ScriptRun* JsParser::createRunner(ScriptCode* code, ScriptContext* context, const char* title,
unsigned int instIdx, unsigned int maxInst) const
{
if (!code)
return 0;
ScriptContext* ctxt = 0;
if (!context)
context = ctxt = createContext(instIdx,maxInst);
ScriptRun* runner = new JsRunner(code,context,title);
TelEngine::destruct(ctxt);
return runner;
}
// Check if function or method exists
bool JsParser::callable(const String& name)
{
const JsCode* c = static_cast<const JsCode*>(code());
return (c && c->getGlobalFunction(name));
}
// Parse a piece of Javascript text
bool JsParser::parse(const char* text, bool fragment, const char* file, int len)
{
if (TelEngine::null(text))
return false;
String::stripBOM(text);
JsCode* jsc = static_cast<JsCode*>(code());
ParsePoint expr(text,0,(jsc ? jsc->lineNumber() : 0),file);
if (fragment)
return jsc && jsc->compile(expr,this);
m_parsedFile.clear();
jsc = new JsCode;
setCode(jsc);
jsc->deref();
expr.m_eval = jsc;
if (!TelEngine::null(file)) {
jsc->setBaseFile(file);
expr.m_fileName = file;
expr.m_lineNo = jsc->lineNumber();
}
if (!jsc->compile(expr,this)) {
setCode(0);
return false;
}
m_parsedFile = file;
DDebug(DebugAll,"Compiled: %s",jsc->ExpEvaluator::dump().c_str());
jsc->simplify();
DDebug(DebugAll,"Simplified: %s",jsc->ExpEvaluator::dump().c_str());
if (m_allowLink) {
jsc->link();
#ifdef DEBUG
#ifdef XDEBUG
Debug(DebugAll,"Linked: %s",jsc->ExpEvaluator::dump(true).c_str());
#else
Debug(DebugAll,"Linked: %s",jsc->ExpEvaluator::dump(false).c_str());
#endif
#endif
}
jsc->trace(m_allowTrace);
return true;
}
// Check if the script, path or any included files have changed
bool JsParser::scriptChanged(const char* file) const
{
if (TelEngine::null(file))
return true;
const JsCode* c = static_cast<const JsCode*>(code());
if (!c)
return true;
String tmp(file);
adjustPath(tmp);
return (parsedFile() != tmp) || c->scriptChanged();
}
// Evaluate a string as expression or statement
ScriptRun::Status JsParser::eval(const String& text, ExpOperation** result, ScriptContext* context)
{
if (TelEngine::null(text))
return ScriptRun::Invalid;
JsParser parser;
if (!parser.parse(text))
return ScriptRun::Invalid;
ScriptRun* runner = parser.createRunner(context);
ScriptRun::Status rval = runner->run();
if (result && (ScriptRun::Succeeded == rval))
*result = ExpEvaluator::popOne(runner->stack());
TelEngine::destruct(runner);
return rval;
}
// Parse JSON using native methods
ExpOperation* JsParser::parseJSON(const char* text, ScriptMutex* mtx, ObjList* stack, GenObject* context, const ExpOperation* op)
{
if (!text)
return 0;
ExpOperation* ret = 0;
JsCode* code = new JsCode;
ParsePoint pp(text,code);
if (code->parseSimple(pp,true,mtx)) {
ret = code->popOpcode();
if (code->skipComments(pp,context))
TelEngine::destruct(ret);
}
if (stack && ret) {
JsObject* obj = YOBJECT(JsObject,ret);
code->resolveObjectParams(obj,*stack,context);
if (op)
JsObject::setLineForObj(obj,op->lineNumber(),true);
}
TelEngine::destruct(code);
return ret;
}
// Return a "null" object wrapper
ExpOperation* JsParser::nullClone(const char* name)
{
return TelEngine::null(name) ? s_null.ExpOperation::clone() : s_null.clone(name);
}
// Return the "null" object
JsObject* JsParser::nullObject()
{
JsObject* n = YOBJECT(JsObject,s_null.object());
return (n && n->ref()) ? n : 0;
}
// Check if an object is identic to null
bool JsParser::isNull(const ExpOperation& oper)
{
ExpWrapper* w = YOBJECT(ExpWrapper,&oper);
return w && (w->object() == s_null.object());
}
// Check if an operation is undefined
bool JsParser::isUndefined(const ExpOperation& oper)
{
ExpWrapper* w = YOBJECT(ExpWrapper,&oper);
return w && !w->object();
}
// Check if an operation is null or undefined
bool JsParser::isMissing(const ExpOperation& oper)
{
ExpWrapper* w = YOBJECT(ExpWrapper,&oper);
return w && (!w->object() || (w->object() == s_null.object()));
}
/* vi: set ts=8 sw=4 sts=4 noet: */