1 /*
2 * mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3 * Copyright (C) 2013-2014, Peter Olsson <peter@olssononline.se>
4 *
5 * Version: MPL 1.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is mod_v8 for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18 *
19 * The Initial Developer of the Original Code is
20 * Peter Olsson <peter@olssononline.se>
21 * Portions created by the Initial Developer are Copyright (C)
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 * Peter Olsson <peter@olssononline.se>
26 *
27 * jsmain.cpp -- JavaScript Main V8 script runner
28 *
29 */
30
31 #include "javascript.hpp"
32 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
33 #include "mod_v8.h"
34 #endif
35
36 #ifdef V8_ENABLE_DEBUGGING
37 #include <v8-debug.h>
38 #endif
39
40 #include <stdio.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <string>
44 #include <iostream>
45 #include <sstream>
46 #include <fstream>
47 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
48 #include <switch.h>
49 #endif
50
51 using namespace std;
52 using namespace v8;
53
54 #ifdef V8_ENABLE_DEBUGGING
V8DispatchDebugMessages()55 void V8DispatchDebugMessages()
56 {
57 Isolate* isolate = Isolate::GetCurrent();
58 Persistent<Context> *persistent_contect = (Persistent<Context> *)isolate->GetData(1);
59 HandleScope handle_scope(isolate);
60 Local<Context> context = Local<Context>::New(isolate, *persistent_contect);
61 Context::Scope scope(context);
62
63 Debug::ProcessDebugMessages();
64 }
65 #endif
66
FileExists(const char * file)67 bool JSMain::FileExists(const char *file)
68 {
69 ifstream fh(file);
70 bool file_exists = false;
71
72 if (fh) {
73 fh.close();
74 file_exists = true;
75 }
76
77 return file_exists;
78 }
79
LoadFileToString(const string & filename)80 const string JSMain::LoadFileToString(const string& filename)
81 {
82 if (filename.length() == 0) {
83 return "";
84 }
85
86 ifstream in(filename.c_str(), std::ios::in | std::ios::binary);
87
88 if (in) {
89 string contents;
90
91 in.seekg(0, std::ios::end);
92 contents.resize((size_t)in.tellg());
93
94 in.seekg(0, std::ios::beg);
95 in.read(&contents[0], contents.size());
96 in.close();
97
98 return contents;
99 }
100
101 return "";
102 }
103
JSMain(void)104 JSMain::JSMain(void)
105 {
106 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
107 Isolate::CreateParams params;
108 params.array_buffer_allocator =
109 v8::ArrayBuffer::Allocator::NewDefaultAllocator();
110 isolate = Isolate::New(params);
111 #else
112 isolate = Isolate::New();
113 #endif
114
115 extenderClasses = new vector<const js_class_definition_t *>();
116 extenderFunctions = new vector<js_function_t *>();
117 extenderInstances = new vector<registered_instance_t*>();
118 activeInstances = new set<JSBase *>();
119
120 forcedTermination = false;
121 forcedTerminationMessage = NULL;
122 forcedTerminationLineNumber = 0;
123 forcedTerminationScriptFile = NULL;
124 }
125
~JSMain(void)126 JSMain::~JSMain(void)
127 {
128 bool enteredIsolate = false;
129
130 for (size_t i = 0; i < extenderInstances->size(); i++) {
131 registered_instance_t *inst = (*extenderInstances)[i];
132
133 if (inst) {
134 if (inst->name) free(inst->name);
135 free(inst);
136 }
137 }
138
139 for (size_t i = 0; i < extenderFunctions->size(); i++) {
140 js_function_t *proc = (*extenderFunctions)[i];
141
142 if (proc) {
143 if (proc->name) free((char *)proc->name);
144 free(proc);
145 }
146 }
147
148 extenderInstances->clear();
149 extenderClasses->clear();
150 extenderFunctions->clear();
151
152 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
153 if (isolate) {
154 #else
155 if (!Isolate::GetCurrent()) {
156 #endif
157 enteredIsolate = true;
158 isolate->Enter();
159 }
160
161 /* It's probably not totally safe to call this here, but it whould always be empty by now anyway */
162 DisposeActiveInstances();
163
164 if (enteredIsolate) {
165 isolate->Exit();
166 }
167
168 delete extenderClasses;
169 delete extenderFunctions;
170 delete extenderInstances;
171 delete activeInstances;
172
173 if (forcedTerminationMessage) free(forcedTerminationMessage);
174 if (forcedTerminationScriptFile) free(forcedTerminationScriptFile);
175
176 isolate->Dispose();
177 }
178
179 const string JSMain::GetExceptionInfo(Isolate* isolate, TryCatch* try_catch)
180 {
181 HandleScope handle_scope(isolate);
182 String::Utf8Value exception(try_catch->Exception());
183 const char *exception_string = js_safe_str(*exception);
184 Handle<Message> message = try_catch->Message();
185 string res;
186
187 if (message.IsEmpty()) {
188 // V8 didn't provide any extra information about this error; just return the exception.
189 res = exception_string;
190 } else {
191 String::Utf8Value filename(message->GetScriptResourceName());
192 const char *filename_string = js_safe_str(*filename);
193 int linenum = message->GetLineNumber();
194
195 ostringstream ss;
196
197 ss << filename_string << ":" << linenum << ": " << exception_string << "\r\n";
198
199 // Print line of source code.
200 String::Utf8Value sourceline(message->GetSourceLine());
201 const char *sourceline_string = js_safe_str(*sourceline);
202
203 ss << sourceline_string << "\r\n";
204
205 // Print wavy underline.
206 int start = message->GetStartColumn();
207
208 for (int i = 0; i < start; i++) {
209 ss << " ";
210 }
211
212 int32_t end = message->GetEndColumn(isolate->GetCurrentContext()).FromMaybe(0);
213
214 for (int i = start; i < end; i++) {
215 ss << "^";
216 }
217
218 res = ss.str();
219 }
220
221 return res;
222 }
223
224 void JSMain::Include(const v8::FunctionCallbackInfo<Value>& args)
225 {
226 for (int i = 0; i < args.Length(); i++) {
227 HandleScope handle_scope(args.GetIsolate());
228 String::Utf8Value str(args[i]);
229
230 // load_file loads the file with this name into a string
231 string js_file = LoadFileToString(js_safe_str(*str));
232
233 if (js_file.length() > 0) {
234 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
235 MaybeLocal<v8::Script> script;
236 LoadScript(&script, args.GetIsolate(), js_file.c_str(), js_safe_str(*str));
237
238 if (script.IsEmpty()) {
239 args.GetReturnValue().Set(false);
240 }
241 else {
242 args.GetReturnValue().Set(script.ToLocalChecked()->Run());
243 }
244 #else
245 Handle<String> source = String::NewFromUtf8(args.GetIsolate(), js_file.c_str());
246 Handle<Script> script = Script::Compile(source, args[i]);
247 args.GetReturnValue().Set(script->Run());
248 #endif
249
250 return;
251 }
252 }
253
254 args.GetReturnValue().Set(Undefined(args.GetIsolate()));
255 }
256
257 void JSMain::Log(const v8::FunctionCallbackInfo<Value>& args)
258 {
259 HandleScope handle_scope(args.GetIsolate());
260 String::Utf8Value str(args[0]);
261
262 printf("%s\r\n", js_safe_str(*str));
263
264 args.GetReturnValue().Set(Undefined(args.GetIsolate()));
265 }
266
267 const string JSMain::ExecuteScript(const string& filename, bool *resultIsError)
268 {
269 // Get the file and load into a string.
270 string data = LoadFileToString(filename);
271
272 return ExecuteString(data, filename, resultIsError);
273 }
274
275 const string JSMain::ExecuteString(const string& scriptData, const string& fileName, bool *resultIsError)
276 {
277 string res;
278 bool isError = false;
279
280 //solate->Enter();
281 {
282 Locker lock(isolate);
283 Isolate::Scope iscope(isolate);
284 {
285 // Create a stack-allocated handle scope.
286 HandleScope scope(isolate);
287
288 isolate->SetData(0, this);
289
290 Handle<ObjectTemplate> global = ObjectTemplate::New(isolate);
291 global->Set(String::NewFromUtf8(isolate, "include"), FunctionTemplate::New(isolate, Include));
292 global->Set(String::NewFromUtf8(isolate, "require"), FunctionTemplate::New(isolate, Include));
293 global->Set(String::NewFromUtf8(isolate, "log"), FunctionTemplate::New(isolate, Log));
294
295 for (size_t i = 0; i < extenderFunctions->size(); i++) {
296 js_function_t *proc = (*extenderFunctions)[i];
297 global->Set(String::NewFromUtf8(isolate, proc->name), FunctionTemplate::New(isolate, proc->func));
298 }
299
300 // Create a new context.
301 Local<Context> context = Context::New(isolate, NULL, global);
302
303 if (context.IsEmpty()) {
304 return "Failed to create new JS context";
305 }
306
307 // Enter the created context for compiling and running the script.
308 Context::Scope context_scope(context);
309
310 // Register all plugins.
311 for (size_t i = 0; i < extenderClasses->size(); i++) {
312 JSBase::Register(isolate, (*extenderClasses)[i]);
313 }
314
315 // Register all instances.
316 for (size_t i = 0; i < extenderInstances->size(); i++) {
317 registered_instance_t *inst = (*extenderInstances)[i];
318 inst->obj->RegisterInstance(isolate, inst->name, inst->auto_destroy);
319 }
320
321 TryCatch try_catch(isolate);
322
323 // Compile the source code.
324 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
325 // Compile the source code.
326 MaybeLocal<v8::Script> script;
327 LoadScript(&script, isolate, scriptData.c_str(), fileName.c_str());
328 #else
329 // Create a string containing the JavaScript source code.
330 Handle<String> source = String::NewFromUtf8(isolate, scriptData.c_str());
331 Handle<Script> script = Script::Compile(source, Local<Value>::New(isolate, String::NewFromUtf8(isolate, fileName.c_str())));
332 #endif
333
334 if (try_catch.HasCaught()) {
335 res = JSMain::GetExceptionInfo(isolate, &try_catch);
336 isError = true;
337 } else {
338 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
339 // Run the script
340 Handle<Value> result;
341
342 if (!script.IsEmpty()) {
343 result = script.ToLocalChecked()->Run();
344 }
345 #else
346 // Run the script
347 Handle<Value> result = script->Run();
348 #endif
349 if (try_catch.HasCaught()) {
350 res = JSMain::GetExceptionInfo(isolate, &try_catch);
351 isError = true;
352 } else {
353 if (forcedTermination) {
354 forcedTermination = false;
355 res = GetForcedTerminationMessage();
356 }
357
358 // return result as string.
359 String::Utf8Value ascii(result);
360 if (*ascii) {
361 res = *ascii;
362 }
363 #ifndef V8_FORCE_GC_AFTER_EXECUTION
364 DisposeActiveInstances();
365 #endif
366 }
367 }
368
369 isolate->SetData(0, NULL);
370 }
371
372 #ifdef V8_FORCE_GC_AFTER_EXECUTION
373 V8::ContextDisposedNotification();
374 while (!V8::IdleNotification()) {}
375 DisposeActiveInstances();
376 #endif
377 }
378 //isolate->Exit();
379
380 if (resultIsError) {
381 *resultIsError = isError;
382 }
383
384 return res;
385 }
386
387 /** Add a class to be used in JS, the definition passed here must never be destroyed */
388 void JSMain::AddJSExtenderClass(const js_class_definition_t *method)
389 {
390 extenderClasses->push_back(method);
391 }
392
393 void JSMain::AddJSExtenderFunction(FunctionCallback func, const string& name)
394 {
395 if (!func || name.length() == 0) {
396 return;
397 }
398
399 js_function_t *proc = (js_function_t *)malloc(sizeof(*proc));
400
401 if (proc) {
402 memset(proc, 0, sizeof(*proc));
403
404 proc->func = func;
405 js_strdup(proc->name, name.c_str());
406
407 extenderFunctions->push_back(proc);
408 }
409 }
410
411 void JSMain::AddJSExtenderInstance(JSBase *instance, const string& objectName, bool autoDestroy)
412 {
413 registered_instance_t *inst = (registered_instance_t *)malloc(sizeof(*inst));
414
415 if (inst) {
416 memset(inst, 0, sizeof(*inst));
417
418 inst->obj = instance;
419 if (objectName.size() > 0) js_strdup(inst->name, objectName.c_str());
420 inst->auto_destroy = autoDestroy;
421
422 extenderInstances->push_back(inst);
423 }
424 }
425
426 JSMain *JSMain::GetScriptInstanceFromIsolate(Isolate* isolate)
427 {
428 if (isolate) {
429 void *p = isolate->GetData(0);
430 if (p) {
431 return static_cast<JSMain *>(p);
432 }
433 }
434
435 return NULL;
436 }
437
438 Isolate *JSMain::GetIsolate()
439 {
440 return isolate;
441 }
442
443 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
444 void JSMain::Initialize(v8::Platform **platform)
445 {
446 V8::InitializeICUDefaultLocation(SWITCH_GLOBAL_dirs.mod_dir);
447 V8::InitializeExternalStartupData(SWITCH_GLOBAL_dirs.mod_dir);
448
449 *platform = v8::platform::CreateDefaultPlatform();
450 V8::InitializePlatform(*platform);
451 V8::Initialize();
452 }
453 #else
454 void JSMain::Initialize()
455 {
456 V8::InitializeICU(); // Initialize();
457 }
458 #endif
459
460 void JSMain::Dispose()
461 {
462 // Make sure to cleanup properly!
463 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
464 v8::Isolate::GetCurrent()->LowMemoryNotification();
465 while (!v8::Isolate::GetCurrent()->IdleNotificationDeadline(0.500)) {}
466 V8::Dispose();
467 V8::ShutdownPlatform();
468 #else
469 V8::LowMemoryNotification();
470 while (!V8::IdleNotification()) {}
471 V8::Dispose();
472 #endif
473
474 }
475
476 const vector<const js_class_definition_t *>& JSMain::GetExtenderClasses() const
477 {
478 return *extenderClasses;
479 }
480
481 const vector<js_function_t *>& JSMain::GetExtenderFunctions() const
482 {
483 return *extenderFunctions;
484 }
485
486 const vector<registered_instance_t*>& JSMain::GetExtenderInstances() const
487 {
488 return *extenderInstances;
489 }
490
491 void JSMain::AddActiveInstance(JSBase *obj)
492 {
493 activeInstances->insert(obj);
494 }
495
496 void JSMain::RemoveActiveInstance(JSBase *obj)
497 {
498 if (obj) {
499 set<JSBase *>::iterator it = activeInstances->find(obj);
500
501 if (it != activeInstances->end()) {
502 activeInstances->erase(it);
503 }
504 }
505 }
506
507 void JSMain::DisposeActiveInstances()
508 {
509 set<JSBase *>::iterator it = activeInstances->begin();
510 size_t c = activeInstances->size();
511
512 while (it != activeInstances->end()) {
513 JSBase *obj = *it;
514 delete obj; /* After this, the iteratior might be invalid, since the slot in the set might be removed already */
515
516 if (c == activeInstances->size()) {
517 /* Nothing changed in the set, make sure to manually remove this instance from the set */
518 activeInstances->erase(it);
519 }
520
521 it = activeInstances->begin();
522 c = activeInstances->size();
523 }
524 }
525
526 bool JSMain::GetForcedTermination(void)
527 {
528 return forcedTermination;
529 }
530
531 void JSMain::ResetForcedTermination(void)
532 {
533 forcedTermination = false;
534 }
535
536 const char *JSMain::GetForcedTerminationMessage(void)
537 {
538 return js_safe_str(forcedTerminationMessage);
539 }
540
541 const char *JSMain::GetForcedTerminationScriptFile(void)
542 {
543 return js_safe_str(forcedTerminationScriptFile);
544 }
545
546 int JSMain::GetForcedTerminationLineNumber(void)
547 {
548 return forcedTerminationLineNumber;
549 }
550
551 void JSMain::ExitScript(Isolate *isolate, const char *msg, bool jskill)
552 {
553 if (!isolate) {
554 return;
555 }
556
557 JSMain *js = JSMain::GetScriptInstanceFromIsolate(isolate);
558
559 if (js) {
560 js->forcedTermination = true;
561
562 /* Free old data if it exists already */
563 if (js->forcedTerminationMessage) {
564 free(js->forcedTerminationMessage);
565 js->forcedTerminationMessage = NULL;
566 }
567
568 if (js->forcedTerminationScriptFile) {
569 free(js->forcedTerminationScriptFile);
570 js->forcedTerminationScriptFile = NULL;
571 }
572
573 /* Save message for later use */
574 if (msg) {
575 js_strdup(js->forcedTerminationMessage, msg);
576 }
577
578 /* When forcefully killed, don't call GetStackInfo() because isolate is locked by another thread */
579 if (!jskill) {
580 js->forcedTerminationScriptFile = GetStackInfo(isolate, &js->forcedTerminationLineNumber);
581 }
582 }
583
584 isolate->TerminateExecution();
585 }
586
587 char *JSMain::GetStackInfo(Isolate *isolate, int *lineNumber)
588 {
589 HandleScope handle_scope(isolate);
590 const char *file = __FILE__; /* Use current filename if we can't find the correct from JS stack */
591 int line = __LINE__; /* Use current line number if we can't find the correct from JS stack */
592 char *ret = NULL;
593
594 /* Try to get the current stack trace (script file) */
595 Local<StackTrace> stFile = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kScriptName);
596
597 if (!stFile.IsEmpty()) {
598 Local<StackFrame> sf = stFile->GetFrame(0);
599
600 if (!sf.IsEmpty()) {
601 Local<String> fn = sf->GetScriptName();
602
603 if (!fn.IsEmpty()) {
604 String::Utf8Value str(fn);
605
606 if (*str) {
607 js_strdup(ret, *str); // We must dup here
608 }
609 }
610 }
611 }
612
613 /* dup current filename if we got nothing from stack */
614 if (ret == NULL) {
615 js_strdup(ret, file);
616 }
617
618 /* Try to get the current stack trace (line number) */
619 if (lineNumber) {
620 *lineNumber = 0;
621
622 Local<StackTrace> stLine = StackTrace::CurrentStackTrace(isolate, 1, StackTrace::kLineNumber);
623
624 if (!stLine.IsEmpty()) {
625 Local<StackFrame> sf = stLine->GetFrame(0);
626
627 if (!sf.IsEmpty()) {
628 *lineNumber = sf->GetLineNumber();
629 }
630 }
631
632 /* Use current file number if we got nothing from stack */
633 if (*lineNumber == 0) {
634 *lineNumber = line;
635 }
636 }
637
638 /* Return dup'ed value - this must be freed by the calling function */
639 return ret;
640 }
641
642 /* For Emacs:
643 * Local Variables:
644 * mode:c
645 * indent-tabs-mode:t
646 * tab-width:4
647 * c-basic-offset:4
648 * End:
649 * For VIM:
650 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
651 */
652