654 lines
16 KiB
C++
654 lines
16 KiB
C++
/*
|
|
* mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
* Copyright (C) 2013-2014, Peter Olsson <peter@olssononline.se>
|
|
*
|
|
* Version: MPL 1.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Peter Olsson <peter@olssononline.se>
|
|
* Portions created by the Initial Developer are Copyright (C)
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Peter Olsson <peter@olssononline.se>
|
|
*
|
|
* jsmain.cpp -- JavaScript Main V8 script runner
|
|
*
|
|
*/
|
|
|
|
#include "javascript.hpp"
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
#include "mod_v8.h"
|
|
#endif
|
|
|
|
#ifdef V8_ENABLE_DEBUGGING
|
|
#include <v8-debug.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
#include <switch.h>
|
|
#endif
|
|
|
|
using namespace std;
|
|
using namespace v8;
|
|
|
|
#ifdef V8_ENABLE_DEBUGGING
|
|
void V8DispatchDebugMessages()
|
|
{
|
|
Isolate* isolate = Isolate::GetCurrent();
|
|
Persistent<Context> *persistent_contect = (Persistent<Context> *)isolate->GetData(1);
|
|
HandleScope handle_scope(isolate);
|
|
Local<Context> context = Local<Context>::New(isolate, *persistent_contect);
|
|
Context::Scope scope(context);
|
|
|
|
Debug::ProcessDebugMessages();
|
|
}
|
|
#endif
|
|
|
|
bool JSMain::FileExists(const char *file)
|
|
{
|
|
ifstream fh(file);
|
|
bool file_exists = false;
|
|
|
|
if (fh) {
|
|
fh.close();
|
|
file_exists = true;
|
|
}
|
|
|
|
return file_exists;
|
|
}
|
|
|
|
const string JSMain::LoadFileToString(const string& filename)
|
|
{
|
|
if (filename.length() == 0) {
|
|
return "";
|
|
}
|
|
|
|
ifstream in(filename.c_str(), std::ios::in | std::ios::binary);
|
|
|
|
if (in) {
|
|
string contents;
|
|
|
|
in.seekg(0, std::ios::end);
|
|
contents.resize((size_t)in.tellg());
|
|
|
|
in.seekg(0, std::ios::beg);
|
|
in.read(&contents[0], contents.size());
|
|
in.close();
|
|
|
|
return contents;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
JSMain::JSMain(void)
|
|
{
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
Isolate::CreateParams params;
|
|
params.array_buffer_allocator =
|
|
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
|
isolate = Isolate::New(params);
|
|
#else
|
|
isolate = Isolate::New();
|
|
#endif
|
|
|
|
extenderClasses = new vector<const js_class_definition_t *>();
|
|
extenderFunctions = new vector<js_function_t *>();
|
|
extenderInstances = new vector<registered_instance_t*>();
|
|
activeInstances = new set<JSBase *>();
|
|
|
|
forcedTermination = false;
|
|
forcedTerminationMessage = NULL;
|
|
forcedTerminationLineNumber = 0;
|
|
forcedTerminationScriptFile = NULL;
|
|
}
|
|
|
|
JSMain::~JSMain(void)
|
|
{
|
|
bool enteredIsolate = false;
|
|
|
|
for (size_t i = 0; i < extenderInstances->size(); i++) {
|
|
registered_instance_t *inst = (*extenderInstances)[i];
|
|
|
|
if (inst) {
|
|
if (inst->name) free(inst->name);
|
|
free(inst);
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < extenderFunctions->size(); i++) {
|
|
js_function_t *proc = (*extenderFunctions)[i];
|
|
|
|
if (proc) {
|
|
if (proc->name) free((char *)proc->name);
|
|
free(proc);
|
|
}
|
|
}
|
|
|
|
extenderInstances->clear();
|
|
extenderClasses->clear();
|
|
extenderFunctions->clear();
|
|
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
if (isolate) {
|
|
#else
|
|
if (!Isolate::GetCurrent()) {
|
|
#endif
|
|
enteredIsolate = true;
|
|
isolate->Enter();
|
|
}
|
|
|
|
/* It's probably not totally safe to call this here, but it whould always be empty by now anyway */
|
|
DisposeActiveInstances();
|
|
|
|
if (enteredIsolate) {
|
|
isolate->Exit();
|
|
}
|
|
|
|
delete extenderClasses;
|
|
delete extenderFunctions;
|
|
delete extenderInstances;
|
|
delete activeInstances;
|
|
|
|
if (forcedTerminationMessage) free(forcedTerminationMessage);
|
|
if (forcedTerminationScriptFile) free(forcedTerminationScriptFile);
|
|
|
|
isolate->Dispose();
|
|
}
|
|
|
|
const string JSMain::GetExceptionInfo(Isolate* isolate, TryCatch* try_catch)
|
|
{
|
|
HandleScope handle_scope(isolate);
|
|
String::Utf8Value exception(try_catch->Exception());
|
|
const char *exception_string = js_safe_str(*exception);
|
|
Handle<Message> message = try_catch->Message();
|
|
string res;
|
|
|
|
if (message.IsEmpty()) {
|
|
// V8 didn't provide any extra information about this error; just return the exception.
|
|
res = exception_string;
|
|
} else {
|
|
String::Utf8Value filename(message->GetScriptResourceName());
|
|
const char *filename_string = js_safe_str(*filename);
|
|
int linenum = message->GetLineNumber();
|
|
|
|
ostringstream ss;
|
|
|
|
ss << filename_string << ":" << linenum << ": " << exception_string << "\r\n";
|
|
|
|
// Print line of source code.
|
|
String::Utf8Value sourceline(message->GetSourceLine());
|
|
const char *sourceline_string = js_safe_str(*sourceline);
|
|
|
|
ss << sourceline_string << "\r\n";
|
|
|
|
// Print wavy underline.
|
|
int start = message->GetStartColumn();
|
|
|
|
for (int i = 0; i < start; i++) {
|
|
ss << " ";
|
|
}
|
|
|
|
int end = message->GetEndColumn();
|
|
|
|
for (int i = start; i < end; i++) {
|
|
ss << "^";
|
|
}
|
|
|
|
res = ss.str();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void JSMain::Include(const v8::FunctionCallbackInfo<Value>& args)
|
|
{
|
|
for (int i = 0; i < args.Length(); i++) {
|
|
HandleScope handle_scope(args.GetIsolate());
|
|
String::Utf8Value str(args[i]);
|
|
|
|
// load_file loads the file with this name into a string
|
|
string js_file = LoadFileToString(js_safe_str(*str));
|
|
|
|
if (js_file.length() > 0) {
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
MaybeLocal<v8::Script> script;
|
|
LoadScript(&script, args.GetIsolate(), js_file.c_str(), js_safe_str(*str));
|
|
|
|
if (script.IsEmpty()) {
|
|
args.GetReturnValue().Set(false);
|
|
}
|
|
else {
|
|
args.GetReturnValue().Set(script.ToLocalChecked()->Run());
|
|
}
|
|
#else
|
|
Handle<String> source = String::NewFromUtf8(args.GetIsolate(), js_file.c_str());
|
|
Handle<Script> script = Script::Compile(source, args[i]);
|
|
args.GetReturnValue().Set(script->Run());
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
args.GetReturnValue().Set(Undefined(args.GetIsolate()));
|
|
}
|
|
|
|
void JSMain::Log(const v8::FunctionCallbackInfo<Value>& args)
|
|
{
|
|
HandleScope handle_scope(args.GetIsolate());
|
|
String::Utf8Value str(args[0]);
|
|
|
|
printf("%s\r\n", js_safe_str(*str));
|
|
|
|
args.GetReturnValue().Set(Undefined(args.GetIsolate()));
|
|
}
|
|
|
|
void JSMain::Version(const v8::FunctionCallbackInfo<Value>& args)
|
|
{
|
|
args.GetReturnValue().Set(String::NewFromUtf8(args.GetIsolate(), V8::GetVersion()));
|
|
}
|
|
|
|
const string JSMain::ExecuteScript(const string& filename, bool *resultIsError)
|
|
{
|
|
// Get the file and load into a string.
|
|
string data = LoadFileToString(filename);
|
|
|
|
return ExecuteString(data, filename, resultIsError);
|
|
}
|
|
|
|
const string JSMain::ExecuteString(const string& scriptData, const string& fileName, bool *resultIsError)
|
|
{
|
|
string res;
|
|
bool isError = false;
|
|
|
|
//solate->Enter();
|
|
{
|
|
Locker lock(isolate);
|
|
Isolate::Scope iscope(isolate);
|
|
{
|
|
// Create a stack-allocated handle scope.
|
|
HandleScope scope(isolate);
|
|
|
|
isolate->SetData(0, this);
|
|
|
|
Handle<ObjectTemplate> global = ObjectTemplate::New();
|
|
global->Set(String::NewFromUtf8(isolate, "include"), FunctionTemplate::New(isolate, Include));
|
|
global->Set(String::NewFromUtf8(isolate, "require"), FunctionTemplate::New(isolate, Include));
|
|
global->Set(String::NewFromUtf8(isolate, "log"), FunctionTemplate::New(isolate, Log));
|
|
|
|
for (size_t i = 0; i < extenderFunctions->size(); i++) {
|
|
js_function_t *proc = (*extenderFunctions)[i];
|
|
global->Set(String::NewFromUtf8(isolate, proc->name), FunctionTemplate::New(isolate, proc->func));
|
|
}
|
|
|
|
// Create a new context.
|
|
Local<Context> context = Context::New(isolate, NULL, global);
|
|
|
|
if (context.IsEmpty()) {
|
|
return "Failed to create new JS context";
|
|
}
|
|
|
|
// Enter the created context for compiling and running the script.
|
|
Context::Scope context_scope(context);
|
|
|
|
// Register all plugins.
|
|
for (size_t i = 0; i < extenderClasses->size(); i++) {
|
|
JSBase::Register(isolate, (*extenderClasses)[i]);
|
|
}
|
|
|
|
// Register all instances.
|
|
for (size_t i = 0; i < extenderInstances->size(); i++) {
|
|
registered_instance_t *inst = (*extenderInstances)[i];
|
|
inst->obj->RegisterInstance(isolate, inst->name, inst->auto_destroy);
|
|
}
|
|
|
|
TryCatch try_catch;
|
|
|
|
// Compile the source code.
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
// Compile the source code.
|
|
MaybeLocal<v8::Script> script;
|
|
LoadScript(&script, isolate, scriptData.c_str(), fileName.c_str());
|
|
#else
|
|
// Create a string containing the JavaScript source code.
|
|
Handle<String> source = String::NewFromUtf8(isolate, scriptData.c_str());
|
|
Handle<Script> script = Script::Compile(source, Local<Value>::New(isolate, String::NewFromUtf8(isolate, fileName.c_str())));
|
|
#endif
|
|
|
|
if (try_catch.HasCaught()) {
|
|
res = JSMain::GetExceptionInfo(isolate, &try_catch);
|
|
isError = true;
|
|
} else {
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
// Run the script
|
|
Handle<Value> result;
|
|
|
|
if (!script.IsEmpty()) {
|
|
result = script.ToLocalChecked()->Run();
|
|
}
|
|
#else
|
|
// Run the script
|
|
Handle<Value> result = script->Run();
|
|
#endif
|
|
if (try_catch.HasCaught()) {
|
|
res = JSMain::GetExceptionInfo(isolate, &try_catch);
|
|
isError = true;
|
|
} else {
|
|
if (forcedTermination) {
|
|
forcedTermination = false;
|
|
res = GetForcedTerminationMessage();
|
|
}
|
|
|
|
// return result as string.
|
|
String::Utf8Value ascii(result);
|
|
if (*ascii) {
|
|
res = *ascii;
|
|
}
|
|
#ifndef V8_FORCE_GC_AFTER_EXECUTION
|
|
DisposeActiveInstances();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
isolate->SetData(0, NULL);
|
|
}
|
|
|
|
#ifdef V8_FORCE_GC_AFTER_EXECUTION
|
|
V8::ContextDisposedNotification();
|
|
while (!V8::IdleNotification()) {}
|
|
DisposeActiveInstances();
|
|
#endif
|
|
}
|
|
//isolate->Exit();
|
|
|
|
if (resultIsError) {
|
|
*resultIsError = isError;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/** Add a class to be used in JS, the definition passed here must never be destroyed */
|
|
void JSMain::AddJSExtenderClass(const js_class_definition_t *method)
|
|
{
|
|
extenderClasses->push_back(method);
|
|
}
|
|
|
|
void JSMain::AddJSExtenderFunction(FunctionCallback func, const string& name)
|
|
{
|
|
if (!func || name.length() == 0) {
|
|
return;
|
|
}
|
|
|
|
js_function_t *proc = (js_function_t *)malloc(sizeof(*proc));
|
|
|
|
if (proc) {
|
|
memset(proc, 0, sizeof(*proc));
|
|
|
|
proc->func = func;
|
|
js_strdup(proc->name, name.c_str());
|
|
|
|
extenderFunctions->push_back(proc);
|
|
}
|
|
}
|
|
|
|
void JSMain::AddJSExtenderInstance(JSBase *instance, const string& objectName, bool autoDestroy)
|
|
{
|
|
registered_instance_t *inst = (registered_instance_t *)malloc(sizeof(*inst));
|
|
|
|
if (inst) {
|
|
memset(inst, 0, sizeof(*inst));
|
|
|
|
inst->obj = instance;
|
|
if (objectName.size() > 0) js_strdup(inst->name, objectName.c_str());
|
|
inst->auto_destroy = autoDestroy;
|
|
|
|
extenderInstances->push_back(inst);
|
|
}
|
|
}
|
|
|
|
JSMain *JSMain::GetScriptInstanceFromIsolate(Isolate* isolate)
|
|
{
|
|
if (isolate) {
|
|
void *p = isolate->GetData(0);
|
|
if (p) {
|
|
return static_cast<JSMain *>(p);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Isolate *JSMain::GetIsolate()
|
|
{
|
|
return isolate;
|
|
}
|
|
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
void JSMain::Initialize(v8::Platform **platform)
|
|
{
|
|
bool res = V8::InitializeICUDefaultLocation(SWITCH_GLOBAL_dirs.mod_dir);
|
|
V8::InitializeExternalStartupData(SWITCH_GLOBAL_dirs.mod_dir);
|
|
|
|
*platform = v8::platform::CreateDefaultPlatform();
|
|
V8::InitializePlatform(*platform);
|
|
V8::Initialize();
|
|
}
|
|
#else
|
|
void JSMain::Initialize()
|
|
{
|
|
V8::InitializeICU(); // Initialize();
|
|
}
|
|
#endif
|
|
|
|
void JSMain::Dispose()
|
|
{
|
|
// Make sure to cleanup properly!
|
|
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
|
|
v8::Isolate::GetCurrent()->LowMemoryNotification();
|
|
while (!v8::Isolate::GetCurrent()->IdleNotificationDeadline(0.500)) {}
|
|
V8::Dispose();
|
|
V8::ShutdownPlatform();
|
|
#else
|
|
V8::LowMemoryNotification();
|
|
while (!V8::IdleNotification()) {}
|
|
V8::Dispose();
|
|
#endif
|
|
|
|
}
|
|
|
|
const vector<const js_class_definition_t *>& JSMain::GetExtenderClasses() const
|
|
{
|
|
return *extenderClasses;
|
|
}
|
|
|
|
const vector<js_function_t *>& JSMain::GetExtenderFunctions() const
|
|
{
|
|
return *extenderFunctions;
|
|
}
|
|
|
|
const vector<registered_instance_t*>& JSMain::GetExtenderInstances() const
|
|
{
|
|
return *extenderInstances;
|
|
}
|
|
|
|
void JSMain::AddActiveInstance(JSBase *obj)
|
|
{
|
|
activeInstances->insert(obj);
|
|
}
|
|
|
|
void JSMain::RemoveActiveInstance(JSBase *obj)
|
|
{
|
|
if (obj) {
|
|
set<JSBase *>::iterator it = activeInstances->find(obj);
|
|
|
|
if (it != activeInstances->end()) {
|
|
activeInstances->erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JSMain::DisposeActiveInstances()
|
|
{
|
|
set<JSBase *>::iterator it = activeInstances->begin();
|
|
size_t c = activeInstances->size();
|
|
|
|
while (it != activeInstances->end()) {
|
|
JSBase *obj = *it;
|
|
delete obj; /* After this, the iteratior might be invalid, since the slot in the set might be removed already */
|
|
|
|
if (c == activeInstances->size()) {
|
|
/* Nothing changed in the set, make sure to manually remove this instance from the set */
|
|
activeInstances->erase(it);
|
|
}
|
|
|
|
it = activeInstances->begin();
|
|
c = activeInstances->size();
|
|
}
|
|
}
|
|
|
|
bool JSMain::GetForcedTermination(void)
|
|
{
|
|
return forcedTermination;
|
|
}
|
|
|
|
void JSMain::ResetForcedTermination(void)
|
|
{
|
|
forcedTermination = false;
|
|
}
|
|
|
|
const char *JSMain::GetForcedTerminationMessage(void)
|
|
{
|
|
return js_safe_str(forcedTerminationMessage);
|
|
}
|
|
|
|
const char *JSMain::GetForcedTerminationScriptFile(void)
|
|
{
|
|
return js_safe_str(forcedTerminationScriptFile);
|
|
}
|
|
|
|
int JSMain::GetForcedTerminationLineNumber(void)
|
|
{
|
|
return forcedTerminationLineNumber;
|
|
}
|
|
|
|
void JSMain::ExitScript(Isolate *isolate, const char *msg)
|
|
{
|
|
if (!isolate) {
|
|
return;
|
|
}
|
|
|
|
JSMain *js = JSMain::GetScriptInstanceFromIsolate(isolate);
|
|
|
|
if (js) {
|
|
js->forcedTermination = true;
|
|
|
|
/* Free old data if it exists already */
|
|
if (js->forcedTerminationMessage) {
|
|
free(js->forcedTerminationMessage);
|
|
js->forcedTerminationMessage = NULL;
|
|
}
|
|
|
|
if (js->forcedTerminationScriptFile) {
|
|
free(js->forcedTerminationScriptFile);
|
|
js->forcedTerminationScriptFile = NULL;
|
|
}
|
|
|
|
/* Save message for later use */
|
|
if (msg) {
|
|
js_strdup(js->forcedTerminationMessage, msg);
|
|
}
|
|
|
|
js->forcedTerminationScriptFile = GetStackInfo(isolate, &js->forcedTerminationLineNumber);
|
|
}
|
|
|
|
V8::TerminateExecution(isolate);
|
|
}
|
|
|
|
char *JSMain::GetStackInfo(Isolate *isolate, int *lineNumber)
|
|
{
|
|
HandleScope handle_scope(isolate);
|
|
const char *file = __FILE__; /* Use current filename if we can't find the correct from JS stack */
|
|
int line = __LINE__; /* Use current line number if we can't find the correct from JS stack */
|
|
char *ret = NULL;
|
|
|
|
/* Try to get the current stack trace (script file) */
|
|
Local<StackTrace> stFile = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName);
|
|
|
|
if (!stFile.IsEmpty()) {
|
|
Local<StackFrame> sf = stFile->GetFrame(0);
|
|
|
|
if (!sf.IsEmpty()) {
|
|
Local<String> fn = sf->GetScriptName();
|
|
|
|
if (!fn.IsEmpty()) {
|
|
String::Utf8Value str(fn);
|
|
|
|
if (*str) {
|
|
js_strdup(ret, *str); // We must dup here
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* dup current filename if we got nothing from stack */
|
|
if (ret == NULL) {
|
|
js_strdup(ret, file);
|
|
}
|
|
|
|
/* Try to get the current stack trace (line number) */
|
|
if (lineNumber) {
|
|
*lineNumber = 0;
|
|
|
|
Local<StackTrace> stLine = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kLineNumber);
|
|
|
|
if (!stLine.IsEmpty()) {
|
|
Local<StackFrame> sf = stLine->GetFrame(0);
|
|
|
|
if (!sf.IsEmpty()) {
|
|
*lineNumber = sf->GetLineNumber();
|
|
}
|
|
}
|
|
|
|
/* Use current file number if we got nothing from stack */
|
|
if (*lineNumber == 0) {
|
|
*lineNumber = line;
|
|
}
|
|
}
|
|
|
|
/* Return dup'ed value - this must be freed by the calling function */
|
|
return ret;
|
|
}
|
|
|
|
/* For Emacs:
|
|
* Local Variables:
|
|
* mode:c
|
|
* indent-tabs-mode:t
|
|
* tab-width:4
|
|
* c-basic-offset:4
|
|
* End:
|
|
* For VIM:
|
|
* vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
|
|
*/
|