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  * javascript.hpp -- Header file for main JavaScript classes
28  *
29  */
30 
31 #ifndef V8_JAVASCRIPT_H
32 #define V8_JAVASCRIPT_H
33 
34 #include <stdint.h>
35 #include <v8.h>
36 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
37 #include <libplatform/libplatform.h>
38 #include <v8-util.h>
39 #endif
40 
41 #include <string>
42 #include <vector>
43 #include <set>
44 #include <assert.h>
45 
46 /* Enable this define enable V8 debugging protocol, this is not yet working */
47 //#define V8_ENABLE_DEBUGGING
48 
49 /*
50  * Enable this define to force a GC after the script has finished execution.
51  * This is only to help debug memory leaks, and should not be needed for anything else
52  */
53 //#define V8_FORCE_GC_AFTER_EXECUTION
54 
55 
56 /* Macro for easy V8 "get property" callback definition */
57 #define JS_GET_PROPERTY_DEF(method_name, class_name) \
58 	static void method_name(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)\
59 	{\
60 		JS_CHECK_SCRIPT_STATE();\
61 		class_name *obj = JSBase::GetInstance<class_name>(info.Holder());\
62 		if (obj) {\
63 			obj->method_name##Impl(property, info);\
64 		} else {\
65 			int line;\
66 			char *file = JSMain::GetStackInfo(info.GetIsolate(), &line);\
67 			v8::String::Utf8Value str(info.Holder());\
68 			switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, "mod_v8", line, NULL, SWITCH_LOG_DEBUG, "No valid internal data available for %s when calling %s\n", *str ? *str : "[unknown]", #class_name "::" #method_name "()");\
69 			free(file);\
70 			info.GetReturnValue().Set(false);\
71 		}\
72 	}\
73 	void method_name##Impl(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
74 
75 /* Macro for easy V8 "set property" callback definition */
76 #define JS_SET_PROPERTY_DEF(method_name, class_name) \
77 	static void method_name(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)\
78 	{\
79 		JS_CHECK_SCRIPT_STATE();\
80 		class_name *obj = JSBase::GetInstance<class_name>(info.Holder());\
81 		if (obj) {\
82 			obj->method_name##Impl(property, value, info);\
83 		} else {\
84 			int line;\
85 			char *file = JSMain::GetStackInfo(info.GetIsolate(), &line);\
86 			v8::String::Utf8Value str(info.Holder());\
87 			switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, "mod_v8", line, NULL, SWITCH_LOG_DEBUG, "No valid internal data available for %s when calling %s\n", *str ? *str : "[unknown]", #class_name "::" #method_name "()");\
88 			free(file);\
89 			info.GetReturnValue().Set(false);\
90 		}\
91 	}\
92 	void method_name##Impl(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
93 
94 /* Macro for easy V8 "function" callback definition */
95 #define JS_FUNCTION_DEF(method_name, class_name) \
96 	static void method_name(const v8::FunctionCallbackInfo<v8::Value>& info)\
97 	{\
98 		JS_CHECK_SCRIPT_STATE();\
99 		class_name *obj = JSBase::GetInstance<class_name>(info.Holder());\
100 		if (obj) {\
101 			obj->method_name##Impl(info);\
102 		} else {\
103 			int line;\
104 			char *file = JSMain::GetStackInfo(info.GetIsolate(), &line);\
105 			v8::String::Utf8Value str(info.Holder());\
106 			switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, "mod_v8", line, NULL, SWITCH_LOG_DEBUG, "No valid internal data available for %s when calling %s\n", *str ? *str : "[unknown]", #class_name "::" #method_name "()");\
107 			free(file);\
108 			info.GetReturnValue().Set(false);\
109 		}\
110 	}\
111 	void method_name##Impl(const v8::FunctionCallbackInfo<v8::Value>& info)
112 
113 /* Macros for V8 callback implementations */
114 #define JS_GET_PROPERTY_IMPL(method_name, class_name) void class_name::method_name##Impl(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
115 #define JS_SET_PROPERTY_IMPL(method_name, class_name) void class_name::method_name##Impl(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
116 #define JS_FUNCTION_IMPL(method_name, class_name) void class_name::method_name##Impl(const v8::FunctionCallbackInfo<v8::Value>& info)
117 
118 /* Macros for V8 callback definitions (class static version) */
119 #define JS_GET_PROPERTY_DEF_STATIC(method_name) static void method_name(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
120 #define JS_SET_PROPERTY_DEF_STATIC(method_name) static void method_name(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
121 #define JS_FUNCTION_DEF_STATIC(method_name) static void method_name(const v8::FunctionCallbackInfo<v8::Value>& info)
122 
123 /* Macros for V8 callback implementations (class static version) */
124 #define JS_GET_PROPERTY_IMPL_STATIC(method_name, class_name) void class_name::method_name(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
125 #define JS_SET_PROPERTY_IMPL_STATIC(method_name, class_name) void class_name::method_name(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info)
126 #define JS_FUNCTION_IMPL_STATIC(method_name, class_name) void class_name::method_name(const v8::FunctionCallbackInfo<v8::Value>& info)
127 
128 /* Macro for basic script state check (to know if the script is being terminated), should be called before calling any callback actual code */
129 #define JS_CHECK_SCRIPT_STATE() \
130 	if (info.GetIsolate()->IsExecutionTerminating()) return;\
131 	if (JSMain::GetScriptInstanceFromIsolate(info.GetIsolate()) && JSMain::GetScriptInstanceFromIsolate(info.GetIsolate())->GetForcedTermination()) return
132 
133 /* Macro for easy unlocking an isolate on a long running c call */
134 #define JS_EXECUTE_LONG_RUNNING_C_CALL_WITH_UNLOCKER(call) {\
135 		/* Unlock isolate on a long running c call to let another thread execute the callback */\
136 		info.GetIsolate()->Exit();\
137 		Unlocker unlock(info.GetIsolate());\
138 		call;\
139 	}\
140 	/* Lock it back */\
141 	info.GetIsolate()->Enter();
142 
143 /* strdup function for all platforms */
144 #ifdef NDEBUG
145 #if (_MSC_VER >= 1500)			// VC9+
146 #define js_strdup(ptr, s) (void)( (!!(ptr = _strdup(s))) || (fprintf(stderr,"ABORT! Malloc failure at: %s:%d", __FILE__, __LINE__),abort(), 0), ptr)
147 #else
148 #define js_strdup(ptr, s) (void)( (!!(ptr = strdup(s))) || (fprintf(stderr,"ABORT! Malloc failure at: %s:%d", __FILE__, __LINE__),abort(), 0), ptr)
149 #endif
150 #else
151 #if (_MSC_VER >= 1500)			// VC9+
152 #define js_strdup(ptr, s) (void)(assert(((ptr) = _strdup(s))),ptr);__analysis_assume( ptr )
153 #else
154 #define js_strdup(ptr, s) (void)(assert(((ptr) = strdup((s)))),ptr)
155 #endif
156 #endif
157 
158 /* Makes sure to return a valid char pointer */
159 #define js_safe_str(s) (s ? s : "")
160 
161 /* JS Constructor callback definition */
162 typedef void * void_pointer_t;
163 typedef void_pointer_t (*ConstructorCallback)(const v8::FunctionCallbackInfo<v8::Value>& info);
164 
165 /* JS Function definition */
166 typedef struct {
167 	const char *name;						/* Name of the function */
168 	v8::FunctionCallback func;				/* Function callback */
169 } js_function_t;
170 
171 /* JS Property definition */
172 typedef struct {
173 	const char *name;						/* Name of the property */
174 	v8::AccessorGetterCallback get;			/* The property getter */
175 	v8::AccessorSetterCallback set;			/* The property setter */
176 } js_property_t;
177 
178 /* JS Class definition */
179 typedef struct {
180 	const char *name;						/* The name of the class */
181 	ConstructorCallback constructor;		/* The constructor definition */
182 	const js_function_t *functions;			/* An array of function definitions */
183 	const js_property_t *properties;		/* An array of property definitions */
184 } js_class_definition_t;
185 
186 /* Import/export definitions (used by extra loadable modules) */
187 #ifdef WIN32
188 /* WIN32 */
189 #ifdef JSMOD_IMPORT
190 #define JSMOD_EXPORT __declspec(dllimport)
191 #else
192 #define JSMOD_EXPORT __declspec(dllexport)
193 #endif
194 #else
195 /* Not WIN32 */
196 #ifdef JSMOD_IMPORT
197 #define JSMOD_EXPORT
198 #else
199 #if (HAVE_VISIBILITY != 1)
200 #define JSMOD_EXPORT
201 #else
202 #define JSMOD_EXPORT __attribute__ ((visibility("default")))
203 #endif
204 #endif
205 #endif
206 
207 /* JSMain class prototype */
208 class JSMOD_EXPORT JSMain;
209 
210 /* Base class used by all C++ classes implemented in JS */
211 class JSMOD_EXPORT JSBase
212 {
213 private:
214 	v8::Persistent<v8::Object> *persistentHandle;	/* The persistent handle of the JavaScript object for this instance */
215 	bool autoDestroy;								/* flag to tell if this instance should be auto destroyed during JavaScript GC */
216 	JSMain *js;										/* The "owner" of this instance */
217 
218 	/* The callback that happens when the V8 GC cleans up object instances */
219 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
220 	static void WeakCallback(const v8::WeakCallbackInfo<JSBase>& data);
221 #else
222 	static void WeakCallback(const v8::WeakCallbackData<v8::Object, JSBase>& data);
223 #endif
224 
225 	/* Internal basic constructor when creating a new instance from JS. It will call the actual user code inside */
226 	static void CreateInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
227 
228 	/* Store a C++ instance to a JS object's private data */
229 	static void AddInstance(v8::Isolate *isolate, const v8::Handle<v8::Object>& handle, const v8::Handle<v8::External>& object, bool autoDestroy);
230 public:
231 	JSBase(JSMain *owner);
232 	JSBase(const v8::FunctionCallbackInfo<v8::Value>& info);
233 	virtual ~JSBase(void);
234 
235 	/* Returns the JS object related to the C++ instance */
236 	v8::Handle<v8::Object> GetJavaScriptObject();
237 
238 	/* Register a C++ class inside V8 (must be called within a entered isolate, and context) */
239 	static void Register(v8::Isolate *isolate, const js_class_definition_t *desc);
240 
241 	/* Register an existing C++ class instance inside V8 (must be called within a entered isolate, and context) */
242 	void RegisterInstance(v8::Isolate *isolate, std::string name, bool autoDestroy);
243 
244 	/* Get a JSBase instance from JavaScript callback arguments  */
GetInstance(const v8::FunctionCallbackInfo<v8::Value> & info)245 	template <typename T> static T *GetInstance(const v8::FunctionCallbackInfo<v8::Value>& info)
246 	{
247 		v8::HandleScope scope(info.GetIsolate());
248 		return GetInstance<T>(info.Holder());
249 	}
250 
251 	/* Get a JSBase instance from a JavaScript object */
GetInstance(const v8::Local<v8::Object> & self)252 	template <typename T> static T *GetInstance(const v8::Local<v8::Object>& self)
253 	{
254 		v8::Local<v8::Value> val = self->GetInternalField(0);
255 
256 		if (!val.IsEmpty() && val->IsExternal()) {
257 			v8::Local<v8::External> wrap = v8::Local<v8::External>::Cast(val);
258 			JSBase *ptr = static_cast<JSBase*>(wrap->Value());
259 			return dynamic_cast<T*>(ptr); /* If we're trying to cast to the wrong type, dynamic_cast will return NULL */
260 		} else {
261 			return NULL;
262 		}
263 	}
264 
265 	/* Get a JavaScript function from a JavaScript argument (can be either a string or the actual function) */
266 	static v8::Handle<v8::Function> GetFunctionFromArg(v8::Isolate *isolate, const v8::Local<v8::Value>& arg);
267 
268 	/* Default JS setter callback, to be used for read only values */
269 	static void DefaultSetProperty(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info);
270 
271 	/* Get the name of the JavaScript class - must be overridden by the actual implementation */
272 	virtual std::string GetJSClassName() = 0;
273 
274 	/* Get the JavaScript class instance that owns this instance */
275 	JSMain *GetOwner();
276 
277 	/* Get the JavaScript isolate that's active for the current context */
278 	v8::Isolate *GetIsolate();
279 
280 	/* Get autoDestroy variable */
281 	bool GetAutoDestroy();
282 };
283 
284 /* Definition of the class registration method */
285 typedef void (*JSExtenderRegisterMethod)(js_class_definition_t *class_definition);
286 
287 /* The struct holding a C++ class instance, to be used in JS */
288 typedef struct {
289 	JSBase *obj;			/* The class instance to be used in JS */
290 	char *name;				/* The name of the instance within JS */
291 	bool auto_destroy;		/* Flag to know if the instance should be auto destroyed when not needed by JS anymore */
292 } registered_instance_t;
293 
294 /* Main class for executing a V8 JavaScript */
295 class JSMOD_EXPORT JSMain
296 {
297 private:
298 	v8::Isolate* isolate;										/* The V8 isolate for this script instance */
299 
300 	std::vector<const js_class_definition_t *> *extenderClasses;/* List holding C++ classes to be registered in JS on execution */
301 	std::vector<js_function_t *> *extenderFunctions;			/* List holding C++ functions to be registered in JS on execution */
302 	std::vector<registered_instance_t*> *extenderInstances;		/* List holding C++ class instances to be registered in JS on execution */
303 	std::set<JSBase *> *activeInstances;						/* List holding all active instances right now (in a running script) */
304 
305 	bool forcedTermination;										/* Is set to true if script is triggering a forced termination of the script */
306 	char *forcedTerminationMessage;								/* The message given during forced termination */
307 	int forcedTerminationLineNumber;							/* The JS line number that called the exit function */
308 	char *forcedTerminationScriptFile;							/* The JS script file that called the exit function */
309 
310 	/* Internal Log function accessable from JS - used just for testing */
311 	static void Log(const v8::FunctionCallbackInfo<v8::Value>& args);
312 public:
313 	JSMain(void);
314 	~JSMain(void);
315 
316 	void AddJSExtenderFunction(v8::FunctionCallback func, const std::string& name);	/* Add a C++ function to be registered when running the script */
317 	void AddJSExtenderClass(const js_class_definition_t *method);					/* Add a C++ class to be registered when running the script */
318 	void AddJSExtenderInstance(JSBase *instance, const std::string& objectName, bool autoDestroy);	/* Add a C++ class instance to be registered when running the script */
319 
320 	static JSMain *GetScriptInstanceFromIsolate(v8::Isolate* isolate);	/* Get the JavaScript C++ instance from a V8 isolate */
321 	v8::Isolate *GetIsolate();											/* Get the V8 isolate from the current instance */
322 
323 	const std::string ExecuteScript(const std::string& filename, bool *resultIsError);
324 	const std::string ExecuteString(const std::string& scriptData, const std::string& fileName, bool *resultIsError);
325 
326 #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >=5
327 	static void Initialize(v8::Platform **platform);					/* Initialize the V8 engine */
328 #else
329 	static void Initialize();											/* Initialize the V8 engine */
330 #endif
331 
332 	static void Dispose();												/* Deinitialize the V8 engine */
333 
334 	static void Include(const v8::FunctionCallbackInfo<v8::Value>& args);		/* Adds functionality to include another JavaScript from the running script */
335 	static const std::string GetExceptionInfo(v8::Isolate* isolate, v8::TryCatch* try_catch);	/* Get the exception information from a V8 TryCatch instance */
336 
337 	const std::vector<const js_class_definition_t *>& GetExtenderClasses() const;/* Returns the list of class definitions */
338 	const std::vector<js_function_t *>& GetExtenderFunctions() const;			/* Returns the list of function definitions */
339 	const std::vector<registered_instance_t*>& GetExtenderInstances() const;	/* Returns the list of class instance definitions */
340 
341 	/* Methods to keep track of all created C++ instances within JS */
342 	void AddActiveInstance(JSBase *obj);
343 	void RemoveActiveInstance(JSBase *obj);
344 	void DisposeActiveInstances();
345 
346 	static bool FileExists(const char *file);
347 	static const std::string LoadFileToString(const std::string& filename);
348 
349 	/* Data related to forced script termination */
350 	bool GetForcedTermination(void);
351 	void ResetForcedTermination(void);
352 	const char *GetForcedTerminationMessage(void);
353 	const char *GetForcedTerminationScriptFile(void);
354 	int GetForcedTerminationLineNumber(void);
355 
356 	/* Method to force termination of a script */
357 	static void ExitScript(v8::Isolate *isolate, const char *msg, bool jskill = false);
358 
359 	/* Get the filename and line number of the current JS stack */
360 	static char *GetStackInfo(v8::Isolate *isolate, int *lineNumber);
361 };
362 
363 #ifdef V8_ENABLE_DEBUGGING
364 void V8DispatchDebugMessages();
365 #endif
366 
367 #endif /* V8_JAVASCRIPT_H */
368 
369 /* For Emacs:
370  * Local Variables:
371  * mode:c
372  * indent-tabs-mode:t
373  * tab-width:4
374  * c-basic-offset:4
375  * End:
376  * For VIM:
377  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
378  */
379