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