diff options
-rw-r--r-- | src/jsifier.js | 5 | ||||
-rw-r--r-- | tests/runner.py | 53 | ||||
-rwxr-xr-x | tools/bindings_generator.py | 81 | ||||
-rw-r--r-- | tools/shared.py | 2 |
4 files changed, 126 insertions, 15 deletions
diff --git a/src/jsifier.js b/src/jsifier.js index 330597bf..c19eda3a 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -235,6 +235,10 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { constant = makePointer(constant, null, BUILD_AS_SHARED_LIB ? 'ALLOC_NORMAL' : 'ALLOC_STATIC', item.type); var js = item.ident + '=' + constant + ';'; + // Special case: class vtables. We make sure they are null-terminated, to allow easy runtime operations + if (item.ident.substr(0, 5) == '__ZTV') { + js += '\n' + makePointer('[0]', null, BUILD_AS_SHARED_LIB ? 'ALLOC_NORMAL' : 'ALLOC_STATIC', ['void*']) + ';'; + } if (item.ident in EXPORTED_GLOBALS) { js += '\nModule["' + item.ident + '"] = ' + item.ident + ';'; } @@ -857,6 +861,7 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { var preFile = BUILD_AS_SHARED_LIB ? 'preamble_sharedlib.js' : 'preamble.js'; var pre = processMacros(preprocess(read(preFile).replace('{{RUNTIME}}', getRuntime()), CONSTANTS)); print(pre); + print('Runtime.QUANTUM_SIZE = ' + QUANTUM_SIZE); if (RUNTIME_TYPE_INFO) { Types.cleanForRuntime(); print('Runtime.typeInfo = ' + JSON.stringify(Types.types)); diff --git a/tests/runner.py b/tests/runner.py index 4eda8095..25b3eda5 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -5,7 +5,7 @@ See settings.py file for options¶ms. Edit as needed. ''' from subprocess import Popen, PIPE, STDOUT -import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, json +import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, json, difflib # Setup @@ -223,13 +223,19 @@ class RunnerCore(unittest.TestCase): if type(value) is not str: value = value() # lazy loading if type(string) is not str: string = string() if value not in string: - raise Exception("Expected to find '%s' in '%s'" % (limit_size(value), limit_size(string))) + raise Exception("Expected to find '%s' in '%s', diff:\n\n%s" % ( + limit_size(value), limit_size(string), + limit_size(''.join([a.rstrip()+'\n' for a in difflib.unified_diff(value.split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])) + )) def assertNotContained(self, value, string): if type(value) is not str: value = value() # lazy loading if type(string) is not str: string = string() if value in string: - raise Exception("Expected to NOT find '%s' in '%s'" % (limit_size(value), limit_size(string))) + raise Exception("Expected to NOT find '%s' in '%s', diff:\n\n%s" % ( + limit_size(value), limit_size(string), + limit_size(''.join([a.rstrip()+'\n' for a in difflib.unified_diff(value.split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])) + )) ################################################################################################### @@ -3439,6 +3445,10 @@ if 'benchmark' not in str(sys.argv): Child2() : Parent(9) { printf("Child2:%d\\n", value); }; int getValCube() { return value*value*value; } static void printStatic() { printf("*static*\\n"); } + + virtual void virtualFunc() { printf("*virtualf*\\n"); } + virtual void virtualFunc2() { printf("*virtualf2*\\n"); } + static void runVirtualFunc(Child2 *self) { self->virtualFunc(); }; private: void doSomethingSecret() { printf("security breached!\\n"); }; // we should not be able to do this }; @@ -3514,6 +3524,32 @@ if 'benchmark' not in str(sys.argv): Child2.prototype.printStatic(); // static calls go through the prototype + // virtual function + c2.virtualFunc(); + Child2.prototype.runVirtualFunc(c2); + c2.virtualFunc2(); + + // extend the class from JS + var c3 = new Child2; + customizeVTable(c3, [{ + original: Child2.prototype.virtualFunc, + replacement: function() { + print('*js virtualf replacement*'); + } + }, { + original: Child2.prototype.virtualFunc2, + replacement: function() { + print('*js virtualf2 replacement*'); + } + }]); + c3.virtualFunc(); + Child2.prototype.runVirtualFunc(c3); + c3.virtualFunc2(); + + c2.virtualFunc(); // original should remain the same + Child2.prototype.runVirtualFunc(c2); + c2.virtualFunc2(); + print('*ok*'); ''' @@ -3552,6 +3588,17 @@ Child2:9 0 1 *static* +*virtualf* +*virtualf* +*virtualf2* +Parent:9 +Child2:9 +*js virtualf replacement* +*js virtualf replacement* +*js virtualf2 replacement* +*virtualf* +*virtualf* +*virtualf2* *ok* ''', post_build=post2) diff --git a/tools/bindings_generator.py b/tools/bindings_generator.py index 3da10b89..87359fa0 100755 --- a/tools/bindings_generator.py +++ b/tools/bindings_generator.py @@ -40,6 +40,9 @@ It's only purpose is to make it easy to access the C++ code in the JS bindings, and to prevent DFE from removing the code we care about. The JS bindings do more serious work, creating class structures in JS and linking them to the C bindings. + +NOTE: ammo.js is currently the biggest consumer of this code. For some + more docs you can see that project's README ''' import os, sys, glob, re @@ -360,10 +363,9 @@ gen_js = open(basename + '.js', 'w') gen_c.write('extern "C" {\n') -# Use this when calling a binding function when you want to pass a null pointer. -# Having this object saves us needing to do checks for the object being null each time in the bindings code. gen_js.write(''' // Bindings utilities + var Object__cache = {}; // we do it this way so we do not modify |Object| function wrapPointer(ptr, __class__) { var cache = __class__ ? __class__.prototype.__cache__ : Object__cache; @@ -375,14 +377,14 @@ function wrapPointer(ptr, __class__) { ret.__class__ = __class__; return cache[ptr] = ret; } -this['wrapPointer'] = wrapPointer; +Module['wrapPointer'] = wrapPointer; function castObject(obj, __class__) { return wrapPointer(obj.ptr, __class__); } -this['castObject'] = castObject; +Module['castObject'] = castObject; -this['NULL'] = wrapPointer(0); +Module['NULL'] = wrapPointer(0); function destroy(obj) { if (!obj['__destroy__']) throw 'Error: Cannot destroy object. (Did you create it yourself?)'; @@ -394,22 +396,77 @@ function destroy(obj) { delete Object__cache[obj.ptr]; } } -this['destroy'] = destroy; +Module['destroy'] = destroy; function compare(obj1, obj2) { return obj1.ptr === obj2.ptr; } -this['compare'] = compare; +Module['compare'] = compare; function getPointer(obj) { return obj.ptr; } -this['getPointer'] = getPointer; +Module['getPointer'] = getPointer; function getClass(obj) { return obj.__class__; } -this['getClass'] = getClass; +Module['getClass'] = getClass; + +function customizeVTable(object, replacementPairs) { + // Does not handle multiple inheritance + + // Find out vtable size + var vTable = getValue(object.ptr, 'void*'); + // This assumes our modification where we null-terminate vtables + var size = 0; + while (getValue(vTable + Runtime.QUANTUM_SIZE*size, 'void*')) { + size++; + } + + // Prepare replacement lookup table and add replacements to FUNCTION_TABLE + // There is actually no good way to do this! So we do the following hack: + // We create a fake vtable with canary functions, to detect which actual + // function is being called + var vTable2 = _malloc(size*Runtime.QUANTUM_SIZE); + setValue(object.ptr, vTable2, 'void*'); + var canaryValue; + var functions = FUNCTION_TABLE.length; + for (var i = 0; i < size; i++) { + var index = FUNCTION_TABLE.length; + (function(j) { + FUNCTION_TABLE.push(function() { + canaryValue = j; + }); + })(i); + FUNCTION_TABLE.push(0); + setValue(vTable2 + Runtime.QUANTUM_SIZE*i, index, 'void*'); + } + replacementPairs.forEach(function(pair) { + pair.original.call(object); + pair.originalIndex = getValue(vTable + canaryValue*Runtime.QUANTUM_SIZE, 'void*'); + }); + FUNCTION_TABLE = FUNCTION_TABLE.slice(0, functions); + + // Do the replacements + + var replacements = {}; + replacementPairs.forEach(function(pair) { + var replacementIndex = FUNCTION_TABLE.length; + FUNCTION_TABLE.push(pair.replacement); + FUNCTION_TABLE.push(0); + replacements[pair.originalIndex] = replacementIndex; + }); + + // Copy and modify vtable + for (var i = 0; i < size; i++) { + var value = getValue(vTable + Runtime.QUANTUM_SIZE*i, 'void*'); + if (value in replacements) value = replacements[value]; + setValue(vTable2 + Runtime.QUANTUM_SIZE*i, value, 'void*'); + } + return object; +} +Module['customizeVTable'] = customizeVTable; ''') def generate_wrapping_code(classname): @@ -483,6 +540,8 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge return ([] if not need_self else [classname + ' * self']) + map(lambda i: args[i]['type'] + ' arg' + str(i), range(len(args))) def justargs(args): return map(lambda i: 'arg' + str(i), range(len(args))) + def justtypes(args): # note: this ignores 'self' + return map(lambda i: args[i]['type'], range(len(args))) fullname = ('emscripten_bind_' + generating_classname + '__' + mname).replace('::', '__') generating_classname_suffixed = generating_classname @@ -522,8 +581,7 @@ def generate_class(generating_classname, classname, clazz): # TODO: deprecate ge gen_c.write(method['operator']) else: # normal method gen_c.write(''' %s%s%s(%s);''' % ('return ' if ret.replace(' ', '') != 'void' else '', - callprefix, actualmname, ', '.join(justargs(args)[:i]))) - + callprefix, actualmname, ', '.join(justargs(args)[:i]))) gen_c.write('\n') gen_c.write('}') else: @@ -695,6 +753,7 @@ struct EmscriptenEnsurer { EmscriptenEnsurer() { // Actually use the binding functions, so DFE will not eliminate them + // FIXME: A negative side effect of this is that they take up space in FUNCTION_TABLE int sum = 0; void *seen = (void*)%s; ''' % c_funcs[0]) diff --git a/tools/shared.py b/tools/shared.py index cc466e68..83c921da 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -70,7 +70,7 @@ def line_splitter(data): def limit_size(string, MAX=80*20): if len(string) < MAX: return string - return string[0:MAX] + '...' + return string[0:MAX/2] + '\n[..]\n' + string[-MAX/2:] def pick_llvm_opts(optimization_level, optimize_size, allow_nonportable=False, use_aa=False): opts = [] |