/** * 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 //#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(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(this); if (name == YATOM("ExpEvaluator")) return const_cast((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(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 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 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 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(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(nested) : (JsCode::JsOpcode)ExpEvaluator::OpcNone; } inline static ParseNested* find(GenObject* nested, JsCode::JsOpcode opcode) { return nested ? static_cast(nested)->find(opcode) : 0; } inline static ParseNested* findMatch(GenObject* nested, JsCode::JsOpcode opcode) { return nested ? static_cast(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(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(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(args[0]); if (op1) { int base = 0; ExpOperation* op2 = static_cast(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(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(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(args[0]); if (what) { ExpOperation* from = static_cast(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(args[0]); if (op && op->isInteger()) offs = (int)op->number(); op = static_cast(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(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(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(args.skipNull()->get()); \ if (tmp) \ what = tmp->c_str(); \ } \ if (args.count() >= 2) { \ String* tmp = static_cast(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(gen)->at(0)); unsigned int limit = 0; if (args.count() >= 2) { String* l = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(parseStack.m_cases.remove(false))) { ExpOperation* j = static_cast(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(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(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(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(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(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(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(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(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(context); const ObjList* (& opcode) = runner->m_opcode; while (opcode) { const ExpOperation* o = static_cast(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(context); unsigned int& index = runner->m_index; while (index < m_linked.length()) { const ExpOperation* o = static_cast(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(context); if (m_opcodes.skipNull()) { for (ObjList* l = m_opcodes.skipNull(); l; l = l->skipNext()) { const ExpOperation* o = static_cast(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(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(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(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(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(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(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(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(code())->m_opcodes.skipNull() : 0; m_index = 0; return s; } ScriptRun::Status JsRunner::resume() { Lock mylock(this); if (Running != state()) return state(); RefPointer 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(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(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(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(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(code())->formatLineNo(line,m_lastLine); Debug(STATS_TRACE,DebugNote,"Operation %u %s @ %s %s took " FMT64U " usec", oper.opcode(),static_cast(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(m_opcode->get()); if (!o) o = static_cast(static_cast(code())->m_linked[m_index]); if (!o) { String str; static_cast(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(code())->formatLineNo(caller,m_lastLine); static_cast(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(m_traceStack.remove(false)); if (!info) { String str; static_cast(code())->formatLineNo(str,m_lastLine); Debug(DebugWarn,"Stats stack underflow in %s [%p]",str.c_str(),this); return; } m_callInfo = static_cast(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(code())->formatLineNo(caller,info->callerLine); static_cast(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(code())->formatLineNo(caller,info->callerLine); static_cast(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(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(l->get()); if (s) { if (s->lineNumber == caller && s->isCall) { JsCallStats* cs = static_cast(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(m_opcode->get()); if (!o) o = static_cast(static_cast(code())->m_linked[m_index]); if (!o) { String str; static_cast(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(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(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(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(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(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(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(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(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(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(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(args[1]); // fall through case 1: full = static_cast(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(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(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(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(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: */