diff options
59 files changed, 3285 insertions, 1105 deletions
@@ -4,6 +4,8 @@ *.bc src/relooper*.js +node_modules/ + # Ignore generated files src/relooper.js src/relooper.js.raw.js @@ -663,6 +663,12 @@ if '-M' in sys.argv or '-MM' in sys.argv: logging.debug('just dependencies: ' + ' '.join(cmd)) exit(subprocess.call(cmd)) +if '-E' in sys.argv: + # Just run the preprocessor + cmd = [CC] + sys.argv[1:] + logging.debug('just preprocssor ' + ' '.join(cmd)) + exit(subprocess.call(cmd)) + # Check if a target is specified target = None for i in range(len(sys.argv)-1): @@ -1070,9 +1076,6 @@ try: shared.Settings.CORRECT_OVERFLOWS = 1 assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' - if shared.Settings.ASSERTIONS and shared.Settings.ALIASING_FUNCTION_POINTERS: - logging.warning('ALIASING_FUNCTION_POINTERS is on, function pointer comparisons may be invalid across types') - if shared.Settings.CORRECT_SIGNS >= 2 or shared.Settings.CORRECT_OVERFLOWS >= 2 or shared.Settings.CORRECT_ROUNDINGS >= 2: debug_level = 4 # must keep debug info to do line-by-line operations @@ -1107,13 +1110,21 @@ try: shared.Settings.LINKABLE = 1 # TODO: add FORCE_DCE option for the brave people that do want to dce here and in side modules debug_level = max(debug_level, 2) - if shared.Settings.DLOPEN_SUPPORT: - shared.Settings.LINKABLE = 1 + if shared.Settings.ASSERTIONS and shared.Settings.ALIASING_FUNCTION_POINTERS: + logging.warning('ALIASING_FUNCTION_POINTERS is on, function pointer comparisons may be invalid across types') if shared.Settings.STB_IMAGE and final_suffix in JS_CONTAINING_SUFFIXES: input_files.append(shared.path_from_root('third_party', 'stb_image.c')) shared.Settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free'] + if type(shared.Settings.EXPORTED_FUNCTIONS) in (list, tuple): + # always need malloc and free to be kept alive and exported, for internal use and other modules + for required_export in ['_malloc', '_free']: + if required_export not in shared.Settings.EXPORTED_FUNCTIONS: + shared.Settings.EXPORTED_FUNCTIONS.append(required_export) + else: + logging.debug('using response file for EXPORTED_FUNCTIONS, make sure it includes _malloc and _free') + ## Compile source code to bitcode logging.debug('compiling to bitcode') diff --git a/emscripten.py b/emscripten.py index c5e235d8..257527fe 100755 --- a/emscripten.py +++ b/emscripten.py @@ -444,6 +444,11 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, forwarded_json['Functions']['libraryFunctions'].get('llvm_ctlz_i32'): basic_vars += ['cttz_i8', 'ctlz_i8'] + if settings.get('DLOPEN_SUPPORT'): + for sig in last_forwarded_json['Functions']['tables'].iterkeys(): + basic_vars.append('F_BASE_%s' % sig) + asm_setup += ' var F_BASE_%s = %s;\n' % (sig, 'FUNCTION_TABLE_OFFSET' if settings.get('SIDE_MODULE') else '0') + '\n' + asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew'] + ['setTempRet%d' % i for i in range(10)] # function tables def asm_coerce(value, sig): @@ -475,8 +480,12 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, ''' % (sig, i, args, arg_coercions, jsret)) from tools import shared + shared.Settings.copy(settings) asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' basic_funcs.append('invoke_%s' % sig) + if settings.get('DLOPEN_SUPPORT'): + asm_setup += '\n' + shared.JS.make_extcall(sig) + '\n' + basic_funcs.append('extCall_%s' % sig) # calculate exports exported_implemented_functions = list(exported_implemented_functions) @@ -606,23 +615,36 @@ function setTempRet%d(value) { // EMSCRIPTEN_END_ASM (%s, %s, buffer); %s; +''' % (pre_tables + '\n'.join(function_tables_impls) + '\n' + function_tables_defs.replace('\n', '\n '), exports, the_global, sending, receiving)] + + if not settings.get('SIDE_MODULE'): + funcs_js.append(''' Runtime.stackAlloc = function(size) { return asm['stackAlloc'](size) }; Runtime.stackSave = function() { return asm['stackSave']() }; Runtime.stackRestore = function(top) { asm['stackRestore'](top) }; -''' % (pre_tables + '\n'.join(function_tables_impls) + '\n' + function_tables_defs.replace('\n', '\n '), exports, the_global, sending, receiving)] +''') # Set function table masks - def function_table_maskize(js): - masks = {} - default = None - for sig, table in last_forwarded_json['Functions']['tables'].iteritems(): - masks[sig] = str(table.count(',')) - default = sig + masks = {} + max_mask = 0 + for sig, table in last_forwarded_json['Functions']['tables'].iteritems(): + mask = table.count(',') + masks[sig] = str(mask) + max_mask = max(mask, max_mask) + def function_table_maskize(js, masks): def fix(m): sig = m.groups(0)[0] return masks[sig] return re.sub(r'{{{ FTM_([\w\d_$]+) }}}', lambda m: fix(m), js) # masks[m.groups(0)[0]] - funcs_js = map(function_table_maskize, funcs_js) + funcs_js = map(lambda js: function_table_maskize(js, masks), funcs_js) + + if settings.get('DLOPEN_SUPPORT'): + funcs_js.append(''' + asm.maxFunctionIndex = %(max_mask)d; + DLFCN.registerFunctions(asm, %(max_mask)d+1, %(sigs)s, Module); + Module.SYMBOL_TABLE = SYMBOL_TABLE; +''' % { 'max_mask': max_mask, 'sigs': str(map(str, last_forwarded_json['Functions']['tables'].keys())) }) + else: function_tables_defs = '\n'.join([table for table in last_forwarded_json['Functions']['tables'].itervalues()]) outfile.write(function_tables_defs) @@ -637,13 +659,18 @@ Runtime.stackRestore = function(top) { asm['stackRestore'](top) }; symbol_table = {} for k, v in forwarded_json['Variables']['indexedGlobals'].iteritems(): if forwarded_json['Variables']['globals'][k]['named']: - symbol_table[k] = v + forwarded_json['Runtime']['GLOBAL_BASE'] + symbol_table[k] = str(v + forwarded_json['Runtime']['GLOBAL_BASE']) for raw in last_forwarded_json['Functions']['tables'].itervalues(): if raw == '': continue table = map(string.strip, raw[raw.find('[')+1:raw.find(']')].split(",")) - symbol_table.update(map(lambda x: (x[1], x[0]), - filter(lambda x: x[1] != '0', enumerate(table)))) - outfile.write("var SYMBOL_TABLE = %s;" % json.dumps(symbol_table)) + for i in range(len(table)): + value = table[i] + if value != '0': + if settings.get('SIDE_MODULE'): + symbol_table[value] = 'FUNCTION_TABLE_OFFSET+' + str(i) + else: + symbol_table[value] = str(i) + outfile.write("var SYMBOL_TABLE = %s;" % json.dumps(symbol_table).replace('"', '')) for funcs_js_item in funcs_js: # do this loop carefully to save memory funcs_js_item = indexize(funcs_js_item) @@ -663,69 +690,6 @@ def main(args, compiler_engine, cache, jcache, relooper, temp_files, DEBUG, DEBU name, value = setting.strip().split('=', 1) settings[name] = json.loads(value) - # Add header defines to settings - defines = {} - include_root = path_from_root('system', 'include') - headers = args.headers[0].split(',') if len(args.headers) > 0 else [] - seen_headers = set() - while len(headers) > 0: - header = headers.pop(0) - if not os.path.isabs(header): - header = os.path.join(include_root, header) - seen_headers.add(header) - for line in open(header, 'r'): - line = line.replace('\t', ' ') - m = re.match('^ *# *define +(?P<name>[-\w_.]+) +\(?(?P<value>[-\w_.|]+)\)?.*', line) - if not m: - # Catch enum defines of a very limited sort - m = re.match('^ +(?P<name>[A-Z_\d]+) += +(?P<value>\d+).*', line) - if m: - if m.group('name') != m.group('value'): - defines[m.group('name')] = m.group('value') - #else: - # print 'Warning: %s #defined to itself' % m.group('name') # XXX this can happen if we are set to be equal to an enum (with the same name) - m = re.match('^ *# *include *["<](?P<name>[\w_.-/]+)[">].*', line) - if m: - # Find this file - found = False - for w in [w for w in os.walk(include_root)]: - for f in w[2]: - curr = os.path.join(w[0], f) - if curr.endswith(m.group('name')) and curr not in seen_headers: - headers.append(curr) - found = True - break - if found: break - #assert found, 'Could not find header: ' + m.group('name') - if len(defines) > 0: - def lookup(value): - try: - while not unicode(value).isnumeric(): - value = defines[value] - return value - except: - pass - try: # 0x300 etc. - value = eval(value) - return value - except: - pass - try: # CONST1|CONST2 - parts = map(lookup, value.split('|')) - value = reduce(lambda a, b: a|b, map(eval, parts)) - return value - except: - pass - return None - for key, value in defines.items(): - value = lookup(value) - if value is not None: - defines[key] = str(value) - else: - del defines[key] - #print >> sys.stderr, 'new defs:', str(defines).replace(',', ',\n '), '\n\n' - settings.setdefault('C_DEFINES', {}).update(defines) - # libraries libraries = args.libraries[0].split(',') if len(args.libraries) > 0 else [] diff --git a/package.json b/package.json new file mode 100644 index 00000000..a1447c9f --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "emscripten", + "version": "1.0.0", + "dependencies": { + "ws": "~0.4.28" + } +} diff --git a/src/jsifier.js b/src/jsifier.js index 7273f54c..8592364d 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1405,7 +1405,10 @@ function JSify(data, functionsOnly, givenFunctions) { // We cannot compile assembly. See comment in intertyper.js:'Call' assert(ident != 'asm', 'Inline assembly cannot be compiled to JavaScript!'); + var extCall = false; + if (ASM_JS && funcData.setjmpTable) forceByPointer = true; // in asm.js mode, we must do an invoke for each call + if (ASM_JS && DLOPEN_SUPPORT && !invoke) extCall = true; // go out, to be able to access other modules TODO: optimize ident = Variables.resolveAliasToIdent(ident); var shortident = ident.slice(1); @@ -1466,7 +1469,7 @@ function JSify(data, functionsOnly, givenFunctions) { args = args.map(function(arg, i) { return indexizeFunctions(arg, argsTypes[i]) }); if (ASM_JS) { - if (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || funcData.setjmpTable) { + if (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable) { args = args.map(function(arg, i) { return asmCoercion(arg, argsTypes[i]) }); } else { args = args.map(function(arg, i) { return asmEnsureFloat(arg, argsTypes[i]) }); @@ -1556,17 +1559,17 @@ function JSify(data, functionsOnly, givenFunctions) { var sig = Functions.getSignature(returnType, argsTypes, hasVarArgs); if (ASM_JS) { assert(returnType.search(/\("'\[,/) == -1); // XXX need isFunctionType(type, out) - var functionTableCall = !byPointerForced && !funcData.setjmpTable && !invoke; + Functions.neededTables[sig] = 1; + var functionTableCall = !byPointerForced && !funcData.setjmpTable && !invoke && !extCall; if (functionTableCall) { // normal asm function pointer call callIdent = '(' + callIdent + ')&{{{ FTM_' + sig + ' }}}'; // the function table mask is set in emscripten.py - Functions.neededTables[sig] = 1; } else { - // This is a call through an invoke_*, either a forced one, or a setjmp-required one + // This is a call through an invoke_* or extCall, either a forced one, or a setjmp-required one // note: no need to update argsTypes at this point if (byPointerForced) Functions.unimplementedFunctions[callIdent] = sig; args.unshift(byPointerForced ? Functions.getIndex(callIdent, sig) : asmCoercion(callIdent, 'i32')); - callIdent = 'invoke_' + sig; + callIdent = (extCall ? 'extCall' : 'invoke') + '_' + sig; } } else if (SAFE_DYNCALLS) { assert(!ASM_JS, 'cannot emit safe dyncalls in asm'); @@ -1660,8 +1663,13 @@ function JSify(data, functionsOnly, givenFunctions) { Variables.generatedGlobalBase = true; // Globals are done, here is the rest of static memory assert((TARGET_LE32 && Runtime.GLOBAL_BASE == 8) || (TARGET_X86 && Runtime.GLOBAL_BASE == 4)); // this is assumed in e.g. relocations for linkable modules - print('STATIC_BASE = ' + Runtime.GLOBAL_BASE + ';\n'); - print('STATICTOP = STATIC_BASE + ' + Runtime.alignMemory(Variables.nextIndexedOffset) + ';\n'); + if (!SIDE_MODULE) { + print('STATIC_BASE = ' + Runtime.GLOBAL_BASE + ';\n'); + print('STATICTOP = STATIC_BASE + ' + Runtime.alignMemory(Variables.nextIndexedOffset) + ';\n'); + } else { + print('H_BASE = parentModule["_malloc"](' + Runtime.alignMemory(Variables.nextIndexedOffset) + ' + Runtime.GLOBAL_BASE);\n'); + print('// STATICTOP = STATIC_BASE + ' + Runtime.alignMemory(Variables.nextIndexedOffset) + ';\n'); // comment as metadata only + } } var generated = itemsDict.function.concat(itemsDict.type).concat(itemsDict.GlobalVariableStub).concat(itemsDict.GlobalVariable); print(generated.map(function(item) { return item.JS; }).join('\n')); @@ -1688,7 +1696,7 @@ function JSify(data, functionsOnly, givenFunctions) { return true; }); // write out the singleton big memory initialization value - print('/* memory initializer */ ' + makePointer(memoryInitialization, null, 'ALLOC_NONE', 'i8', 'Runtime.GLOBAL_BASE', true)); + print('/* memory initializer */ ' + makePointer(memoryInitialization, null, 'ALLOC_NONE', 'i8', 'Runtime.GLOBAL_BASE' + (SIDE_MODULE ? '+H_BASE' : ''), true)); } else { print('/* no memory initializer */'); // test purposes } @@ -1700,7 +1708,7 @@ function JSify(data, functionsOnly, givenFunctions) { print('}\n'); if (USE_TYPED_ARRAYS == 2) { - if (!BUILD_AS_SHARED_LIB) { + if (!BUILD_AS_SHARED_LIB && !SIDE_MODULE) { print('var tempDoublePtr = Runtime.alignMemory(allocate(12, "i8", ALLOC_STATIC), 8);\n'); print('assert(tempDoublePtr % 8 == 0);\n'); print('function copyTempFloat(ptr) { // functions, because inlining this code increases code size too much\n'); @@ -1754,7 +1762,7 @@ function JSify(data, functionsOnly, givenFunctions) { legalizedI64s = legalizedI64sDefault; - if (!BUILD_AS_SHARED_LIB) { + if (!BUILD_AS_SHARED_LIB && !SIDE_MODULE) { print('STACK_BASE = STACKTOP = Runtime.alignMemory(STATICTOP);\n'); print('staticSealed = true; // seal the static portion of memory\n'); print('STACK_MAX = STACK_BASE + ' + TOTAL_STACK + ';\n'); @@ -1779,7 +1787,7 @@ function JSify(data, functionsOnly, givenFunctions) { } } - if (abortExecution) throw 'Aborting compilation due to previous warnings'; + if (abortExecution) throw 'Aborting compilation due to previous errors'; if (phase == 'pre' || phase == 'funcs') { PassManager.serialize(); diff --git a/src/library.js b/src/library.js index 3ba2f56b..7894e033 100644 --- a/src/library.js +++ b/src/library.js @@ -600,31 +600,32 @@ LibraryManager.library = { // poll.h // ========================================================================== + __DEFAULT_POLLMASK: {{{ cDefine('POLLIN') }}} | {{{ cDefine('POLLOUT') }}}, __pollfd_struct_layout: Runtime.generateStructInfo([ ['i32', 'fd'], ['i16', 'events'], ['i16', 'revents']]), - poll__deps: ['$FS', '__pollfd_struct_layout'], + poll__deps: ['$FS', '__DEFAULT_POLLMASK', '__pollfd_struct_layout'], poll: function(fds, nfds, timeout) { // int poll(struct pollfd fds[], nfds_t nfds, int timeout); // http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html - // NOTE: This is pretty much a no-op mimicking glibc. var offsets = ___pollfd_struct_layout; var nonzero = 0; for (var i = 0; i < nfds; i++) { var pollfd = fds + ___pollfd_struct_layout.__size__ * i; var fd = {{{ makeGetValue('pollfd', 'offsets.fd', 'i32') }}}; var events = {{{ makeGetValue('pollfd', 'offsets.events', 'i16') }}}; - var revents = 0; + var mask = {{{ cDefine('POLLNVAL') }}}; var stream = FS.getStream(fd); if (stream) { - if (events & {{{ cDefine('POLLIN') }}}) revents |= {{{ cDefine('POLLIN') }}}; - if (events & {{{ cDefine('POLLOUT') }}}) revents |= {{{ cDefine('POLLOUT') }}}; - } else { - if (events & {{{ cDefine('POLLNVAL') }}}) revents |= {{{ cDefine('POLLNVAL') }}}; + mask = ___DEFAULT_POLLMASK; + if (stream.stream_ops.poll) { + mask = stream.stream_ops.poll(stream); + } } - if (revents) nonzero++; - {{{ makeSetValue('pollfd', 'offsets.revents', 'revents', 'i16') }}} + mask &= events | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}}; + if (mask) nonzero++; + {{{ makeSetValue('pollfd', 'offsets.revents', 'mask', 'i16') }}} } return nonzero; }, @@ -723,7 +724,7 @@ LibraryManager.library = { FS.close(stream); return 0; } catch (e) { - FS.handleFSError(e);; + FS.handleFSError(e); return -1; } }, @@ -1006,9 +1007,11 @@ LibraryManager.library = { return -1; } +#if SOCKET_WEBRTC if (stream && ('socket' in stream)) { return _recv(fildes, buf, nbyte, 0); } +#endif try { var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; @@ -1139,9 +1142,11 @@ LibraryManager.library = { return -1; } +#if SOCKET_WEBRTC if (stream && ('socket' in stream)) { return _send(fildes, buf, nbyte, 0); } +#endif try { var slab = {{{ makeGetSlabs('buf', 'i8', true) }}}; @@ -2261,7 +2266,11 @@ LibraryManager.library = { // void clearerr(FILE *stream); // http://pubs.opengroup.org/onlinepubs/000095399/functions/clearerr.html stream = FS.getStream(stream); - if (stream) stream.error = false; + if (!stream) { + return; + } + stream.eof = false; + stream.error = false; }, fclose__deps: ['close', 'fsync'], fclose: function(stream) { @@ -2322,7 +2331,6 @@ LibraryManager.library = { if (streamObj.eof || streamObj.error) return -1; var ret = _fread(_fgetc.ret, 1, 1, stream); if (ret == 0) { - streamObj.eof = true; return -1; } else if (ret == -1) { streamObj.error = true; @@ -4724,16 +4732,22 @@ LibraryManager.library = { erff: 'erf', log: 'Math.log', logf: 'Math.log', + logl: 'Math.log', sqrt: 'Math.sqrt', sqrtf: 'Math.sqrt', + sqrtl: 'Math.sqrt', fabs: 'Math.abs', fabsf: 'Math.abs', + fabsl: 'Math.abs', ceil: 'Math.ceil', ceilf: 'Math.ceil', + ceill: 'Math.ceil', floor: 'Math.floor', floorf: 'Math.floor', + floorl: 'Math.floor', pow: 'Math.pow', powf: 'Math.pow', + powl: 'Math.pow', llvm_sqrt_f32: 'Math.sqrt', llvm_sqrt_f64: 'Math.sqrt', llvm_pow_f32: 'Math.pow', @@ -5004,24 +5018,74 @@ LibraryManager.library = { // being compiled. Not sure how to tell LLVM to not do so. // ========================================================================== - // Data for dlfcn.h. - $DLFCN_DATA: { + $DLFCN: { +#if DLOPEN_SUPPORT + // extra asm.js dlopen support + functionTable: [], // will contain objects mapping sigs to js functions that call into the right asm module with the right index + + registerFunctions: function(asm, num, sigs, jsModule) { + // use asm module dynCall_* from functionTable + if (num % 2 == 1) num++; // keep pointers even + var table = DLFCN.functionTable; + var from = table.length; + assert(from % 2 == 0); + for (var i = 0; i < num; i++) { + table[from + i] = {}; + sigs.forEach(function(sig) { // TODO: new Function etc. + var full = 'dynCall_' + sig; + table[from + i][sig] = function() { + arguments[0] -= from; + return asm[full].apply(null, arguments); + } + }); + } + + if (jsModule.cleanups) { + var newLength = table.length; + jsModule.cleanups.push(function() { + if (table.length === newLength) { + table.length = from; // nothing added since, just shrink + } else { + // something was added above us, clear and leak the span + for (var i = 0; i < num; i++) { + table[from + i] = null; + } + } + while (table.length > 0 && table[table.length-1] === null) table.pop(); + }); + } + + // patch js module dynCall_* to use functionTable + sigs.forEach(function(sig) { + jsModule['dynCall_' + sig] = function() { + return table[arguments[0]][sig].apply(null, arguments); + }; + }); + }, +#endif + error: null, errorMsg: null, loadedLibs: {}, // handle -> [refcount, name, lib_object] loadedLibNames: {}, // name -> handle }, // void* dlopen(const char* filename, int flag); - dlopen__deps: ['$DLFCN_DATA', '$FS', '$ENV'], + dlopen__deps: ['$DLFCN', '$FS', '$ENV'], dlopen: function(filename, flag) { // void *dlopen(const char *file, int mode); // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html filename = filename === 0 ? '__self__' : (ENV['LD_LIBRARY_PATH'] || '/') + Pointer_stringify(filename); - if (DLFCN_DATA.loadedLibNames[filename]) { +#if ASM_JS +#if DLOPEN_SUPPORT == 0 + abort('need to build with DLOPEN_SUPPORT=1 to get dlopen support in asm.js'); +#endif +#endif + + if (DLFCN.loadedLibNames[filename]) { // Already loaded; increment ref count and return. - var handle = DLFCN_DATA.loadedLibNames[filename]; - DLFCN_DATA.loadedLibs[handle].refcount++; + var handle = DLFCN.loadedLibNames[filename]; + DLFCN.loadedLibs[handle].refcount++; return handle; } @@ -5032,7 +5096,7 @@ LibraryManager.library = { } else { var target = FS.findObject(filename); if (!target || target.isFolder || target.isDevice) { - DLFCN_DATA.errorMsg = 'Could not find dynamic lib: ' + filename; + DLFCN.errorMsg = 'Could not find dynamic lib: ' + filename; return 0; } else { FS.forceLoadFile(target); @@ -5040,19 +5104,26 @@ LibraryManager.library = { } try { - var lib_module = eval(lib_data)({{{ Functions.getTable('x') }}}.length); + var lib_module = eval(lib_data)( +#if ASM_JS + DLFCN.functionTable.length, +#else + {{{ Functions.getTable('x') }}}.length, +#endif + Module + ); } catch (e) { #if ASSERTIONS Module.printErr('Error in loading dynamic library: ' + e); #endif - DLFCN_DATA.errorMsg = 'Could not evaluate dynamic lib: ' + filename; + DLFCN.errorMsg = 'Could not evaluate dynamic lib: ' + filename; return 0; } // Not all browsers support Object.keys(). var handle = 1; - for (var key in DLFCN_DATA.loadedLibs) { - if (DLFCN_DATA.loadedLibs.hasOwnProperty(key)) handle++; + for (var key in DLFCN.loadedLibs) { + if (DLFCN.loadedLibs.hasOwnProperty(key)) handle++; } // We don't care about RTLD_NOW and RTLD_LAZY. @@ -5066,60 +5137,66 @@ LibraryManager.library = { var cached_functions = {}; } - DLFCN_DATA.loadedLibs[handle] = { + DLFCN.loadedLibs[handle] = { refcount: 1, name: filename, module: lib_module, cached_functions: cached_functions }; - DLFCN_DATA.loadedLibNames[filename] = handle; + DLFCN.loadedLibNames[filename] = handle; return handle; }, // int dlclose(void* handle); - dlclose__deps: ['$DLFCN_DATA'], + dlclose__deps: ['$DLFCN'], dlclose: function(handle) { // int dlclose(void *handle); // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlclose.html - if (!DLFCN_DATA.loadedLibs[handle]) { - DLFCN_DATA.errorMsg = 'Tried to dlclose() unopened handle: ' + handle; + if (!DLFCN.loadedLibs[handle]) { + DLFCN.errorMsg = 'Tried to dlclose() unopened handle: ' + handle; return 1; } else { - var lib_record = DLFCN_DATA.loadedLibs[handle]; + var lib_record = DLFCN.loadedLibs[handle]; if (--lib_record.refcount == 0) { - delete DLFCN_DATA.loadedLibNames[lib_record.name]; - delete DLFCN_DATA.loadedLibs[handle]; + if (lib_record.module.cleanups) { + lib_record.module.cleanups.forEach(function(cleanup) { cleanup() }); + } + delete DLFCN.loadedLibNames[lib_record.name]; + delete DLFCN.loadedLibs[handle]; } return 0; } }, // void* dlsym(void* handle, const char* symbol); - dlsym__deps: ['$DLFCN_DATA'], + dlsym__deps: ['$DLFCN'], dlsym: function(handle, symbol) { // void *dlsym(void *restrict handle, const char *restrict name); // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html symbol = '_' + Pointer_stringify(symbol); - if (!DLFCN_DATA.loadedLibs[handle]) { - DLFCN_DATA.errorMsg = 'Tried to dlsym() from an unopened handle: ' + handle; + if (!DLFCN.loadedLibs[handle]) { + DLFCN.errorMsg = 'Tried to dlsym() from an unopened handle: ' + handle; return 0; } else { - var lib = DLFCN_DATA.loadedLibs[handle]; + var lib = DLFCN.loadedLibs[handle]; // self-dlopen means that lib.module is not a superset of // cached_functions, so check the latter first if (lib.cached_functions.hasOwnProperty(symbol)) { return lib.cached_functions[symbol]; } else { if (!lib.module.hasOwnProperty(symbol)) { - DLFCN_DATA.errorMsg = ('Tried to lookup unknown symbol "' + symbol + + DLFCN.errorMsg = ('Tried to lookup unknown symbol "' + symbol + '" in dynamic lib: ' + lib.name); return 0; } else { var result = lib.module[symbol]; if (typeof result == 'function') { - {{{ Functions.getTable('x') }}}.push(result); - {{{ Functions.getTable('x') }}}.push(0); - result = {{{ Functions.getTable('x') }}}.length - 2; +#if ASM_JS + result = lib.module.SYMBOL_TABLE[symbol]; + assert(result); +#else + result = Runtime.addFunction(result); +#endif lib.cached_functions = result; } return result; @@ -5128,18 +5205,18 @@ LibraryManager.library = { } }, // char* dlerror(void); - dlerror__deps: ['$DLFCN_DATA'], + dlerror__deps: ['$DLFCN'], dlerror: function() { // char *dlerror(void); // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlerror.html - if (DLFCN_DATA.errorMsg === null) { + if (DLFCN.errorMsg === null) { return 0; } else { - if (DLFCN_DATA.error) _free(DLFCN_DATA.error); - var msgArr = intArrayFromString(DLFCN_DATA.errorMsg); - DLFCN_DATA.error = allocate(msgArr, 'i8', ALLOC_NORMAL); - DLFCN_DATA.errorMsg = null; - return DLFCN_DATA.error; + if (DLFCN.error) _free(DLFCN.error); + var msgArr = intArrayFromString(DLFCN.errorMsg); + DLFCN.error = allocate(msgArr, 'i8', ALLOC_NORMAL); + DLFCN.errorMsg = null; + return DLFCN.error; } }, @@ -5154,6 +5231,37 @@ LibraryManager.library = { }, // ========================================================================== + // termios.h + // ========================================================================== + tcgetattr: function(fildes, termios_p) { + // http://pubs.opengroup.org/onlinepubs/009695399/functions/tcgetattr.html + var stream = FS.getStream(fildes); + if (!stream) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + if (!stream.tty) { + ___setErrNo(ERRNO_CODES.ENOTTY); + return -1; + } + return 0; + }, + + tcsetattr: function(fildes, optional_actions, termios_p) { + // http://pubs.opengroup.org/onlinepubs/7908799/xsh/tcsetattr.html + var stream = FS.getStream(fildes); + if (!stream) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + if (!stream.tty) { + ___setErrNo(ERRNO_CODES.ENOTTY); + return -1; + } + return 0; + }, + + // ========================================================================== // time.h // ========================================================================== @@ -6876,7 +6984,6 @@ LibraryManager.library = { // ========================================================================== // arpa/inet.h // ========================================================================== - htonl: function(value) { return ((value & 0xff) << 24) + ((value & 0xff00) << 8) + ((value & 0xff0000) >>> 8) + ((value & 0xff000000) >>> 24); @@ -6887,75 +6994,43 @@ LibraryManager.library = { ntohl: 'htonl', ntohs: 'htons', - // http://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html - inet_ntop__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_ntop4', 'inet_ntop6'], - inet_ntop: function(af, src, dst, size) { - switch (af) { - case {{{ cDefine('AF_INET') }}}: - return _inet_ntop4(src, dst, size); - case {{{ cDefine('AF_INET6') }}}: - return _inet_ntop6(src, dst, size); - default: - ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); - return 0; - } - }, - inet_pton__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_pton4', 'inet_pton6'], - inet_pton: function(af, src, dst) { - switch (af) { - case {{{ cDefine('AF_INET') }}}: - return _inet_pton4(src, dst); - case {{{ cDefine('AF_INET6') }}}: - return _inet_pton6(src, dst); - default: - ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); - return -1; - } - }, + // old ipv4 only functions + inet_addr__deps: ['_inet_pton4_raw'], inet_addr: function(ptr) { - var b = Pointer_stringify(ptr).split("."); - if (b.length !== 4) return -1; // we return -1 for error, and otherwise a uint32. this helps inet_pton differentiate - return (Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24)) >>> 0; - }, - _inet_aton_raw: function(str) { - var b = str.split("."); - return (Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24)) >>> 0; - }, - _inet_ntoa_raw: function(addr) { - return (addr & 0xff) + '.' + ((addr >> 8) & 0xff) + '.' + ((addr >> 16) & 0xff) + '.' + ((addr >> 24) & 0xff) + var addr = __inet_pton4_raw(Pointer_stringify(ptr)); + if (addr === null) { + return -1; + } + return addr; }, - inet_ntoa__deps: ['_inet_ntoa_raw'], + inet_ntoa__deps: ['_inet_ntop4_raw'], inet_ntoa: function(in_addr) { if (!_inet_ntoa.buffer) { _inet_ntoa.buffer = _malloc(1024); } - var addr = getValue(in_addr, 'i32'); - var str = __inet_ntoa_raw(addr); + var addr = {{{ makeGetValue('in_addr', '0', 'i32') }}}; + var str = __inet_ntop4_raw(addr); writeStringToMemory(str.substr(0, 1024), _inet_ntoa.buffer); return _inet_ntoa.buffer; }, - inet_aton__deps: ['inet_addr'], + inet_aton__deps: ['_inet_pton4_raw'], inet_aton: function(cp, inp) { - var addr = _inet_addr(cp); - setValue(inp, addr, 'i32'); - if (addr < 0) return 0; - return 1; - }, - - inet_ntop4__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntoa_raw'], - inet_ntop4: function(src, dst, size) { - var str = __inet_ntoa_raw(getValue(src, 'i32')); - if (str.length+1 > size) { - ___setErrNo(ERRNO_CODES.ENOSPC); + var addr = __inet_pton4_raw(Pointer_stringify(cp)); + if (addr === null) { return 0; } - writeStringToMemory(str, dst); - return dst; + {{{ makeSetValue('inp', '0', 'addr', 'i32') }}} + return 1; }, - inet_ntop6__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_ntop6_raw'], - inet_ntop6: function(src, dst, size) { - var str = _inet_ntop6_raw(src); + // new ipv4 / ipv6 functions + _inet_ntop4_raw: function(addr) { + return (addr & 0xff) + '.' + ((addr >> 8) & 0xff) + '.' + ((addr >> 16) & 0xff) + '.' + ((addr >> 24) & 0xff) + }, + _inet_ntop4__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntop4_raw'], + _inet_ntop4: function(src, dst, size) { + var addr = {{{ makeGetValue('src', '0', 'i32') }}}; + var str = __inet_ntop4_raw(addr); if (str.length+1 > size) { ___setErrNo(ERRNO_CODES.ENOSPC); return 0; @@ -6963,9 +7038,8 @@ LibraryManager.library = { writeStringToMemory(str, dst); return dst; }, - inet_ntop6_raw__deps: ['ntohs'], - inet_ntop6_raw: function(src) { - + _inet_ntop6_raw__deps: ['ntohs', '_inet_ntop4_raw'], + _inet_ntop6_raw: function(ints) { // ref: http://www.ietf.org/rfc/rfc2373.txt - section 2.5.4 // Format for IPv4 compatible and mapped 128-bit IPv6 Addresses // 128-bits are split into eight 16-bit words @@ -6980,7 +7054,6 @@ LibraryManager.library = { // +--------------------------------------+----+---------------------+ // |0000..............................0000|FFFF| IPv4 ADDRESS | (mapped) // +--------------------------------------+----+---------------------+ - var str = ""; var word = 0; var longest = 0; @@ -6988,27 +7061,37 @@ LibraryManager.library = { var zstart = 0; var len = 0; var i = 0; + var parts = [ + ints[0] & 0xffff, + (ints[0] >> 16), + ints[1] & 0xffff, + (ints[1] >> 16), + ints[2] & 0xffff, + (ints[2] >> 16), + ints[3] & 0xffff, + (ints[3] >> 16) + ]; // Handle IPv4-compatible, IPv4-mapped, loopback and any/unspecified addresses var hasipv4 = true; var v4part = ""; // check if the 10 high-order bytes are all zeros (first 5 words) - for (i = 0; i < 10; i++) { - if ({{{ makeGetValue('src', 'i', 'i8') }}} !== 0) { hasipv4 = false; break; } + for (i = 0; i < 5; i++) { + if (parts[i] !== 0) { hasipv4 = false; break; } } if (hasipv4) { // low-order 32-bits store an IPv4 address (bytes 13 to 16) (last 2 words) - v4part = __inet_ntoa_raw({{{ makeGetValue('src', '12', 'i32') }}}); + v4part = __inet_ntop4_raw(parts[6] | (parts[7] << 16)); // IPv4-mapped IPv6 address if 16-bit value (bytes 11 and 12) == 0xFFFF (6th word) - if ({{{ makeGetValue('src', '10', 'i16') }}} === -1) { + if (parts[5] === -1) { str = "::ffff:"; str += v4part; return str; } // IPv4-compatible IPv6 address if 16-bit value (bytes 11 and 12) == 0x0000 (6th word) - if ({{{ makeGetValue('src', '10', 'i16') }}} === 0) { + if (parts[5] === 0) { str = "::"; //special case IPv6 addresses if(v4part === "0.0.0.0") v4part = ""; // any/unspecified address @@ -7022,7 +7105,7 @@ LibraryManager.library = { // first run to find the longest contiguous zero words for (word = 0; word < 8; word++) { - if ({{{ makeGetValue('src', 'word*2', 'i16') }}} === 0) { + if (parts[word] === 0) { if (word - lastzero > 1) { len = 0; } @@ -7038,7 +7121,7 @@ LibraryManager.library = { for (word = 0; word < 8; word++) { if (longest > 1) { // compress contiguous zeros - to produce "::" - if ({{{ makeGetValue('src', 'word*2', 'i16') }}} === 0 && word >= zstart && word < (zstart + longest) ) { + if (parts[word] === 0 && word >= zstart && word < (zstart + longest) ) { if (word === zstart) { str += ":"; if (zstart === 0) str += ":"; //leading zeros case @@ -7047,54 +7130,86 @@ LibraryManager.library = { } } // converts 16-bit words from big-endian to little-endian before converting to hex string - str += Number(_ntohs({{{ makeGetValue('src', 'word*2', 'i16') }}} & 0xffff)).toString(16); + str += Number(_ntohs(parts[word] & 0xffff)).toString(16); str += word < 7 ? ":" : ""; } return str; }, - - inet_pton4__deps: ['inet_addr'], - inet_pton4: function(src, dst) { - var ret = _inet_addr(src); - if (ret === -1 || isNaN(ret)) return 0; - setValue(dst, ret, 'i32'); - return 1; + _inet_ntop6__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntop6_raw'], + _inet_ntop6: function(src, dst, size) { + var addr = [ + {{{ makeGetValue('src', '0', 'i32') }}}, {{{ makeGetValue('src', '4', 'i32') }}}, + {{{ makeGetValue('src', '8', 'i32') }}}, {{{ makeGetValue('src', '12', 'i32') }}} + ]; + var str = __inet_ntop6_raw(addr); + if (str.length+1 > size) { + ___setErrNo(ERRNO_CODES.ENOSPC); + return 0; + } + writeStringToMemory(str, dst); + return dst; }, - - inet_pton6__deps: ['inet_pton6_raw'], - inet_pton6: function(src, dst) { - return _inet_pton6_raw(Pointer_stringify(src), dst); + inet_ntop__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_ntop4', '_inet_ntop6'], + inet_ntop: function(af, src, dst, size) { + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html + switch (af) { + case {{{ cDefine('AF_INET') }}}: + return __inet_ntop4(src, dst, size); + case {{{ cDefine('AF_INET6') }}}: + return __inet_ntop6(src, dst, size); + default: + ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); + return 0; + } }, - inet_pton6_raw__deps: ['htons'], - inet_pton6_raw: function(addr, dst) { + _inet_pton4_raw: function(str) { + var b = str.split('.'); + for (var i = 0; i < 4; i++) { + var tmp = Number(b[i]); + if (isNaN(tmp)) return null; + b[i] = tmp; + } + return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0; + }, + _inet_pton4__deps: ['_inet_pton4_raw'], + _inet_pton4: function(src, dst) { + var ret = __inet_pton4_raw(Pointer_stringify(src)); + if (ret === null) { + return 0; + } + {{{ makeSetValue('dst', '0', 'ret', 'i32') }}} + return 1; + }, + _inet_pton6_raw__deps: ['htons'], + _inet_pton6_raw: function(str) { var words; var w, offset, z, i; /* http://home.deds.nl/~aeron/regex/ */ var valid6regx = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i - if (!valid6regx.test(addr)) { - return 0; + var parts = []; + if (!valid6regx.test(str)) { + return null; } - if (addr === "::") { - for (i=0; i < 4; i++) {{{ makeSetValue('dst', 'i*4', '0', 'i32') }}}; - return 1; + if (str === "::") { + return [0, 0, 0, 0, 0, 0, 0, 0]; } // Z placeholder to keep track of zeros when splitting the string on ":" - if (addr.indexOf("::") === 0) { - addr = addr.replace("::", "Z:"); // leading zeros case + if (str.indexOf("::") === 0) { + str = str.replace("::", "Z:"); // leading zeros case } else { - addr = addr.replace("::", ":Z:"); + str = str.replace("::", ":Z:"); } - if (addr.indexOf(".") > 0) { - // parse IPv4 embedded address - addr = addr.replace(new RegExp('[.]', 'g'), ":"); - words = addr.split(":"); + if (str.indexOf(".") > 0) { + // parse IPv4 embedded stress + str = str.replace(new RegExp('[.]', 'g'), ":"); + words = str.split(":"); words[words.length-4] = parseInt(words[words.length-4]) + parseInt(words[words.length-3])*256; words[words.length-3] = parseInt(words[words.length-2]) + parseInt(words[words.length-1])*256; words = words.slice(0, words.length-2); } else { - words = addr.split(":"); + words = str.split(":"); } offset = 0; z = 0; @@ -7103,22 +7218,69 @@ LibraryManager.library = { if (words[w] === 'Z') { // compressed zeros - write appropriate number of zero words for (z = 0; z < (8 - words.length+1); z++) { - {{{ makeSetValue('dst', '(w+z)*2', '0', 'i16') }}}; + parts[w+z] = 0; } offset = z-1; } else { // parse hex to field to 16-bit value and write it in network byte-order - {{{ makeSetValue('dst', '(w+offset)*2', '_htons(parseInt(words[w],16))', 'i16') }}}; + parts[w+offset] = _htons(parseInt(words[w],16)); } } else { // parsed IPv4 words - {{{ makeSetValue('dst', '(w+offset)*2', 'words[w]', 'i16') }}}; + parts[w+offset] = words[w]; } } + return [ + (parts[1] << 16) | parts[0], + (parts[3] << 16) | parts[2], + (parts[5] << 16) | parts[4], + (parts[7] << 16) | parts[6] + ]; + }, + _inet_pton6__deps: ['_inet_pton6_raw'], + _inet_pton6: function(src, dst) { + var ints = __inet_pton6_raw(Pointer_stringify(src)); + if (ints === null) { + return 0; + } + for (var i = 0; i < 4; i++) { + {{{ makeSetValue('dst', 'i*4', 'ints[i]', 'i32') }}}; + } return 1; }, + inet_pton__deps: ['__setErrNo', '$ERRNO_CODES', '_inet_pton4', '_inet_pton6'], + inet_pton: function(af, src, dst) { + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_pton.html + switch (af) { + case {{{ cDefine('AF_INET') }}}: + return __inet_pton4(src, dst); + case {{{ cDefine('AF_INET6') }}}: + return __inet_pton6(src, dst); + default: + ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); + return -1; + } + }, + + // ========================================================================== + // net/if.h + // ========================================================================== + + if_nametoindex: function(a) { + return 0; + }, + if_indextoname: function(a, b) { + return 0; + }, + if_nameindex: function() { + return 0; + }, + if_freenameindex: function(a) { + }, + // ========================================================================== // netinet/in.h + // ========================================================================== _in6addr_any: 'allocate([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], "i8", ALLOC_STATIC)', @@ -7139,10 +7301,56 @@ LibraryManager.library = { // netdb.h // ========================================================================== - // All we can do is alias names to ips. you give this a name, it returns an - // "ip" that we later know to use as a name. There is no way to do actual - // name resolving clientside in a browser. - // we do the aliasing in 172.29.*.*, giving us 65536 possibilities + // We can't actually resolve hostnames in the browser, so instead + // we're generating fake IP addresses with lookup_name that we can + // resolve later on with lookup_addr. + // We do the aliasing in 172.29.*.*, giving us 65536 possibilities. + $DNS: { + address_map: { + id: 1, + addrs: {}, + names: {} + }, + + lookup_name__deps: ['_inet_pton4_raw', '_inet_pton6_raw'], + lookup_name: function (name) { + // If the name is already a valid ipv4 / ipv6 address, don't generate a fake one. + var res = __inet_pton4_raw(name); + if (res) { + return name; + } + res = __inet_pton6_raw(name); + if (res) { + return name; + } + + // See if this name is already mapped. + var addr; + + if (DNS.address_map.addrs[name]) { + addr = DNS.address_map.addrs[name]; + } else { + var id = DNS.address_map.id++; + assert(id < 65535, 'exceeded max address mappings of 65535'); + + addr = '172.29.' + (id & 0xff) + '.' + (id & 0xff00); + + DNS.address_map.names[addr] = name; + DNS.address_map.addrs[name] = addr; + } + + return addr; + }, + + lookup_addr: function (addr) { + if (DNS.address_map.names[addr]) { + return DNS.address_map.names[addr]; + } + + return null; + } + }, + // note: lots of leaking here! __hostent_struct_layout: Runtime.generateStructInfo([ ['i8*', 'h_name'], @@ -7152,44 +7360,273 @@ LibraryManager.library = { ['i8**', 'h_addr_list'], ]), - gethostbyname__deps: ['__hostent_struct_layout'], + _addrinfo_layout: Runtime.generateStructInfo([ + ['i32', 'ai_flags'], + ['i32', 'ai_family'], + ['i32', 'ai_socktype'], + ['i32', 'ai_protocol'], + ['i32', 'ai_addrlen'], + ['*', 'ai_addr'], + ['*', 'ai_canonname'], + ['*', 'ai_next'] + ]), + + gethostbyaddr__deps: ['$DNS', 'gethostbyname', '_inet_ntop4_raw'], + gethostbyaddr: function (addr, addrlen, type) { + if (type !== {{{ cDefine('AF_INET') }}}) { + ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); + return null; + } + addr = {{{ makeGetValue('addr', '0', 'i32') }}}; // addr is in_addr + var host = __inet_ntop4_raw(addr); + var lookup = DNS.lookup_addr(host); + if (lookup) { + host = lookup; + } + var hostp = allocate(intArrayFromString(host), 'i8', ALLOC_STACK); + return _gethostbyname(hostp); + }, + + gethostbyname__deps: ['$DNS', '__hostent_struct_layout', '_inet_pton4_raw'], gethostbyname: function(name) { name = Pointer_stringify(name); - if (!_gethostbyname.id) { - _gethostbyname.id = 1; - _gethostbyname.table = {}; - } - var id = _gethostbyname.id++; - assert(id < 65535); - var fakeAddr = 172 | (29 << 8) | ((id & 0xff) << 16) | ((id & 0xff00) << 24); - _gethostbyname.table[id] = name; + // generate hostent - var ret = _malloc(___hostent_struct_layout.__size__); + var ret = _malloc(___hostent_struct_layout.__size__); // XXX possibly leaked, as are others here var nameBuf = _malloc(name.length+1); writeStringToMemory(name, nameBuf); - setValue(ret+___hostent_struct_layout.h_name, nameBuf, 'i8*'); + {{{ makeSetValue('ret', '___hostent_struct_layout.h_name', 'nameBuf', 'i8*') }}} var aliasesBuf = _malloc(4); - setValue(aliasesBuf, 0, 'i8*'); - setValue(ret+___hostent_struct_layout.h_aliases, aliasesBuf, 'i8**'); - setValue(ret+___hostent_struct_layout.h_addrtype, {{{ cDefine('AF_INET') }}}, 'i32'); - setValue(ret+___hostent_struct_layout.h_length, 4, 'i32'); + {{{ makeSetValue('aliasesBuf', '0', '0', 'i8*') }}} + {{{ makeSetValue('ret', '___hostent_struct_layout.h_aliases', 'aliasesBuf', 'i8**') }}} + var afinet = {{{ cDefine("AF_INET") }}}; + {{{ makeSetValue('ret', '___hostent_struct_layout.h_addrtype', 'afinet', 'i32') }}} + {{{ makeSetValue('ret', '___hostent_struct_layout.h_length', '4', 'i32') }}} var addrListBuf = _malloc(12); - setValue(addrListBuf, addrListBuf+8, 'i32*'); - setValue(addrListBuf+4, 0, 'i32*'); - setValue(addrListBuf+8, fakeAddr, 'i32'); - setValue(ret+___hostent_struct_layout.h_addr_list, addrListBuf, 'i8**'); + {{{ makeSetValue('addrListBuf', '0', 'addrListBuf+8', 'i32*') }}} + {{{ makeSetValue('addrListBuf', '4', '0', 'i32*') }}} + {{{ makeSetValue('addrListBuf', '8', '__inet_pton4_raw(DNS.lookup_name(name))', 'i32') }}} + {{{ makeSetValue('ret', '___hostent_struct_layout.h_addr_list', 'addrListBuf', 'i8**') }}} return ret; }, gethostbyname_r__deps: ['gethostbyname'], - gethostbyname_r: function(name, hostData, buffer, bufferSize, hostEntry, errnum) { + gethostbyname_r: function(name, ret, buf, buflen, err) { var data = _gethostbyname(name); - _memcpy(hostData, data, ___hostent_struct_layout.__size__); + _memcpy(ret, data, ___hostent_struct_layout.__size__); _free(data); - setValue(errnum, 0, 'i32'); + {{{ makeSetValue('err', '0', '0', 'i32') }}}; + return ret; + }, + + getaddrinfo__deps: ['$Sockets', '$DNS', '_addrinfo_layout', '_inet_pton4_raw', '_inet_ntop4_raw', '_inet_pton6_raw', '_inet_ntop6_raw', '_write_sockaddr', 'htonl'], + getaddrinfo: function(node, service, hint, out) { + var addrs = []; + var canon = null; + var addr = 0; + var port = 0; + var flags = 0; + var family = {{{ cDefine('AF_UNSPEC') }}}; + var type = 0; + var proto = 0; + var ai, last; + + function allocaddrinfo(family, type, proto, canon, addr, port) { + var sa, salen, ai; + var res; + + salen = family === {{{ cDefine('AF_INET6') }}} ? + Sockets.sockaddr_in6_layout.__size__ : + Sockets.sockaddr_in_layout.__size__; + addr = family === {{{ cDefine('AF_INET6') }}} ? + __inet_ntop6_raw(addr) : + __inet_ntop4_raw(addr); + sa = _malloc(salen); + res = __write_sockaddr(sa, family, addr, port); + assert(!res.errno); + + ai = _malloc(__addrinfo_layout.__size__); + {{{ makeSetValue('ai', '__addrinfo_layout.ai_family', 'family', 'i32') }}}; + {{{ makeSetValue('ai', '__addrinfo_layout.ai_socktype', 'type', 'i32') }}}; + {{{ makeSetValue('ai', '__addrinfo_layout.ai_protocol', 'proto', 'i32') }}}; + if (canon) { + {{{ makeSetValue('ai', '__addrinfo_layout.ai_canonname', 'canon', 'i32') }}}; + } + {{{ makeSetValue('ai', '__addrinfo_layout.ai_addr', 'sa', '*') }}}; + if (family === {{{ cDefine('AF_INET6') }}}) { + {{{ makeSetValue('ai', '__addrinfo_layout.ai_addrlen', 'Sockets.sockaddr_in6_layout.__size__', 'i32') }}}; + } else { + {{{ makeSetValue('ai', '__addrinfo_layout.ai_addrlen', 'Sockets.sockaddr_in_layout.__size__', 'i32') }}}; + } + + return ai; + } + + if (hint) { + flags = {{{ makeGetValue('hint', '__addrinfo_layout.ai_flags', 'i32') }}}; + family = {{{ makeGetValue('hint', '__addrinfo_layout.ai_family', 'i32') }}}; + type = {{{ makeGetValue('hint', '__addrinfo_layout.ai_socktype', 'i32') }}}; + proto = {{{ makeGetValue('hint', '__addrinfo_layout.ai_protocol', 'i32') }}}; + } + if (type && !proto) { + proto = type === {{{ cDefine('SOCK_DGRAM') }}} ? {{{ cDefine('IPPROTO_UDP') }}} : {{{ cDefine('IPPROTO_TCP') }}}; + } + if (!type && proto) { + type = proto === {{{ cDefine('IPPROTO_UDP') }}} ? {{{ cDefine('SOCK_DGRAM') }}} : {{{ cDefine('SOCK_STREAM') }}}; + } + + if (!node && !service) { + return {{{ cDefine('EAI_NONAME') }}}; + } + if (flags & ~({{{ cDefine('AI_PASSIVE') }}}|{{{ cDefine('AI_CANONNAME') }}}|{{{ cDefine('AI_NUMERICHOST') }}}| + {{{ cDefine('AI_NUMERICSERV') }}}|{{{ cDefine('AI_V4MAPPED') }}}|{{{ cDefine('AI_ALL') }}}|{{{ cDefine('AI_ADDRCONFIG') }}})) { + return {{{ cDefine('EAI_BADFLAGS') }}}; + } + if (({{{ makeGetValue('hint', '__addrinfo_layout.ai_flags', 'i32') }}} & {{{ cDefine('AI_CANONNAME') }}}) && !node) { + return {{{ cDefine('EAI_BADFLAGS') }}}; + } + if (flags & {{{ cDefine('AI_ADDRCONFIG') }}}) { + // TODO + return {{{ cDefine('EAI_NONAME') }}}; + } + if (type !== {{{ cDefine('SOCK_STREAM') }}} && type !== {{{ cDefine('SOCK_DGRAM') }}}) { + return {{{ cDefine('EAI_SOCKTYPE') }}}; + } + if (family !== {{{ cDefine('AF_UNSPEC') }}} && family !== {{{ cDefine('AF_INET') }}} && family !== {{{ cDefine('AF_INET6') }}}) { + return {{{ cDefine('EAI_FAMILY') }}}; + } + + if (service) { + service = Pointer_stringify(service); + port = parseInt(service, 10); + + if (isNaN(port)) { + if (flags & {{{ cDefine('AI_NUMERICSERV') }}}) { + return {{{ cDefine('EAI_NONAME') }}}; + } + // TODO support resolving well-known service names from: + // http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt + return {{{ cDefine('EAI_SERVICE') }}}; + } + } + + if (!node) { + if (family === {{{ cDefine('AF_UNSPEC') }}}) { + family = {{{ cDefine('AF_INET') }}}; + } + if ((flags & {{{ cDefine('AI_PASSIVE') }}}) === 0) { + if (family === {{{ cDefine('AF_INET') }}}) { + addr = _htonl({{{ cDefine('INADDR_LOOPBACK') }}}); + } else { + addr = [0, 0, 0, 1]; + } + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + } + + // + // try as a numeric address + // + node = Pointer_stringify(node); + addr = __inet_pton4_raw(node); + if (addr !== null) { + // incoming node is a valid ipv4 address + if (family === {{{ cDefine('AF_UNSPEC') }}} || family === {{{ cDefine('AF_INET') }}}) { + family = {{{ cDefine('AF_INET') }}}; + } + else if (family === {{{ cDefine('AF_INET6') }}} && (flags & {{{ cDefine('AI_V4MAPPED') }}})) { + addr = [0, 0, _htonl(0xffff), addr]; + family = {{{ cDefine('AF_INET6') }}}; + } else { + return {{{ cDefine('EAI_NONAME') }}}; + } + } else { + addr = __inet_pton6_raw(node); + if (addr !== null) { + // incoming node is a valid ipv6 address + if (family === {{{ cDefine('AF_UNSPEC') }}} || family === {{{ cDefine('AF_INET6') }}}) { + family = {{{ cDefine('AF_INET6') }}}; + } else { + return {{{ cDefine('EAI_NONAME') }}}; + } + } + } + if (addr != null) { + ai = allocaddrinfo(family, type, proto, node, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + } + if (flags & {{{ cDefine('AI_NUMERICHOST') }}}) { + return {{{ cDefine('EAI_NONAME') }}}; + } + + // + // try as a hostname + // + // resolve the hostname to a temporary fake address + node = DNS.lookup_name(node); + addr = __inet_pton4_raw(node); + if (family === {{{ cDefine('AF_UNSPEC') }}}) { + family = {{{ cDefine('AF_INET') }}} + } else if (family === {{{ cDefine('AF_INET6') }}}) { + addr = [0, 0, _htonl(0xffff), addr]; + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + {{{ makeSetValue('out', '0', 'ai', '*') }}}; + return 0; + }, + + freeaddrinfo__deps: ['$Sockets', '_addrinfo_layout'], + freeaddrinfo: function(ai) { + var sa = {{{ makeGetValue('ai', '__addrinfo_layout.ai_addr', '*') }}}; + _free(sa); + _free(ai); + }, + + getnameinfo__deps: ['$Sockets', '$DNS', '__hostent_struct_layout', '_read_sockaddr'], + getnameinfo: function (sa, salen, node, nodelen, serv, servlen, flags) { + var info = __read_sockaddr(sa, salen); + if (info.errno) { + return {{{ cDefine('EAI_FAMILY') }}}; + } + var port = info.port; + var addr = info.addr; + + if (node && nodelen) { + var lookup; + if ((flags & {{{ cDefine('NI_NUMERICHOST') }}}) || !(lookup = DNS.lookup_addr(addr))) { + if (flags & {{{ cDefine('NI_NAMEREQD') }}}) { + return {{{ cDefine('EAI_NONAME') }}}; + } + } else { + addr = lookup; + } + if (addr.length >= nodelen) { + return {{{ cDefine('EAI_OVERFLOW') }}}; + } + writeStringToMemory(addr, node); + } + + if (serv && servlen) { + port = '' + port; + if (port.length > servlen) { + return {{{ cDefine('EAI_OVERFLOW') }}}; + } + writeStringToMemory(port, serv); + } + return 0; }, + gai_strerror: function(val) { + if (!_gai_strerror.error) { + _gai_strerror.error = allocate(intArrayFromString("unknown error"), 'i8', ALLOC_NORMAL); + } + return _gai_strerror.error; + }, + // ========================================================================== // sockets. Note that the implementation assumes all sockets are always // nonblocking @@ -7223,6 +7660,13 @@ LibraryManager.library = { ['i32', 'sin_zero'], ['i16', 'sin_zero_b'], ]), + sockaddr_in6_layout: Runtime.generateStructInfo([ + ['i32', 'sin6_family'], + ['i16', 'sin6_port'], + ['i32', 'sin6_flowinfo'], + ['b16', 'sin6_addr'], + ['i32', 'sin6_scope_id'] + ]), msghdr_layout: Runtime.generateStructInfo([ ['*', 'msg_name'], ['i32', 'msg_namelen'], @@ -7232,6 +7676,10 @@ LibraryManager.library = { ['i32', 'msg_controllen'], ['i32', 'msg_flags'], ]), + iovec_layout: Runtime.generateStructInfo([ + ['i8*', 'iov_base'], + ['i32', 'iov_len'] + ]) }, #if SOCKET_WEBRTC @@ -7390,7 +7838,7 @@ LibraryManager.library = { // Stub: connection-oriented sockets are not supported yet. }, - bind__deps: ['$FS', '$Sockets', '_inet_ntoa_raw', 'ntohs', 'mkport'], + bind__deps: ['$FS', '$Sockets', '_inet_ntop4_raw', 'ntohs', 'mkport'], bind: function(fd, addr, addrlen) { var info = FS.getStream(fd); if (!info) return -1; @@ -7402,7 +7850,7 @@ LibraryManager.library = { info.port = _mkport(); } info.addr = Sockets.localAddr; // 10.0.0.254 - info.host = __inet_ntoa_raw(info.addr); + info.host = __inet_ntop4_raw(info.addr); info.close = function() { Sockets.portmap[info.port] = undefined; } @@ -7411,7 +7859,7 @@ LibraryManager.library = { info.bound = true; }, - sendmsg__deps: ['$FS', '$Sockets', 'bind', '_inet_ntoa_raw', 'ntohs'], + sendmsg__deps: ['$FS', '$Sockets', 'bind', '_inet_ntop4_raw', 'ntohs'], sendmsg: function(fd, msg, flags) { var info = FS.getStream(fd); if (!info) return -1; @@ -7425,7 +7873,7 @@ LibraryManager.library = { var port = _ntohs(getValue(name + Sockets.sockaddr_in_layout.sin_port, 'i16')); var addr = getValue(name + Sockets.sockaddr_in_layout.sin_addr, 'i32'); var connection = Sockets.connections[addr]; - // var host = __inet_ntoa_raw(addr); + // var host = __inet_ntop4_raw(addr); if (!(connection && connection.connected)) { ___setErrNo(ERRNO_CODES.EWOULDBLOCK); @@ -7616,425 +8064,529 @@ LibraryManager.library = { } }, #else - socket__deps: ['$FS', '$Sockets'], + // ========================================================================== + // socket.h + // ========================================================================== + _read_sockaddr__deps: ['$Sockets', '_inet_ntop4_raw', '_inet_ntop6_raw'], + _read_sockaddr: function (sa, salen) { + // family / port offsets are common to both sockaddr_in and sockaddr_in6 + var family = {{{ makeGetValue('sa', 'Sockets.sockaddr_in_layout.sin_family', 'i32') }}}; + var port = _ntohs({{{ makeGetValue('sa', 'Sockets.sockaddr_in_layout.sin_port', 'i16') }}}); + var addr; + + switch (family) { + case {{{ cDefine('AF_INET') }}}: + if (salen !== Sockets.sockaddr_in_layout.__size__) { + return { errno: ERRNO_CODES.EINVAL }; + } + addr = {{{ makeGetValue('sa', 'Sockets.sockaddr_in_layout.sin_addr', 'i32') }}}; + addr = __inet_ntop4_raw(addr); + break; + case {{{ cDefine('AF_INET6') }}}: + if (salen !== Sockets.sockaddr_in6_layout.__size__) { + return { errno: ERRNO_CODES.EINVAL }; + } + addr = [ + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+0', 'i32') }}}, + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+4', 'i32') }}}, + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+8', 'i32') }}}, + {{{ makeGetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+12', 'i32') }}} + ]; + addr = __inet_ntop6_raw(addr); + break; + default: + return { errno: ERRNO_CODES.EAFNOSUPPORT }; + } + + return { family: family, addr: addr, port: port }; + }, + _write_sockaddr__deps: ['$Sockets', '_inet_pton4_raw', '_inet_pton6_raw'], + _write_sockaddr: function (sa, family, addr, port) { + switch (family) { + case {{{ cDefine('AF_INET') }}}: + addr = __inet_pton4_raw(addr); + {{{ makeSetValue('sa', 'Sockets.sockaddr_in_layout.sin_family', 'family', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in_layout.sin_addr', 'addr', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in_layout.sin_port', '_htons(port)', 'i16') }}}; + break; + case {{{ cDefine('AF_INET6') }}}: + addr = __inet_pton6_raw(addr); + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_family', 'family', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+0', 'addr[0]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+4', 'addr[1]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+8', 'addr[2]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_addr+12', 'addr[3]', 'i32') }}}; + {{{ makeSetValue('sa', 'Sockets.sockaddr_in6_layout.sin6_port', '_htons(port)', 'i16') }}}; + break; + default: + return { errno: ERRNO_CODES.EAFNOSUPPORT }; + } + // kind of lame, but let's match _read_sockaddr's interface + return {}; + }, + + socket__deps: ['$FS', '$SOCKFS'], socket: function(family, type, protocol) { - var stream = type == {{{ cDefine('SOCK_STREAM') }}}; - if (protocol) { - assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp + var sock = SOCKFS.createSocket(family, type, protocol); + assert(sock.stream.fd < 64); // select() assumes socket fd values are in 0..63 + return sock.stream.fd; + }, + + socketpair__deps: ['$ERRNO_CODES', '__setErrNo'], + socketpair: function(domain, type, protocol, sv) { + // int socketpair(int domain, int type, int protocol, int sv[2]); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/socketpair.html + ___setErrNo(ERRNO_CODES.EOPNOTSUPP); + return -1; + }, + + shutdown__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo'], + shutdown: function(fd, how) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - var stream = FS.createStream({ - connected: false, - stream: stream, - socket: true, - stream_ops: {} - }); - assert(stream.fd < 64); // select() assumes socket fd values are in 0..63 - return stream.fd; + _close(fd); }, - connect__deps: ['$FS', '$Sockets', '_inet_ntoa_raw', 'ntohs', 'gethostbyname'], - connect: function(fd, addr, addrlen) { - var info = FS.getStream(fd); - if (!info) return -1; - info.connected = true; - info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32'); - info.port = _htons(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16')); - info.host = __inet_ntoa_raw(info.addr); - // Support 'fake' ips from gethostbyname - var parts = info.host.split('.'); - if (parts[0] == '172' && parts[1] == '29') { - var low = Number(parts[2]); - var high = Number(parts[3]); - info.host = _gethostbyname.table[low + 0xff*high]; - assert(info.host, 'problem translating fake ip ' + parts); + bind__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + bind: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - try { - console.log('opening ws://' + info.host + ':' + info.port); - info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['binary']); - info.socket.binaryType = 'arraybuffer'; - var i32Temp = new Uint32Array(1); - var i8Temp = new Uint8Array(i32Temp.buffer); + var info = __read_sockaddr(addrp, addrlen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + var port = info.port; + var addr = DNS.lookup_addr(info.addr) || info.addr; - info.inQueue = []; - info.hasData = function() { return info.inQueue.length > 0 } - if (!info.stream) { - var partialBuffer = null; // in datagram mode, inQueue contains full dgram messages; this buffers incomplete data. Must begin with the beginning of a message - } + try { + sock.sock_ops.bind(sock, addr, port); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; + } + }, - info.socket.onmessage = function(event) { - assert(typeof event.data !== 'string' && event.data.byteLength); // must get binary data! - var data = new Uint8Array(event.data); // make a typed array view on the array buffer -#if SOCKET_DEBUG - Module.print(['onmessage', data.length, '|', Array.prototype.slice.call(data)]); -#endif - if (info.stream) { - info.inQueue.push(data); - } else { - // we added headers with message sizes, read those to find discrete messages - if (partialBuffer) { - // append to the partial buffer - var newBuffer = new Uint8Array(partialBuffer.length + data.length); - newBuffer.set(partialBuffer); - newBuffer.set(data, partialBuffer.length); - // forget the partial buffer and work on data - data = newBuffer; - partialBuffer = null; - } - var currPos = 0; - while (currPos+4 < data.length) { - i8Temp.set(data.subarray(currPos, currPos+4)); - var currLen = i32Temp[0]; - assert(currLen > 0); - if (currPos + 4 + currLen > data.length) { - break; // not enough data has arrived - } - currPos += 4; -#if SOCKET_DEBUG - Module.print(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]); -#endif - info.inQueue.push(data.subarray(currPos, currPos+currLen)); - currPos += currLen; - } - // If data remains, buffer it - if (currPos < data.length) { - partialBuffer = data.subarray(currPos); - } - } - } - function send(data) { - // TODO: if browser accepts views, can optimize this -#if SOCKET_DEBUG - Module.print('sender actually sending ' + Array.prototype.slice.call(data)); -#endif - // ok to use the underlying buffer, we created data and know that the buffer starts at the beginning - info.socket.send(data.buffer); - } - var outQueue = []; - var intervalling = false, interval; - function trySend() { - if (info.socket.readyState != info.socket.OPEN) { - if (!intervalling) { - intervalling = true; - console.log('waiting for socket in order to send'); - interval = setInterval(trySend, 100); - } - return; - } - for (var i = 0; i < outQueue.length; i++) { - send(outQueue[i]); - } - outQueue.length = 0; - if (intervalling) { - intervalling = false; - clearInterval(interval); - } - } - info.sender = function(data) { - if (!info.stream) { - // add a header with the message size - var header = new Uint8Array(4); - i32Temp[0] = data.length; - header.set(i8Temp); - outQueue.push(header); - } - outQueue.push(new Uint8Array(data)); - trySend(); - }; - } catch(e) { - Module.printErr('Error in connect(): ' + e); - ___setErrNo(ERRNO_CODES.EACCES); + connect__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + connect: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); return -1; } - // always "fail" in non-blocking mode - ___setErrNo(ERRNO_CODES.EINPROGRESS); - return -1; + var info = __read_sockaddr(addrp, addrlen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + var port = info.port; + var addr = DNS.lookup_addr(info.addr) || info.addr; + + try { + sock.sock_ops.connect(sock, addr, port); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; + } }, - recv__deps: ['$FS'], - recv: function(fd, buf, len, flags) { - var info = FS.getStream(fd); - if (!info) { + listen__deps: ['$FS', '$SOCKFS', '$ERRNO_CODES', '__setErrNo'], + listen: function(fd, backlog) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } -#if SOCKET_WEBRTC == 0 - if (!info.hasData()) { - if (info.socket.readyState === WebSocket.CLOSING || info.socket.readyState === WebSocket.CLOSED) { - // socket has closed - return 0; - } else { - // else, our socket is in a valid state but truly has nothing available - ___setErrNo(ERRNO_CODES.EAGAIN); - return -1; - } + try { + sock.sock_ops.listen(sock, backlog); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; } -#endif - var buffer = info.inQueue.shift(); -#if SOCKET_DEBUG - Module.print('recv: ' + [Array.prototype.slice.call(buffer)]); -#endif - if (len < buffer.length) { - if (info.stream) { - // This is tcp (reliable), so if not all was read, keep it - info.inQueue.unshift(buffer.subarray(len)); -#if SOCKET_DEBUG - Module.print('recv: put back: ' + (len - buffer.length)); -#endif + }, + + accept__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr'], + accept: function(fd, addrp, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + try { + var newsock = sock.sock_ops.accept(sock); + if (addrp) { + var res = __write_sockaddr(addr, newsock.family, DNS.lookup_name(newsock.daddr), newsock.dport); + assert(!res.errno); } - buffer = buffer.subarray(0, len); + return newsock.stream.fd; + } catch (e) { + FS.handleFSError(e); + return -1; } - HEAPU8.set(buffer, buf); - return buffer.length; }, - send__deps: ['$FS'], - send: function(fd, buf, len, flags) { - var info = FS.getStream(fd); - if (!info) { + getsockname__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', '_inet_pton_raw'], + getsockname: function (fd, addr, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { ___setErrNo(ERRNO_CODES.EBADF); return -1; } -#if SOCKET_WEBRTC == 0 - if (info.socket.readyState === WebSocket.CLOSING || info.socket.readyState === WebSocket.CLOSED) { - ___setErrNo(ERRNO_CODES.ENOTCONN); - return -1; - } else if (info.socket.readyState === WebSocket.CONNECTING) { - ___setErrNo(ERRNO_CODES.EAGAIN); + try { + var info = sock.sock_ops.getname(sock); + var res = __write_sockaddr(addr, sock.family, DNS.lookup_name(info.addr), info.port); + assert(!res.errno); + return 0; + } catch (e) { + FS.handleFSError(e); return -1; } -#endif - info.sender(HEAPU8.subarray(buf, buf+len)); - return len; }, - sendmsg__deps: ['$FS', '$Sockets', 'connect'], - sendmsg: function(fd, msg, flags) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); - _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + getpeername__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr', '_inet_pton_raw'], + getpeername: function (fd, addr, addrlen) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; - var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('sendmsg vecs: ' + num); -#endif - var totalSize = 0; - for (var i = 0; i < num; i++) { - totalSize += {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; + try { + var info = sock.sock_ops.getname(sock, true); + var res = __write_sockaddr(addr, sock.family, DNS.lookup_name(info.addr), info.port); + assert(!res.errno); + return 0; + } catch (e) { + FS.handleFSError(e); + return -1; } - var buffer = new Uint8Array(totalSize); - var ret = 0; - for (var i = 0; i < num; i++) { - var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('sendmsg curr size: ' + currNum); -#endif - if (!currNum) continue; - var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}}; - buffer.set(HEAPU8.subarray(currBuf, currBuf+currNum), ret); - ret += currNum; + }, + + send__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo', 'write'], + send: function(fd, buf, len, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - info.sender(buffer); // send all the iovs as a single message - return ret; + // TODO honor flags + return _write(fd, buf, len); }, - recvmsg__deps: ['$FS', '$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES', 'htons'], - recvmsg: function(fd, msg, flags) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { -#if SOCKET_DEBUG - Module.print('recvmsg connecting'); -#endif - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); - _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + recv__deps: ['$SOCKFS', '$ERRNO_CODES', '__setErrNo', 'read'], + recv: function(fd, buf, len, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - if (!info.hasData()) { - ___setErrNo(ERRNO_CODES.EWOULDBLOCK); + // TODO honor flags + return _read(fd, buf, len); + }, + + sendto__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + sendto: function(fd, message, length, flags, dest_addr, dest_len) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); return -1; } - var buffer = info.inQueue.shift(); - var bytes = buffer.length; -#if SOCKET_DEBUG - Module.print('recvmsg bytes: ' + bytes); -#endif - // write source - var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; - {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_addr', 'info.addr', 'i32') }}}; - {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_port', '_htons(info.port)', 'i16') }}}; - // write data - var ret = bytes; - var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; - var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; - var bufferPos = 0; - for (var i = 0; i < num && bytes > 0; i++) { - var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}}; -#if SOCKET_DEBUG - Module.print('recvmsg loop ' + [i, num, bytes, currNum]); -#endif - if (!currNum) continue; - currNum = Math.min(currNum, bytes); // XXX what should happen when we partially fill a buffer..? - bytes -= currNum; - var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}}; -#if SOCKET_DEBUG - Module.print('recvmsg call recv ' + currNum); -#endif - HEAPU8.set(buffer.subarray(bufferPos, bufferPos + currNum), currBuf); - bufferPos += currNum; + + // read the address and port to send to + var info = __read_sockaddr(dest_addr, dest_len); + if (info.errno) { + ___setErrNo(info.errno); + return -1; } - if (info.stream) { - // This is tcp (reliable), so if not all was read, keep it - if (bufferPos < bytes) { - info.inQueue.unshift(buffer.subarray(bufferPos)); -#if SOCKET_DEBUG - Module.print('recvmsg: put back: ' + (bytes - bufferPos)); -#endif - } + var port = info.port; + var addr = DNS.lookup_addr(info.addr) || info.addr; + + // send the message + try { + var slab = {{{ makeGetSlabs('message', 'i8', true) }}}; + return sock.sock_ops.sendmsg(sock, slab, message, length, addr, port); + } catch (e) { + FS.handleFSError(e); + return -1; } - return ret; }, - recvfrom__deps: ['$FS', 'connect', 'recv'], + recvfrom__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_write_sockaddr'], recvfrom: function(fd, buf, len, flags, addr, addrlen) { - var info = FS.getStream(fd); - if (!info) return -1; - // if we are not connected, use the address info in the message - if (!info.connected) { - //var name = {{{ makeGetValue('addr', '0', '*') }}}; - _connect(fd, addr, addrlen); + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } - return _recv(fd, buf, len, flags); - }, - shutdown__deps: ['$FS'], - shutdown: function(fd, how) { - var stream = FS.getStream(fd); - if (!stream) return -1; - stream.socket.close(); - FS.closeStream(stream); - }, + // read from the socket + var msg; + try { + msg = sock.sock_ops.recvmsg(sock, len); + } catch (e) { + FS.handleFSError(e); + return -1; + } - ioctl__deps: ['$FS'], - ioctl: function(fd, request, varargs) { - var info = FS.getStream(fd); - if (!info) return -1; - var bytes = 0; - if (info.hasData()) { - bytes = info.inQueue[0].length; + if (!msg) { + // socket is closed + return 0; } - var dest = {{{ makeGetValue('varargs', '0', 'i32') }}}; - {{{ makeSetValue('dest', '0', 'bytes', 'i32') }}}; - return 0; + + // write the source address out + if (addr) { + var res = __write_sockaddr(addr, sock.family, DNS.lookup_name(msg.addr), msg.port); + assert(!res.errno); + } + // write the buffer out + HEAPU8.set(msg.buffer, buf); + + return msg.buffer.byteLength; }, - setsockopt: function(d, level, optname, optval, optlen) { - console.log('ignoring setsockopt command'); - return 0; + sendmsg__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_read_sockaddr'], + sendmsg: function(fd, message, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + + var iov = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iov', '*') }}}; + var num = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + + // read the address and port to send to + var addr; + var port; + var name = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_name', '*') }}}; + var namelen = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}; + if (name) { + var info = __read_sockaddr(name, namelen); + if (info.errno) { + ___setErrNo(info.errno); + return -1; + } + port = info.port; + addr = DNS.lookup_addr(info.addr) || info.addr; + } + + // concatenate scatter-gather arrays into one message buffer + var total = 0; + for (var i = 0; i < num; i++) { + total += {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + } + var view = new Uint8Array(total); + var offset = 0; + for (var i = 0; i < num; i++) { + var iovbase = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_base', 'i8*') }}}; + var iovlen = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + for (var j = 0; j < iovlen; j++) { + view[offset++] = {{{ makeGetValue('iovbase', 'j', 'i8') }}}; + } + } + + // write the buffer + try { + return sock.sock_ops.sendmsg(sock, view, 0, total, addr, port); + } catch (e) { + FS.handleFSError(e); + return -1; + } }, - bind__deps: ['connect'], - bind: function(fd, addr, addrlen) { - _connect(fd, addr, addrlen); - return 0; + recvmsg__deps: ['$FS', '$SOCKFS', '$DNS', '$ERRNO_CODES', '__setErrNo', '_inet_pton_raw', '_write_sockaddr'], + recvmsg: function(fd, message, flags) { + var sock = SOCKFS.getSocket(fd); + if (!sock) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } + + var iov = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}}; + var num = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + + // get the total amount of data we can read across all arrays + var total = 0; + for (var i = 0; i < num; i++) { + total += {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + } + + // try to read total data + var msg; + try { + msg = sock.sock_ops.recvmsg(sock, total); + } catch (e) { + FS.handleFSError(e); + return -1; + } + + if (!msg) { + // socket is closed + return 0; + } + + // TODO honor flags: + // MSG_OOB + // Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific. + // MSG_PEEK + // Peeks at the incoming message. + // MSG_WAITALL + // Requests that the function block until the full amount of data requested can be returned. The function may return a smaller amount of data if a signal is caught, if the connection is terminated, if MSG_PEEK was specified, or if an error is pending for the socket. + + // write the source address out + var name = {{{ makeGetValue('message', 'Sockets.msghdr_layout.msg_name', '*') }}}; + if (name) { + var res = __write_sockaddr(name, sock.family, DNS.lookup_name(msg.addr), msg.port); + assert(!res.errno); + } + // write the buffer out to the scatter-gather arrays + var bytesRead = 0; + var bytesRemaining = msg.buffer.byteLength; + + for (var i = 0; bytesRemaining > 0 && i < num; i++) { + var iovbase = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_base', 'i8*') }}}; + var iovlen = {{{ makeGetValue('iov', '(Sockets.iovec_layout.__size__ * i) + Sockets.iovec_layout.iov_len', 'i32') }}}; + if (!iovlen) { + continue; + } + var length = Math.min(iovlen, bytesRemaining); + var buf = msg.buffer.subarray(bytesRead, bytesRead + length); + HEAPU8.set(buf, iovbase + bytesRead); + bytesRead += length; + bytesRemaining -= length; + } + + // TODO set msghdr.msg_flags + // MSG_EOR + // End of record was received (if supported by the protocol). + // MSG_OOB + // Out-of-band data was received. + // MSG_TRUNC + // Normal data was truncated. + // MSG_CTRUNC + + return bytesRead; }, - listen: function(fd, backlog) { + setsockopt: function(fd, level, optname, optval, optlen) { + console.log('ignoring setsockopt command'); return 0; }, - accept__deps: ['$FS', '$Sockets'], - accept: function(fd, addr, addrlen) { - // TODO: webrtc queued incoming connections, etc. - // For now, the model is that bind does a connect, and we "accept" that one connection, - // which has host:port the same as ours. We also return the same socket fd. - var info = FS.getStream(fd); - if (!info) return -1; - if (addr) { - setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32'); - setValue(addr + Sockets.sockaddr_in_layout.sin_port, info.port, 'i32'); - setValue(addrlen, Sockets.sockaddr_in_layout.__size__, 'i32'); - } - return fd; - }, + // ========================================================================== + // select.h + // ========================================================================== - select__deps: ['$FS'], + select__deps: ['$FS', '__DEFAULT_POLLMASK'], select: function(nfds, readfds, writefds, exceptfds, timeout) { // readfds are supported, // writefds checks socket open status // exceptfds not supported // timeout is always 0 - fully async - assert(!exceptfds); + assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits + assert(!exceptfds, 'exceptfds not supported'); - var errorCondition = 0; - - function canRead(info) { - return (info.hasData && info.hasData()) || - info.socket.readyState == WebSocket.CLOSING || // let recv return 0 once closed - info.socket.readyState == WebSocket.CLOSED; - } + var total = 0; + + var srcReadLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0), + srcReadHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0); + var srcWriteLow = (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0), + srcWriteHigh = (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0); + var srcExceptLow = (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0), + srcExceptHigh = (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + var dstReadLow = 0, + dstReadHigh = 0; + var dstWriteLow = 0, + dstWriteHigh = 0; + var dstExceptLow = 0, + dstExceptHigh = 0; + + var allLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0); + var allHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + function get(fd, low, high, val) { + return (fd < 32 ? (low & val) : (high & val)); + } + + for (var fd = 0; fd < nfds; fd++) { + var mask = 1 << (fd % 32); + if (!(get(fd, allLow, allHigh, mask))) { + continue; // index isn't in the set + } - function canWrite(info) { - return info.socket && (info.socket.readyState == info.socket.OPEN); - } + var stream = FS.getStream(fd); + if (!stream) { + ___setErrNo(ERRNO_CODES.EBADF); + return -1; + } - function checkfds(nfds, fds, can) { - if (!fds) return 0; + var flags = ___DEFAULT_POLLMASK; - var bitsSet = 0; - var dstLow = 0; - var dstHigh = 0; - var srcLow = {{{ makeGetValue('fds', 0, 'i32') }}}; - var srcHigh = {{{ makeGetValue('fds', 4, 'i32') }}}; - nfds = Math.min(64, nfds); // fd sets have 64 bits + if (stream.stream_ops.poll) { + flags = stream.stream_ops.poll(stream); + } - for (var fd = 0; fd < nfds; fd++) { - var mask = 1 << (fd % 32), int_ = fd < 32 ? srcLow : srcHigh; - if (int_ & mask) { - // index is in the set, check if it is ready for read - var info = FS.getStream(fd); - if (!info) { - ___setErrNo(ERRNO_CODES.EBADF); - return -1; - } - if (can(info)) { - // set bit - fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask); - bitsSet++; - } - } + if ((flags & {{{ cDefine('POLLIN') }}}) && get(fd, srcReadLow, srcReadHigh, mask)) { + fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask); + total++; + } + if ((flags & {{{ cDefine('POLLOUT') }}}) && get(fd, srcWriteLow, srcWriteHigh, mask)) { + fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask); + total++; } + if ((flags & {{{ cDefine('POLLPRI') }}}) && get(fd, srcExceptLow, srcExceptHigh, mask)) { + fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask); + total++; + } + } - {{{ makeSetValue('fds', 0, 'dstLow', 'i32') }}}; - {{{ makeSetValue('fds', 4, 'dstHigh', 'i32') }}}; - return bitsSet; + if (readfds) { + {{{ makeSetValue('readfds', '0', 'dstReadLow', 'i32') }}}; + {{{ makeSetValue('readfds', '4', 'dstReadHigh', 'i32') }}}; } + if (writefds) { + {{{ makeSetValue('writefds', '0', 'dstWriteLow', 'i32') }}}; + {{{ makeSetValue('writefds', '4', 'dstWriteHigh', 'i32') }}}; + } + if (exceptfds) { + {{{ makeSetValue('exceptfds', '0', 'dstExceptLow', 'i32') }}}; + {{{ makeSetValue('exceptfds', '4', 'dstExceptHigh', 'i32') }}}; + } + + return total; + }, - var totalHandles = checkfds(nfds, readfds, canRead) + checkfds(nfds, writefds, canWrite); - if (errorCondition) { + // ========================================================================== + // sys/ioctl.h + // ========================================================================== + + ioctl__deps: ['$FS'], + ioctl: function(fd, request, varargs) { + var stream = FS.getStream(fd); + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return -1; - } else { - return totalHandles; } + var arg = {{{ makeGetValue('varargs', '0', 'i32') }}}; + return FS.ioctl(stream, request, arg); }, #endif - socketpair__deps: ['__setErrNo', '$ERRNO_CODES'], - socketpair: function(domain, type, protocol, sv) { - // int socketpair(int domain, int type, int protocol, int sv[2]); - // http://pubs.opengroup.org/onlinepubs/009695399/functions/socketpair.html - ___setErrNo(ERRNO_CODES.EOPNOTSUPP); - return -1; - }, - // pty.h openpty: function() { throw 'openpty: TODO' }, diff --git a/src/library_browser.js b/src/library_browser.js index 591a3c11..235ccc78 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -830,15 +830,21 @@ mergeInto(LibraryManager.library, { }, emscripten_get_now: function() { - if (ENVIRONMENT_IS_NODE) { - var t = process['hrtime'](); - return t[0] * 1e3 + t[1] / 1e6; - } - else if (ENVIRONMENT_IS_WEB && window['performance'] && window['performance']['now']) { - return window['performance']['now'](); - } else { - return Date.now(); + if (!_emscripten_get_now.actual) { + if (ENVIRONMENT_IS_NODE) { + _emscripten_get_now.actual = function() { + var t = process['hrtime'](); + return t[0] * 1e3 + t[1] / 1e6; + } + } else if (typeof dateNow !== 'undefined') { + _emscripten_get_now.actual = dateNow; + } else if (ENVIRONMENT_IS_WEB && window['performance'] && window['performance']['now']) { + _emscripten_get_now.actual = function() { return window['performance']['now'](); }; + } else { + _emscripten_get_now.actual = Date.now; + } } + return _emscripten_get_now.actual(); }, emscripten_create_worker: function(url) { diff --git a/src/library_fs.js b/src/library_fs.js index 1d9748d3..5573dc27 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -159,6 +159,9 @@ mergeInto(LibraryManager.library, { isFIFO: function(mode) { return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}}; }, + isSocket: function(mode) { + return (mode & {{{ cDefine('S_IFSOCK') }}}) === {{{ cDefine('S_IFSOCK') }}}; + }, // // paths @@ -400,6 +403,9 @@ mergeInto(LibraryManager.library, { getStream: function(fd) { return FS.streams[fd]; }, + // TODO parameterize this function such that a stream + // object isn't directly passed in. not possible until + // SOCKFS is completed. createStream: function(stream, fd_start, fd_end) { var fd = FS.nextfd(fd_start, fd_end); stream.fd = fd; @@ -1459,6 +1465,12 @@ mergeInto(LibraryManager.library, { throw new FS.errnoError(ERRNO_CODES.ENODEV); } return stream.stream_ops.mmap(stream, buffer, offset, length, position, prot, flags); + }, + ioctl: function(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTTY); + } + return stream.stream_ops.ioctl(stream, cmd, arg); } } }); diff --git a/src/library_sdl.js b/src/library_sdl.js index d6cb6d18..9231f41b 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -2191,11 +2191,37 @@ var LibrarySDL = { // Joysticks - SDL_NumJoysticks: function() { return 0 }, + SDL_NumJoysticks: function() { return 0; }, - SDL_JoystickOpen: function(deviceIndex) { return 0 }, + SDL_JoystickName: function(deviceIndex) { return 0; }, - SDL_JoystickGetButton: function(joystick, button) { return 0 }, + SDL_JoystickOpen: function(deviceIndex) { return 0; }, + + SDL_JoystickOpened: function(deviceIndex) { return 0; }, + + SDL_JoystickIndex: function(joystick) { return 0; }, + + SDL_JoystickNumAxes: function(joystick) { return 0; }, + + SDL_JoystickNumBalls: function(joystick) { return 0; }, + + SDL_JoystickNumHats: function(joystick) { return 0; }, + + SDL_JoystickNumButtons: function(joystick) { return 0; }, + + SDL_JoystickUpdate: function() {}, + + SDL_JoystickEventState: function(state) { return 0; }, + + SDL_JoystickGetAxis: function(joystick, axis) { return 0; }, + + SDL_JoystickGetHat: function(joystick, hat) { return 0; }, + + SDL_JoystickGetBall: function(joystick, ball, dxptr, dyptr) { return -1; }, + + SDL_JoystickGetButton: function(joystick, button) { return 0; }, + + SDL_JoystickClose: function(joystick) {}, // Misc diff --git a/src/library_sockfs.js b/src/library_sockfs.js index 13118b71..b11c6495 100644 --- a/src/library_sockfs.js +++ b/src/library_sockfs.js @@ -3,16 +3,574 @@ mergeInto(LibraryManager.library, { $SOCKFS__deps: ['$FS'], $SOCKFS: { mount: function(mount) { - var node = FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); - node.node_ops = SOCKFS.node_ops; - node.stream_ops = SOCKFS.stream_ops; - return node; + return FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); }, - node_ops: { + nextname: function() { + if (!SOCKFS.nextname.current) { + SOCKFS.nextname.current = 0; + } + return 'socket[' + (SOCKFS.nextname.current++) + ']'; }, + createSocket: function(family, type, protocol) { + var streaming = type == {{{ cDefine('SOCK_STREAM') }}}; + if (protocol) { + assert(streaming == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp + } + + // create our internal socket structure + var sock = { + family: family, + type: type, + protocol: protocol, + server: null, + peers: {}, + pending: [], + recv_queue: [], +#if SOCKET_WEBRTC +#else + sock_ops: SOCKFS.websocket_sock_ops +#endif + }; + + // create the filesystem node to store the socket structure + var name = SOCKFS.nextname(); + var node = FS.createNode(SOCKFS.root, name, {{{ cDefine('S_IFSOCK') }}}, 0); + node.sock = sock; + + // and the wrapping stream that enables library functions such + // as read and write to indirectly interact with the socket + var stream = FS.createStream({ + path: name, + node: node, + flags: FS.modeStringToFlags('r+'), + seekable: false, + stream_ops: SOCKFS.stream_ops + }); + + // map the new stream to the socket structure (sockets have a 1:1 + // relationship with a stream) + sock.stream = stream; + + return sock; + }, + getSocket: function(fd) { + var stream = FS.getStream(fd); + if (!stream || !FS.isSocket(stream.node.mode)) { + return null; + } + return stream.node.sock; + }, + // node and stream ops are backend agnostic stream_ops: { + poll: function(stream) { + var sock = stream.node.sock; + return sock.sock_ops.poll(sock); + }, + ioctl: function(stream, request, varargs) { + var sock = stream.node.sock; + return sock.sock_ops.ioctl(sock, request, varargs); + }, + read: function(stream, buffer, offset, length, position /* ignored */) { + var sock = stream.node.sock; + var msg = sock.sock_ops.recvmsg(sock, length); + if (!msg) { + // socket is closed + return 0; + } +#if USE_TYPED_ARRAYS == 2 + buffer.set(msg.buffer, offset); +#else + for (var i = 0; i < size; i++) { + buffer[offset + i] = msg.buffer[i]; + } +#endif + return msg.buffer.length; + }, + write: function(stream, buffer, offset, length, position /* ignored */) { + var sock = stream.node.sock; + return sock.sock_ops.sendmsg(sock, buffer, offset, length); + }, + close: function(stream) { + var sock = stream.node.sock; + sock.sock_ops.close(sock); + } }, + // backend-specific stream ops websocket_sock_ops: { + // + // peers are a small wrapper around a WebSocket to help in + // emulating dgram sockets + // + // these functions aren't actually sock_ops members, but we're + // abusing the namespace to organize them + // + createPeer: function(sock, addr, port) { + var ws; + + if (typeof addr === 'object') { + ws = addr; + addr = null; + port = null; + } + + if (ws) { + // for sockets that've already connected (e.g. we're the server) + // we can inspect the _socket property for the address + if (ws._socket) { + addr = ws._socket.remoteAddress; + port = ws._socket.remotePort; + } + // if we're just now initializing a connection to the remote, + // inspect the url property + else { + var result = /ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url); + if (!result) { + throw new Error('WebSocket URL must be in the format ws(s)://address:port'); + } + addr = result[1]; + port = parseInt(result[2], 10); + } + } else { + // create the actual websocket object and connect + try { + var url = 'ws://' + addr + ':' + port; +#if SOCKET_DEBUG + console.log('connect: ' + url); +#endif + // the node ws library API is slightly different than the browser's + var opts = ENVIRONMENT_IS_NODE ? {} : ['binary']; + ws = new WebSocket(url, opts); + ws.binaryType = 'arraybuffer'; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EHOSTUNREACH); + } + } + +#if SOCKET_DEBUG + Module.print('websocket adding peer: ' + addr + ':' + port); +#endif + + var peer = { + addr: addr, + port: port, + socket: ws, + dgram_send_queue: [] + }; + + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + SOCKFS.websocket_sock_ops.handlePeerEvents(sock, peer); + + // if this is a bound dgram socket, send the port number first to allow + // us to override the ephemeral port reported to us by remotePort on the + // remote end. + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}} && typeof sock.sport !== 'undefined') { +#if SOCKET_DEBUG + Module.print('websocket queuing port message (port ' + sock.sport + ')'); +#endif + peer.dgram_send_queue.push(new Uint8Array([ + 255, 255, 255, 255, + 'p'.charCodeAt(0), 'o'.charCodeAt(0), 'r'.charCodeAt(0), 't'.charCodeAt(0), + ((sock.sport & 0xff00) >> 8) , (sock.sport & 0xff) + ])); + } + + return peer; + }, + getPeer: function(sock, addr, port) { + return sock.peers[addr + ':' + port]; + }, + addPeer: function(sock, peer) { + sock.peers[peer.addr + ':' + peer.port] = peer; + }, + removePeer: function(sock, peer) { + delete sock.peers[peer.addr + ':' + peer.port]; + }, + handlePeerEvents: function(sock, peer) { + var first = true; + + var handleOpen = function () { +#if SOCKET_DEBUG + Module.print('websocket handle open'); +#endif + try { + var queued = peer.dgram_send_queue.shift(); + while (queued) { +#if SOCKET_DEBUG + Module.print('websocket sending queued data (' + queued.byteLength + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(queued))]); +#endif + peer.socket.send(queued); + queued = peer.dgram_send_queue.shift(); + } + } catch (e) { + // not much we can do here in the way of proper error handling as we've already + // lied and said this data was sent. shut it down. + peer.socket.close(); + } + }; + + var handleMessage = function(data) { + assert(typeof data !== 'string' && data.byteLength !== undefined); // must receive an ArrayBuffer + data = new Uint8Array(data); // make a typed array view on the array buffer + +#if SOCKET_DEBUG + Module.print('websocket handle message (' + data.byteLength + ' bytes): ' + [Array.prototype.slice.call(data)]); +#endif + + // if this is the port message, override the peer's port with it + var wasfirst = first; + first = false; + if (wasfirst && + data.length === 10 && + data[0] === 255 && data[1] === 255 && data[2] === 255 && data[3] === 255 && + data[4] === 'p'.charCodeAt(0) && data[5] === 'o'.charCodeAt(0) && data[6] === 'r'.charCodeAt(0) && data[7] === 't'.charCodeAt(0)) { + // update the peer's port and it's key in the peer map + var newport = ((data[8] << 8) | data[9]); + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + peer.port = newport; + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + return; + } + + sock.recv_queue.push({ addr: peer.addr, port: peer.port, data: data }); + }; + + if (ENVIRONMENT_IS_NODE) { + peer.socket.on('open', handleOpen); + peer.socket.on('message', function(data, flags) { + if (!flags.binary) { + return; + } + handleMessage((new Uint8Array(data)).buffer); // copy from node Buffer -> ArrayBuffer + }); + peer.socket.on('error', function() { + // don't throw + }); + } else { + peer.socket.onopen = handleOpen; + peer.socket.onmessage = function(event) { + handleMessage(event.data); + }; + } + }, + + // + // actual sock ops + // + poll: function(sock) { + if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && sock.server) { + // listen sockets should only say they're available for reading + // if there are pending clients. + return sock.pending.length ? ({{{ cDefine('POLLRDNORM') }}} | {{{ cDefine('POLLIN') }}}) : 0; + } + + var mask = 0; + var dest = sock.type === {{{ cDefine('SOCK_STREAM') }}} ? // we only care about the socket state for connection-based sockets + SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport) : + null; + + if (sock.recv_queue.length || + !dest || // connection-less sockets are always ready to read + (dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED)) { // let recv return 0 once closed + mask |= ({{{ cDefine('POLLRDNORM') }}} | {{{ cDefine('POLLIN') }}}); + } + + if (!dest || // connection-less sockets are always ready to write + (dest && dest.socket.readyState === dest.socket.OPEN)) { + mask |= {{{ cDefine('POLLOUT') }}}; + } + + if ((dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED)) { + mask |= {{{ cDefine('POLLHUP') }}}; + } + + return mask; + }, + ioctl: function(sock, request, arg) { + switch (request) { + case {{{ cDefine('FIONREAD') }}}: + var bytes = 0; + if (sock.recv_queue.length) { + bytes = sock.recv_queue[0].data.length; + } + {{{ makeSetValue('arg', '0', 'bytes', 'i32') }}}; + return 0; + default: + return ERRNO_CODES.EINVAL; + } + }, + close: function(sock) { + // if we've spawned a listen server, close it + if (sock.server) { + try { + sock.server.close(); + } catch (e) { + } + sock.server = null; + } + // close any peer connections + var peers = Object.keys(sock.peers); + for (var i = 0; i < peers.length; i++) { + var peer = sock.peers[peers[i]]; + try { + peer.socket.close(); + } catch (e) { + } + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + } + return 0; + }, + bind: function(sock, addr, port) { + if (typeof sock.saddr !== 'undefined' || typeof sock.sport !== 'undefined') { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); // already bound + } + sock.saddr = addr; + sock.sport = port || _mkport(); + // in order to emulate dgram sockets, we need to launch a listen server when + // binding on a connection-less socket + // note: this is only required on the server side + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) { + // close the existing server if it exists + if (sock.server) { + sock.server.close(); + sock.server = null; + } + // swallow error operation not supported error that occurs when binding in the + // browser where this isn't supported + try { + sock.sock_ops.listen(sock, 0); + } catch (e) { + if (!(e instanceof FS.ErrnoError)) throw e; + if (e.errno !== ERRNO_CODES.EOPNOTSUPP) throw e; + } + } + }, + connect: function(sock, addr, port) { + if (sock.server) { + throw new FS.ErrnoError(ERRNO_CODS.EOPNOTSUPP); + } + + // TODO autobind + // if (!sock.addr && sock.type == {{{ cDefine('SOCK_DGRAM') }}}) { + // } + + // early out if we're already connected / in the middle of connecting + if (typeof sock.daddr !== 'undefined' && typeof sock.dport !== 'undefined') { + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport); + if (dest) { + if (dest.socket.readyState === dest.socket.CONNECTING) { + throw new FS.ErrnoError(ERRNO_CODES.EALREADY); + } else { + throw new FS.ErrnoError(ERRNO_CODES.EISCONN); + } + } + } + + // add the socket to our peer list and set our + // destination address / port to match + var peer = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port); + sock.daddr = peer.addr; + sock.dport = peer.port; + + // always "fail" in non-blocking mode + throw new FS.ErrnoError(ERRNO_CODES.EINPROGRESS); + }, + listen: function(sock, backlog) { + if (!ENVIRONMENT_IS_NODE) { + throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP); + } + if (sock.server) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); // already listening + } + var WebSocketServer = require('ws').Server; + var host = sock.saddr; +#if SOCKET_DEBUG + console.log('listen: ' + host + ':' + sock.sport); +#endif + sock.server = new WebSocketServer({ + host: host, + port: sock.sport + // TODO support backlog + }); + + sock.server.on('connection', function(ws) { +#if SOCKET_DEBUG + console.log('received connection from: ' + ws._socket.remoteAddress + ':' + ws._socket.remotePort); +#endif + if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) { + var newsock = SOCKFS.createSocket(sock.family, sock.type, sock.protocol); + + // create a peer on the new socket + var peer = SOCKFS.websocket_sock_ops.createPeer(newsock, ws); + newsock.daddr = peer.addr; + newsock.dport = peer.port; + + // push to queue for accept to pick up + sock.pending.push(newsock); + } else { + // create a peer on the listen socket so calling sendto + // with the listen socket and an address will resolve + // to the correct client + SOCKFS.websocket_sock_ops.createPeer(sock, ws); + } + }); + sock.server.on('closed', function() { + sock.server = null; + }); + sock.server.on('error', function() { + // don't throw + }); + }, + accept: function(listensock) { + if (!listensock.server) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var newsock = listensock.pending.shift(); + newsock.stream.flags = listensock.stream.flags; + return newsock; + }, + getname: function(sock, peer) { + var addr, port; + if (peer) { + if (sock.daddr === undefined || sock.dport === undefined) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } + addr = sock.daddr; + port = sock.dport; + } else { + // TODO saddr and sport will be set for bind()'d UDP sockets, but what + // should we be returning for TCP sockets that've been connect()'d? + addr = sock.saddr || 0; + port = sock.sport || 0; + } + return { addr: addr, port: port }; + }, + sendmsg: function(sock, buffer, offset, length, addr, port) { + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) { + // connection-less sockets will honor the message address, + // and otherwise fall back to the bound destination address + if (addr === undefined || port === undefined) { + addr = sock.daddr; + port = sock.dport; + } + // if there was no address to fall back to, error out + if (addr === undefined || port === undefined) { + throw new FS.ErrnoError(ERRNO_CODES.EDESTADDRREQ); + } + } else { + // connection-based sockets will only use the bound + addr = sock.daddr; + port = sock.dport; + } + + // find the peer for the destination address + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, addr, port); + + // early out if not connected with a connection-based socket + if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) { + if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } else if (dest.socket.readyState === dest.socket.CONNECTING) { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + } + + // create a copy of the incoming data to send, as the WebSocket API + // doesn't work entirely with an ArrayBufferView, it'll just send + // the entire underlying buffer + var data; + if (buffer instanceof Array || buffer instanceof ArrayBuffer) { + data = buffer.slice(offset, offset + length); + } else { // ArrayBufferView + data = buffer.buffer.slice(buffer.byteOffset + offset, buffer.byteOffset + offset + length); + } + + // if we're emulating a connection-less dgram socket and don't have + // a cached connection, queue the buffer to send upon connect and + // lie, saying the data was sent now. + if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) { + if (!dest || dest.socket.readyState !== dest.socket.OPEN) { + // if we're not connected, open a new connection + if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) { + dest = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port); + } +#if SOCKET_DEBUG + Module.print('websocket queuing (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]); +#endif + dest.dgram_send_queue.push(data); + return length; + } + } + + try { +#if SOCKET_DEBUG + Module.print('websocket send (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]); +#endif + // send the actual data + dest.socket.send(data); + return length; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + }, + recvmsg: function(sock, length) { + // http://pubs.opengroup.org/onlinepubs/7908799/xns/recvmsg.html + if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && sock.server) { + // tcp servers should not be recv()'ing on the listen socket + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } + + var queued = sock.recv_queue.shift(); + if (!queued) { + if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) { + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport); + + if (!dest) { + // if we have a destination address but are not connected, error out + throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN); + } + else if (dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) { + // return null if the socket has closed + return null; + } + else { + // else, our socket is in a valid state but truly has nothing available + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + } else { + throw new FS.ErrnoError(ERRNO_CODES.EAGAIN); + } + } + + // queued.data will be an ArrayBuffer if it's unadulterated, but if it's + // requeued TCP data it'll be an ArrayBufferView + var queuedLength = queued.data.byteLength || queued.data.length; + var queuedOffset = queued.data.byteOffset || 0; + var queuedBuffer = queued.data.buffer || queued.data; + var bytesRead = Math.min(length, queuedLength); + var res = { + buffer: new Uint8Array(queuedBuffer, queuedOffset, bytesRead), + addr: queued.addr, + port: queued.port + }; + +#if SOCKET_DEBUG + Module.print('websocket read (' + bytesRead + ' bytes): ' + [Array.prototype.slice.call(res.buffer)]); +#endif + + // push back any unread data for TCP connections + if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && bytesRead < queuedLength) { + var bytesRemaining = queuedLength - bytesRead; +#if SOCKET_DEBUG + Module.print('websocket read: put back ' + bytesRemaining + ' bytes'); +#endif + queued.data = new Uint8Array(queuedBuffer, queuedOffset + bytesRead, bytesRemaining); + sock.recv_queue.unshift(queued); + } + + return res; + } } } });
\ No newline at end of file diff --git a/src/library_tty.js b/src/library_tty.js index 8f44cd07..bf4a2472 100644 --- a/src/library_tty.js +++ b/src/library_tty.js @@ -1,17 +1,37 @@ mergeInto(LibraryManager.library, { $TTY__deps: ['$FS'], + $TTY__postset: '__ATINIT__.unshift({ func: function() { TTY.init() } });' + + '__ATEXIT__.push({ func: function() { TTY.shutdown() } });' + + 'TTY.utf8 = new Runtime.UTF8Processor();', $TTY: { ttys: [], + init: function () { + // https://github.com/kripken/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // currently, FS.init does not distinguish if process.stdin is a file or TTY + // // device, it always assumes it's a TTY device. because of this, we're forcing + // // process.stdin to UTF8 encoding to at least make stdin reading compatible + // // with text files until FS.init can be refactored. + // process['stdin']['setEncoding']('utf8'); + // } + }, + shutdown: function() { + // https://github.com/kripken/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? + // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation + // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? + // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle + // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call + // process['stdin']['pause'](); + // } + }, register: function(dev, ops) { TTY.ttys[dev] = { input: [], output: [], ops: ops }; FS.registerDevice(dev, TTY.stream_ops); }, stream_ops: { open: function(stream) { - // this wouldn't be required if the library wasn't eval'd at first... - if (!TTY.utf8) { - TTY.utf8 = new Runtime.UTF8Processor(); - } var tty = TTY.ttys[stream.node.rdev]; if (!tty) { throw new FS.ErrnoError(ERRNO_CODES.ENODEV); @@ -66,17 +86,22 @@ mergeInto(LibraryManager.library, { return i; } }, - // NOTE: This is weird to support stdout and stderr - // overrides in addition to print and printErr overrides. default_tty_ops: { + // get_char has 3 particular return values: + // a.) the next character represented as an integer + // b.) undefined to signal that no data is currently available + // c.) null to signal an EOF get_char: function(tty) { if (!tty.input.length) { var result = null; if (ENVIRONMENT_IS_NODE) { - if (process.stdin.destroyed) { - return undefined; + result = process['stdin']['read'](); + if (!result) { + if (process['stdin']['_readableState'] && process['stdin']['_readableState']['ended']) { + return null; // EOF + } + return undefined; // no data available } - result = process.stdin.read(); } else if (typeof window != 'undefined' && typeof window.prompt == 'function') { // Browser. diff --git a/src/modules.js b/src/modules.js index fa6c0983..373e60d9 100644 --- a/src/modules.js +++ b/src/modules.js @@ -443,6 +443,7 @@ var LibraryManager = { }, isStubFunction: function(ident) { + if (SIDE_MODULE == 1) return false; // cannot eliminate these, as may be implement in the main module and imported by us var libCall = LibraryManager.library[ident.substr(1)]; return typeof libCall === 'function' && libCall.toString().replace(/\s/g, '') === 'function(){}' && !(ident in Functions.implementedFunctions); diff --git a/src/settings.js b/src/settings.js index 03b4ed64..79c54c2b 100644 --- a/src/settings.js +++ b/src/settings.js @@ -354,9 +354,10 @@ var LINKABLE = 0; // If set to 1, this file can be linked with others, either as // LINKABLE of 0 is very useful in that we can reduce the size of the // generated code very significantly, by removing everything not actually used. -var DLOPEN_SUPPORT = 0; // Whether to support dlopen(NULL, ...) which enables dynamic access to the - // module's functions and globals. Implies LINKABLE=1, because we do not want - // dead code elimination. +var DLOPEN_SUPPORT = 0; // Full support for dlopen. This is necessary for asm.js and for all code + // modes for dlopen(NULL, ...). Note that you must use EMSCRIPTEN_KEEPALIVE + // to ensure that functions and globals can be accessed through dlsym, + // otherwise LLVM may optimize them out. var RUNTIME_TYPE_INFO = 0; // Whether to expose type info to the script at run time. This // increases the size of the generated script, but allows you @@ -457,6 +458,7 @@ var C_DEFINES = { 'ABMON_8': '40', 'ABMON_9': '41', 'ACCESSPERMS': '0000400', + 'AF_UNSPEC': '0', 'AF_INET': '2', 'AF_INET6': '10', 'ALLPERMS': '0004000', @@ -718,6 +720,7 @@ var C_DEFINES = { 'PM_STR': '6', 'POLLERR': '8', 'POLLHUP': '16', + 'POLLPRI': '32', 'POLLIN': '1', 'POLLNVAL': '4', 'POLLOUT': '2', @@ -874,9 +877,9 @@ var C_DEFINES = { 'SOCK_DGRAM': '2', 'SOCK_STREAM': '1', 'STDC_HEADERS': '1', - 'STDERR_FILENO': '2', - 'STDIN_FILENO': '0', - 'STDOUT_FILENO': '1', + 'STDERR_FILENO': '3', + 'STDIN_FILENO': '1', + 'STDOUT_FILENO': '2', 'S_BLKSIZE': '1024', 'S_ENFMT': '0002000', 'S_IEXEC': '0000100', @@ -1457,6 +1460,35 @@ var C_DEFINES = { 'ECANCELED': '140', 'ENOTRECOVERABLE': '141', 'EOWNERDEAD': '142', - 'ESTRPIPE': '143' + 'ESTRPIPE': '143', + 'AI_PASSIVE': '0x0001', + 'AI_CANONNAME': '0x0002', + 'AI_NUMERICHOST': '0x0004', + 'AI_V4MAPPED': '0x0008', + 'AI_ALL': '0x0010', + 'AI_ADDRCONFIG': '0x0020', + 'AI_NUMERICSERV': '0x0400', + 'EAI_ADDRFAMILY': '1', + 'EAI_AGAIN': '2', + 'EAI_BADFLAGS': '3', + 'EAI_FAIL': '4', + 'EAI_FAMILY': '5', + 'EAI_MEMORY': '6', + 'EAI_NODATA': '7', + 'EAI_NONAME': '8', + 'EAI_SERVICE': '9', + 'EAI_SOCKTYPE': '10', + 'EAI_SYSTEM': '11', + 'EAI_BADHINTS': '12', + 'EAI_PROTOCOL': '13', + 'EAI_OVERFLOW': '14', + 'EAI_MAX': '15', + 'NI_NOFQDN': '0x00000001', + 'NI_NUMERICHOST': '0x00000002', + 'NI_NAMEREQD': '0x00000004', + 'NI_NUMERICSERV': '0x00000008', + 'NI_DGRAM': '0x00000010', + 'INADDR_ANY': '0', + 'INADDR_LOOPBACK': '0x7f000001' }; diff --git a/src/shell_sharedlib.js b/src/shell_sharedlib.js index 1d34c73e..c070c617 100644 --- a/src/shell_sharedlib.js +++ b/src/shell_sharedlib.js @@ -1,8 +1,24 @@ // Capture the output of this into a variable, if you want -(function(FUNCTION_TABLE_OFFSET, globalScope) { +(function(FUNCTION_TABLE_OFFSET, parentModule) { var Module = {}; var args = []; Module.arguments = []; + Module.print = parentModule.print; + Module.printErr = parentModule.printErr; + + Module.cleanups = []; + +#if ASM_JS + var H_BASE = 0; + // Each module has its own stack + var STACKTOP = parentModule['_malloc'](TOTAL_STACK); + assert(STACKTOP % 8 == 0); + var STACK_MAX = STACKTOP + TOTAL_STACK; + Module.cleanups.push(function() { + parentModule['_free'](STACKTOP); // XXX ensure exported + parentModule['_free'](H_BASE); + }); +#endif {{BODY}} @@ -10,3 +26,4 @@ return Module; }); + diff --git a/system/include/netdb.h b/system/include/netdb.h index df74a117..8860a14a 100644 --- a/system/include/netdb.h +++ b/system/include/netdb.h @@ -101,6 +101,7 @@ struct hostent struct hostent* gethostbyaddr(const void* addr, socklen_t len, int type); struct hostent* gethostbyname(const char* name); +struct hostent* gethostbyname_r(const char *name, struct hostent *ret, char *buf, int buflen, int *err); // XXX not quite standard, see http://linux.die.net/man/3/gethostbyname_r void sethostent(int stayopen); void endhostent(void); void herror(const char* s); diff --git a/tests/browser_main.cpp b/tests/browser_main.cpp new file mode 100644 index 00000000..efdce1be --- /dev/null +++ b/tests/browser_main.cpp @@ -0,0 +1,42 @@ +#include <assert.h> +#include <stdio.h> +#include <dlfcn.h> +#include <emscripten.h> + +typedef void (*voidfunc)(); +typedef int (*intfunc)(); + +void *lib_handle; +voidfunc onefunc; +intfunc twofunc; + +void next(const char *x) { + lib_handle = dlopen("themodule.js", RTLD_NOW); + assert(lib_handle != NULL); + + onefunc = (voidfunc)dlsym(lib_handle, "one"); + twofunc = (intfunc)dlsym(lib_handle, "two"); + assert(onefunc && twofunc); + + assert(twofunc() == 0); + onefunc(); + assert(twofunc() == 1); + onefunc(); + onefunc(); + assert(twofunc() == 3); + onefunc(); + onefunc(); + onefunc(); + onefunc(); + assert(twofunc() == 7); + onefunc(); + int result = twofunc(); + REPORT_RESULT(); +} + +int main() { + emscripten_async_wget("module.js", "themodule.js", next, NULL); + + return 0; +} + diff --git a/tests/browser_module.cpp b/tests/browser_module.cpp new file mode 100644 index 00000000..85d724b5 --- /dev/null +++ b/tests/browser_module.cpp @@ -0,0 +1,15 @@ + +int state = 0; + +extern "C" { + +void one() { + state++; +} + +int two() { + return state; +} + +} + diff --git a/tests/dlmalloc_proxy.c b/tests/dlmalloc_proxy.c new file mode 100644 index 00000000..06137c42 --- /dev/null +++ b/tests/dlmalloc_proxy.c @@ -0,0 +1,85 @@ +// Emscripten tests + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <dlfcn.h> + +typedef void *(*mallocer)(int n); +typedef void (*freeer)(void *p); + +void *lib_handle; +int handles = 0; +mallocer mallocproxy = NULL; +freeer freeproxy = NULL; + +void get_lib() { + //printf("get lib\n"); + lib_handle = dlopen("liblib.so", RTLD_NOW); + assert(lib_handle != NULL); + handles++; + + mallocproxy = (mallocer)dlsym(lib_handle, "mallocproxy"); + assert(mallocproxy!= NULL); + freeproxy = (freeer)dlsym(lib_handle, "freeproxy"); + assert(freeproxy!= NULL); +} + +void unget_lib() { + //printf("unget lib\n"); + assert(lib_handle); + dlclose(lib_handle); + handles--; + if (handles == 0) lib_handle = NULL; +} + +int main() { + int n = 0, total = 0, l = 0; + void *allocs[50]; + allocs[10] = malloc(10); // pull in real malloc + for (int i = 0; i < 1000; i++) { + //printf("%d: total ever %d MB, current MB %d, total libs %d\n", i, total, n, l); + if (i % 5 == 0) { + if (handles < 10) { + get_lib(); + l++; + } + } + if (i % 7 == 0) { + if (handles > 0) unget_lib(); + } + if (i % 3 == 0) { + if (handles > 0) { + if (n < 10) { + if (i % 2 == 0) { + //printf("alloc\n"); + allocs[n++] = mallocproxy(1024*1024); + } else { + //printf("real alloc\n"); + allocs[n++] = malloc(1024*1024); + } + total++; + } else { + //printf("real free\n"); + free(allocs[--n]); // real free + } + } + } + if (i % 4 == 0) { + if (handles > 0 && n > 0) { + //printf("free\n"); + if (i % 2 == 0) { + //printf("free\n"); + freeproxy(allocs[--n]); + } else { + //printf("real free\n"); + free(allocs[--n]); + } + } + } + } + while (n > 0) free(allocs[--n]); // real free + while (handles > 0) unget_lib(); + printf("*%d,%d*\n", total, l); +} + diff --git a/tests/emscripten_get_now.cpp b/tests/emscripten_get_now.cpp new file mode 100644 index 00000000..17aa7d32 --- /dev/null +++ b/tests/emscripten_get_now.cpp @@ -0,0 +1,46 @@ +#include <stdio.h> +#include "emscripten.h" + +#ifndef REPORT_RESULT +// To be able to run this test outside the browser harness in node.js/spidermonkey: +#define REPORT_RESULT int dummy +#endif + +int result = 0; + +int main() { + // This code tests three things: + // a) Calling emscripten_get_now(), time actually proceeds. + // b) Values returned by emscripten_get_now() are strictly nondecreasing. + // c) emscripten_get_now() is able to return sub-millisecond precision timer values. + bool detected_good_timer_precision = false; + float smallest_delta = 0.f; + for(int x = 0; x < 1000; ++x) { // Have several attempts to find a good small delta, i.e. give time to JS engine to warm up the code and so on. + float t = emscripten_get_now(); + float t2 = emscripten_get_now(); + for(int i = 0; i < 100 && t == t2; ++i) { + t2 = emscripten_get_now(); + } + + if (t2 < t && t2 - t < 1000.f) { // Timer must be monotonous. + printf("Timer is not monotonous!\\n"); + smallest_delta = t2 - t; + break; + } + if (t2 > t && t2 - t < 0.7f) { // Must pass less than a millisecond between two calls. + detected_good_timer_precision = true; + smallest_delta = t2 - t; + break; + } + } + + if (detected_good_timer_precision) { + printf("Timer resolution is good. (%f msecs)\\n", smallest_delta); + result = 1; + } else { + printf("Error: Bad timer precision: Smallest timer delta: %f msecs\\n", smallest_delta); + result = 0; + } + REPORT_RESULT(); + return 0; +} diff --git a/tests/module/test_stdin.c b/tests/module/test_stdin.c new file mode 100644 index 00000000..4838d466 --- /dev/null +++ b/tests/module/test_stdin.c @@ -0,0 +1,57 @@ +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +int line = 0; + +void main_loop(void *arg) +{ + char str[10] = {0}; + int ret; + + errno = 0; + while (errno != EAGAIN) { + if (line == 0) { + ret = fgetc(stdin); + if (ret != EOF) putc(ret, stdout); + if (ret == '\n') line++; + } else if (line > 0) { + ret = scanf("%10s", str); + if (ret > 0) puts(str); + } + + if (ferror(stdin) && errno != EAGAIN) { + puts("error"); + exit(EXIT_FAILURE); + } + + if (feof(stdin)) { + puts("eof"); + exit(EXIT_SUCCESS); + } + + clearerr(stdin); + } +} + +int main(int argc, char const *argv[]) +{ + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); + + // SM shell doesn't implement an event loop and therefor doesn't support + // emscripten_set_main_loop. However, its stdin reads are sync so it + // should exit out after calling main_loop once. + main_loop(NULL); + +#if EMSCRIPTEN + emscripten_set_main_loop(main_loop, 60, 0); +#else + while (1) main_loop(NULL); sleep(1); +#endif + return 0; +}
\ No newline at end of file diff --git a/tests/msvc10/emscripten_api_browser.vcxproj b/tests/msvc10/emscripten_api_browser.vcxproj index 9f4a05fd..f1290f02 100644 --- a/tests/msvc10/emscripten_api_browser.vcxproj +++ b/tests/msvc10/emscripten_api_browser.vcxproj @@ -80,6 +80,18 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + <DisableWarnings>vexing-parse</DisableWarnings> + </ClCompile> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + <DisableWarnings>vexing-parse</DisableWarnings> + </ClCompile> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\emscripten_api_browser.cpp" /> </ItemGroup> diff --git a/tests/msvc10/glbook_10_MultiTexture.vcxproj b/tests/msvc10/glbook_10_MultiTexture.vcxproj index a831f351..86167eb3 100644 --- a/tests/msvc10/glbook_10_MultiTexture.vcxproj +++ b/tests/msvc10/glbook_10_MultiTexture.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_11_Multisample.vcxproj b/tests/msvc10/glbook_11_Multisample.vcxproj index 47d5fb4a..3b7becb1 100644 --- a/tests/msvc10/glbook_11_Multisample.vcxproj +++ b/tests/msvc10/glbook_11_Multisample.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_11_Stencil_Test.vcxproj b/tests/msvc10/glbook_11_Stencil_Test.vcxproj index 3a541128..654f4a8e 100644 --- a/tests/msvc10/glbook_11_Stencil_Test.vcxproj +++ b/tests/msvc10/glbook_11_Stencil_Test.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_13_ParticleSystem.vcxproj b/tests/msvc10/glbook_13_ParticleSystem.vcxproj index c18e17ff..b8648f93 100644 --- a/tests/msvc10/glbook_13_ParticleSystem.vcxproj +++ b/tests/msvc10/glbook_13_ParticleSystem.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_15_Hello_Triangle_KD.vcxproj b/tests/msvc10/glbook_15_Hello_Triangle_KD.vcxproj index 489628f5..5d3026a2 100644 --- a/tests/msvc10/glbook_15_Hello_Triangle_KD.vcxproj +++ b/tests/msvc10/glbook_15_Hello_Triangle_KD.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_2_Hello_Triangle.vcxproj b/tests/msvc10/glbook_2_Hello_Triangle.vcxproj index 34de0780..7a26f304 100644 --- a/tests/msvc10/glbook_2_Hello_Triangle.vcxproj +++ b/tests/msvc10/glbook_2_Hello_Triangle.vcxproj @@ -93,11 +93,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> diff --git a/tests/msvc10/glbook_8_Simple_VertexShader.vcxproj b/tests/msvc10/glbook_8_Simple_VertexShader.vcxproj index 19442df9..37b07d9d 100644 --- a/tests/msvc10/glbook_8_Simple_VertexShader.vcxproj +++ b/tests/msvc10/glbook_8_Simple_VertexShader.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_9_MipMap2D.vcxproj b/tests/msvc10/glbook_9_MipMap2D.vcxproj index af0e072b..2cb190b6 100644 --- a/tests/msvc10/glbook_9_MipMap2D.vcxproj +++ b/tests/msvc10/glbook_9_MipMap2D.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_9_Simple_Texture2D.vcxproj b/tests/msvc10/glbook_9_Simple_Texture2D.vcxproj index 09ff3e1e..fe44f617 100644 --- a/tests/msvc10/glbook_9_Simple_Texture2D.vcxproj +++ b/tests/msvc10/glbook_9_Simple_Texture2D.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_9_Simple_TextureCubemap.vcxproj b/tests/msvc10/glbook_9_Simple_TextureCubemap.vcxproj index d89da1c7..d84142a9 100644 --- a/tests/msvc10/glbook_9_Simple_TextureCubemap.vcxproj +++ b/tests/msvc10/glbook_9_Simple_TextureCubemap.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/glbook_9_TextureWrap.vcxproj b/tests/msvc10/glbook_9_TextureWrap.vcxproj index 54f51e26..7f710fba 100644 --- a/tests/msvc10/glbook_9_TextureWrap.vcxproj +++ b/tests/msvc10/glbook_9_TextureWrap.vcxproj @@ -83,11 +83,13 @@ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign;int-conversion</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> <ClCompile> <AdditionalIncludeDirectories>../glbook/Common</AdditionalIncludeDirectories> + <DisableWarnings>pointer-sign;int-conversion</DisableWarnings> </ClCompile> </ItemDefinitionGroup> <ItemGroup> diff --git a/tests/msvc10/new.vcxproj b/tests/msvc10/new.vcxproj deleted file mode 100644 index 11ad95ae..00000000 --- a/tests/msvc10/new.vcxproj +++ /dev/null @@ -1,89 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <ItemGroup Label="ProjectConfigurations"> - <ProjectConfiguration Include="Debug|Emscripten"> - <Configuration>Debug</Configuration> - <Platform>Emscripten</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Debug|Win32"> - <Configuration>Debug</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Emscripten"> - <Configuration>Release</Configuration> - <Platform>Emscripten</Platform> - </ProjectConfiguration> - <ProjectConfiguration Include="Release|Win32"> - <Configuration>Release</Configuration> - <Platform>Win32</Platform> - </ProjectConfiguration> - </ItemGroup> - <PropertyGroup Label="Globals"> - <ProjectGuid>{5190107D-91B3-4EF8-82CB-08381DD19ABB}</ProjectGuid> - <RootNamespace>new</RootNamespace> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>true</UseDebugLibraries> - <CharacterSet>MultiByte</CharacterSet> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> - <ConfigurationType>Application</ConfigurationType> - <UseDebugLibraries>false</UseDebugLibraries> - <WholeProgramOptimization>true</WholeProgramOptimization> - <CharacterSet>MultiByte</CharacterSet> - </PropertyGroup> - <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> - <PlatformToolset>emcc</PlatformToolset> - <ConfigurationType>HTMLPage</ConfigurationType> - </PropertyGroup> - <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> - <PlatformToolset>emcc</PlatformToolset> - <ConfigurationType>HTMLPage</ConfigurationType> - </PropertyGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> - <ImportGroup Label="ExtensionSettings"> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> - <IntDir>$(Platform)\$(ProjectName)_$(Configuration)\</IntDir> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> - <IntDir>$(Platform)\$(ProjectName)_$(Configuration)\</IntDir> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <Optimization>Disabled</Optimization> - </ClCompile> - <Link> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> - </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <ClCompile> - <WarningLevel>Level3</WarningLevel> - <Optimization>MaxSpeed</Optimization> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - </ClCompile> - <Link> - <GenerateDebugInformation>true</GenerateDebugInformation> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - </Link> - </ItemDefinitionGroup> - <ItemGroup> - <ClCompile Include="..\new.cpp" /> - </ItemGroup> - <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> - <ImportGroup Label="ExtensionTargets"> - </ImportGroup> -</Project>
\ No newline at end of file diff --git a/tests/msvc10/sdl_audio.vcxproj b/tests/msvc10/sdl_audio.vcxproj index 80a48d90..7f53853b 100644 --- a/tests/msvc10/sdl_audio.vcxproj +++ b/tests/msvc10/sdl_audio.vcxproj @@ -80,6 +80,22 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + <Link> + <AdditionalOptions>--preload-file ../sounds/alarmvictory_1.ogg@/sound.ogg --preload-file ../sounds/alarmcreatemiltaryfoot_1.wav@/sound2.wav --preload-file ../sounds/noise.ogg@/ --embed-file ../sounds/the_entertainer.ogg@/ --preload-file ../runner.py@/bad.ogg %(AdditionalOptions)</AdditionalOptions> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + <Link> + <AdditionalOptions>--preload-file ../sounds/alarmvictory_1.ogg@/sound.ogg --preload-file ../sounds/alarmcreatemiltaryfoot_1.wav@/sound2.wav --preload-file ../sounds/noise.ogg@/ --embed-file ../sounds/the_entertainer.ogg@/ --preload-file ../runner.py@/bad.ogg %(AdditionalOptions)</AdditionalOptions> + </Link> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\sdl_audio.c" /> </ItemGroup> diff --git a/tests/msvc10/sdl_canvas.vcxproj b/tests/msvc10/sdl_canvas.vcxproj index 42215b38..91fd1dd5 100644 --- a/tests/msvc10/sdl_canvas.vcxproj +++ b/tests/msvc10/sdl_canvas.vcxproj @@ -83,6 +83,16 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + </ItemDefinitionGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> diff --git a/tests/msvc10/sdl_gl_read.vcxproj b/tests/msvc10/sdl_gl_read.vcxproj index de6233c1..8ba31677 100644 --- a/tests/msvc10/sdl_gl_read.vcxproj +++ b/tests/msvc10/sdl_gl_read.vcxproj @@ -80,6 +80,18 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + <DisableWarnings>pointer-sign</DisableWarnings> + </ClCompile> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + <DisableWarnings>pointer-sign</DisableWarnings> + </ClCompile> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\sdl_gl_read.c" /> </ItemGroup> diff --git a/tests/msvc10/sdl_image.vcxproj b/tests/msvc10/sdl_image.vcxproj index 631ea5cd..543d6976 100644 --- a/tests/msvc10/sdl_image.vcxproj +++ b/tests/msvc10/sdl_image.vcxproj @@ -80,6 +80,23 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy;SCREENSHOT_DIRNAME=\"/assets\";SCREENSHOT_BASENAME=\"screenshot.jpg\"</PreprocessorDefinitions> + <EchoCommandLines>true</EchoCommandLines> + </ClCompile> + <Link> + <AdditionalOptions>--preload-file ../screenshot.jpg@/assets/ %(AdditionalOptions)</AdditionalOptions> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy;SCREENSHOT_DIRNAME=\"/assets\";SCREENSHOT_BASENAME=\"screenshot.jpg\"</PreprocessorDefinitions> + </ClCompile> + <Link> + <AdditionalOptions>--preload-file ../screenshot.jpg@/assets/ %(AdditionalOptions)</AdditionalOptions> + </Link> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\sdl_image.c" /> </ItemGroup> diff --git a/tests/msvc10/sdl_key.vcxproj b/tests/msvc10/sdl_key.vcxproj index 3840256a..cb7b888e 100644 --- a/tests/msvc10/sdl_key.vcxproj +++ b/tests/msvc10/sdl_key.vcxproj @@ -80,6 +80,16 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\sdl_key.c" /> </ItemGroup> diff --git a/tests/msvc10/sdl_mouse.vcxproj b/tests/msvc10/sdl_mouse.vcxproj index 7d582eb3..f3336a29 100644 --- a/tests/msvc10/sdl_mouse.vcxproj +++ b/tests/msvc10/sdl_mouse.vcxproj @@ -80,6 +80,16 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\sdl_mouse.c" /> </ItemGroup> diff --git a/tests/msvc10/sdl_ogl.vcxproj b/tests/msvc10/sdl_ogl.vcxproj index b96eac4d..fc8bdb4d 100644 --- a/tests/msvc10/sdl_ogl.vcxproj +++ b/tests/msvc10/sdl_ogl.vcxproj @@ -80,6 +80,22 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + <Link> + <AdditionalOptions>-s LEGACY_GL_EMULATION=1 --preload-file ../screenshot.png@/ %(AdditionalOptions)</AdditionalOptions> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <PreprocessorDefinitions>REPORT_RESULT=int dummy</PreprocessorDefinitions> + </ClCompile> + <Link> + <AdditionalOptions>-s LEGACY_GL_EMULATION=1 %(AdditionalOptions)</AdditionalOptions> + </Link> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\sdl_ogl.c" /> </ItemGroup> diff --git a/tests/msvc10/tests_msvc10.sln b/tests/msvc10/tests_msvc10.sln index 67333016..e606469b 100644 --- a/tests/msvc10/tests_msvc10.sln +++ b/tests/msvc10/tests_msvc10.sln @@ -53,8 +53,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "files", "files.vcxproj", "{ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hashtest", "hashtest.vcxproj", "{61D7F11F-25EE-4C2C-9D73-8601F68B055E}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "new", "new.vcxproj", "{5190107D-91B3-4EF8-82CB-08381DD19ABB}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdl_audio", "sdl_audio.vcxproj", "{F28A1DE1-5949-4AF5-8901-A37871C2514E}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdl_canvas", "sdl_canvas.vcxproj", "{CE360D01-4362-4FE4-A77E-8EF6E3F623CF}" @@ -196,10 +194,6 @@ Global {61D7F11F-25EE-4C2C-9D73-8601F68B055E}.Debug|Emscripten.Build.0 = Debug|Emscripten {61D7F11F-25EE-4C2C-9D73-8601F68B055E}.Release|Emscripten.ActiveCfg = Release|Emscripten {61D7F11F-25EE-4C2C-9D73-8601F68B055E}.Release|Emscripten.Build.0 = Release|Emscripten - {5190107D-91B3-4EF8-82CB-08381DD19ABB}.Debug|Emscripten.ActiveCfg = Debug|Emscripten - {5190107D-91B3-4EF8-82CB-08381DD19ABB}.Debug|Emscripten.Build.0 = Debug|Emscripten - {5190107D-91B3-4EF8-82CB-08381DD19ABB}.Release|Emscripten.ActiveCfg = Release|Emscripten - {5190107D-91B3-4EF8-82CB-08381DD19ABB}.Release|Emscripten.Build.0 = Release|Emscripten {F28A1DE1-5949-4AF5-8901-A37871C2514E}.Debug|Emscripten.ActiveCfg = Debug|Emscripten {F28A1DE1-5949-4AF5-8901-A37871C2514E}.Debug|Emscripten.Build.0 = Debug|Emscripten {F28A1DE1-5949-4AF5-8901-A37871C2514E}.Release|Emscripten.ActiveCfg = Release|Emscripten @@ -270,7 +264,6 @@ Global {65E0ED61-A813-4AEB-8DB0-A58657858EE2} = {8E55380B-7067-47FF-8B6B-6D656B8D8CC3} {BE6A123E-9729-44A3-976F-3C06A3724894} = {8E55380B-7067-47FF-8B6B-6D656B8D8CC3} {61D7F11F-25EE-4C2C-9D73-8601F68B055E} = {8E55380B-7067-47FF-8B6B-6D656B8D8CC3} - {5190107D-91B3-4EF8-82CB-08381DD19ABB} = {8E55380B-7067-47FF-8B6B-6D656B8D8CC3} {678A07B3-3A25-40E4-8A36-7A399056188A} = {8E55380B-7067-47FF-8B6B-6D656B8D8CC3} {C896D890-9132-4A2D-8BA8-0EB6888FEAC2} = {8E55380B-7067-47FF-8B6B-6D656B8D8CC3} {C29BBEE3-02D1-459A-B8BA-832A3439F12D} = {8E55380B-7067-47FF-8B6B-6D656B8D8CC3} diff --git a/tests/msvc10/twopart.vcxproj b/tests/msvc10/twopart.vcxproj index 7d299984..72cc0640 100644 --- a/tests/msvc10/twopart.vcxproj +++ b/tests/msvc10/twopart.vcxproj @@ -80,6 +80,16 @@ <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Emscripten'"> + <ClCompile> + <DisableWarnings>deprecated-writable-strings</DisableWarnings> + </ClCompile> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Emscripten'"> + <ClCompile> + <DisableWarnings>deprecated-writable-strings</DisableWarnings> + </ClCompile> + </ItemDefinitionGroup> <ItemGroup> <ClCompile Include="..\twopart_main.cpp" /> <ClCompile Include="..\twopart_side.cpp" /> diff --git a/tests/runner.py b/tests/runner.py index bbbc23e5..f0e61c4e 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -63,6 +63,10 @@ class RunnerCore(unittest.TestCase): self.working_dir = dirname os.chdir(dirname) + # Use emscripten root for node module lookup + scriptdir = os.path.dirname(os.path.abspath(__file__)) + os.environ['NODE_PATH'] = os.path.join(scriptdir, '..', 'node_modules') + if not self.save_dir: self.has_prev_ll = False for temp_file in os.listdir(TEMP_DIR): diff --git a/tests/sockets/test_getaddrinfo.c b/tests/sockets/test_getaddrinfo.c new file mode 100644 index 00000000..717a9ae7 --- /dev/null +++ b/tests/sockets/test_getaddrinfo.c @@ -0,0 +1,197 @@ +#include <arpa/inet.h> +#include <sys/socket.h> +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +int main() { + struct addrinfo hints; + struct addrinfo *servinfo; + struct sockaddr_in *sa4; + struct sockaddr_in6 *sa6; + int err; + + // no name or service + err = getaddrinfo(NULL, NULL, NULL, &servinfo); + assert(err == EAI_NONAME); + + // invalid socket type + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = 9999; + err = getaddrinfo("www.mozilla.org", "80", &hints, &servinfo); +#ifdef __APPLE__ + assert(err == EAI_BADHINTS); +#else + assert(err == EAI_SOCKTYPE); +#endif + + // invalid family + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNIX; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "80", &hints, &servinfo); + assert(err == EAI_FAMILY); + + // invalid service + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "foobar", &hints, &servinfo); +#ifdef __APPLE__ + assert(err == EAI_NONAME); +#else + assert(err == EAI_SERVICE); +#endif + + // test loopback resolution (ipv4) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(NULL, "80", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*(uint32_t*)&(sa4->sin_addr) == ntohl(INADDR_LOOPBACK)); + assert(sa4->sin_port == ntohs(80)); + freeaddrinfo(servinfo); + + // test loopback resolution (ipv6) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(NULL, "81", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_STREAM); + memcmp(&sa6->sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback)); + assert(sa6->sin6_port == ntohs(81)); + freeaddrinfo(servinfo); + + // test bind preparation + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(NULL, "82", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*(uint32_t*)&(sa4->sin_addr) == 0); + assert(sa4->sin_port == ntohs(82)); + freeaddrinfo(servinfo); + + // test numeric address (ipv4) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + err = getaddrinfo("1.2.3.4", "83", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_DGRAM); + assert(*(uint32_t*)&(sa4->sin_addr) == 67305985); + assert(sa4->sin_port == ntohs(83)); + freeaddrinfo(servinfo); + + // test numeric address (ipv4 address specified as ipv6 with AI_V4MAPPED) + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_V4MAPPED; + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("1.2.3.4", "84", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*((uint32_t*)&(sa6->sin6_addr)+2) == htonl(0xffff)); + assert(*((uint32_t*)&(sa6->sin6_addr)+3) == 67305985); + assert(sa6->sin6_port == ntohs(84)); + freeaddrinfo(servinfo); + + // test numeric address (ipv4 address specified as ipv6 without AI_V4MAPPED) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("1.2.3.4", "85", &hints, &servinfo); +#ifdef __linux__ + assert(err == -9 /* EAI_ADDRFAMILY */); +#else + assert(err == EAI_NONAME); +#endif + + // test numeric address (ipv6) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + err = getaddrinfo("2001:0db8:85a3:0042:1000:8a2e:0370:7334", "86", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_DGRAM); + assert(*((uint32_t*)&(sa6->sin6_addr)+0) == -1207107296); + assert(*((uint32_t*)&(sa6->sin6_addr)+1) == 1107338117); + assert(*((uint32_t*)&(sa6->sin6_addr)+2) == 780795920); + assert(*((uint32_t*)&(sa6->sin6_addr)+3) == 879980547); + assert(sa6->sin6_port == ntohs(86)); + freeaddrinfo(servinfo); + + // test numeric address (ipv6 address specified as ipv4) + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("2001:0db8:85a3:0042:1000:8a2e:0370:7334", "87", &hints, &servinfo); +#ifdef __linux__ + assert(err == -9 /* EAI_ADDRFAMILY */); +#else + assert(err == EAI_NONAME); +#endif + + // test non-numeric host with AI_NUMERICHOST + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "88", &hints, &servinfo); + assert(err == EAI_NONAME); + + // test non-numeric host with AF_INET + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "89", &hints, &servinfo); + assert(!err); + sa4 = ((struct sockaddr_in*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(sa4->sin_port == ntohs(89)); + + // test non-numeric host with AF_INET6 + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "90", &hints, &servinfo); + assert(!err); + sa6 = ((struct sockaddr_in6*)servinfo->ai_addr); + assert(servinfo->ai_family == AF_INET6); + assert(servinfo->ai_socktype == SOCK_STREAM); + assert(*((uint32_t*)&(sa6->sin6_addr)+0) != 0 || + *((uint32_t*)&(sa6->sin6_addr)+1) != 0 || + *((uint32_t*)&(sa6->sin6_addr)+2) != 0 || + *((uint32_t*)&(sa6->sin6_addr)+3) != 0); + assert(sa6->sin6_port == ntohs(90)); + + puts("success"); + + return EXIT_SUCCESS; +} + diff --git a/tests/sockets/test_sockets_gethostbyname.c b/tests/sockets/test_gethostbyname.c index 12fc6d9d..de7da706 100644 --- a/tests/sockets/test_sockets_gethostbyname.c +++ b/tests/sockets/test_gethostbyname.c @@ -11,16 +11,6 @@ #include <emscripten.h> #endif -int sockfd; - -void finish(int result) { - close(sockfd); -#if EMSCRIPTEN - REPORT_RESULT(); -#endif - exit(result); -} - int main() { char str[INET_ADDRSTRLEN]; struct in_addr addr; @@ -29,6 +19,8 @@ int main() { // resolve the hostname ot an actual address struct hostent *host = gethostbyname("slashdot.org"); + assert(host->h_addrtype == AF_INET); + assert(host->h_length == sizeof(uint32_t)); // convert the raw address to a string char **raw_addr_list = host->h_addr_list; @@ -44,6 +36,8 @@ int main() { struct hostent *host1 = gethostbyaddr(&addr, sizeof(addr), host->h_addrtype); assert(strstr(host1->h_name, "slashdot.org")); + puts("success"); + return EXIT_SUCCESS; } diff --git a/tests/sockets/test_getnameinfo.c b/tests/sockets/test_getnameinfo.c new file mode 100644 index 00000000..c3fec6b4 --- /dev/null +++ b/tests/sockets/test_getnameinfo.c @@ -0,0 +1,101 @@ +#include <arpa/inet.h> +#include <sys/socket.h> +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if EMSCRIPTEN +#include <emscripten.h> +#endif + +int main() { + struct addrinfo hints; + struct addrinfo *servinfo; + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + char node[256]; + char serv[256]; + int flags; + int err; + +#ifndef __APPLE__ + // incorrect sockaddr size + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4)-1, NULL, 0, NULL, 0, 0); + assert(err == EAI_FAMILY); + + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; + err = getnameinfo((struct sockaddr*)&sa6, sizeof(sa6)-1, NULL, 0, NULL, 0, 0); + assert(err == EAI_FAMILY); + + // invalid family + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = 9999; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), NULL, 0, NULL, 0, 0); + assert(err == EAI_FAMILY); +#endif + + // NI_NUMERICHOST and NI_NAMEREQD conflict + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + flags = NI_NUMERICHOST | NI_NAMEREQD; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(err == EAI_NONAME); + + // too small of buffer + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + sa4.sin_port = htons(54321); + flags = NI_NUMERICHOST; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, 1, serv, sizeof(serv), flags); + assert(err == EAI_OVERFLOW); + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, 1, flags); + assert(err == EAI_OVERFLOW); + + // NI_NAMEREQD and lookup failed + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + flags = NI_NAMEREQD; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(err == EAI_NONAME); + + // lookup failed + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(err == EAI_NONAME); + + // no lookup + memset(&sa4, 0, sizeof(sa4)); + sa4.sin_family = AF_INET; + *(uint32_t*)&sa4.sin_addr = 67305985; + sa4.sin_port = htons(54321); + flags = NI_NUMERICHOST; + err = getnameinfo((struct sockaddr*)&sa4, sizeof(sa4), node, sizeof(node), serv, sizeof(serv), flags); + assert(!err); + assert(!strcmp(node, "1.2.3.4")); + assert(!strcmp(serv, "54321")); + + // lookup + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo("www.mozilla.org", "54321", &hints, &servinfo); + assert(!err); + flags = NI_NAMEREQD; + err = getnameinfo(servinfo->ai_addr, servinfo->ai_addrlen, node, sizeof(node), serv, sizeof(serv), flags); + assert(!err); + assert(strstr(node, "mozilla")); + assert(!strcmp(serv, "54321")); + + puts("success"); + + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/tests/sockets/test_sockets_echo_client.c b/tests/sockets/test_sockets_echo_client.c index 6b3ccef3..f6ea85cf 100644 --- a/tests/sockets/test_sockets_echo_client.c +++ b/tests/sockets/test_sockets_echo_client.c @@ -38,7 +38,10 @@ int echo_read; int echo_wrote; void finish(int result) { - close(server.fd); + if (server.fd) { + close(server.fd); + server.fd = 0; + } #if EMSCRIPTEN REPORT_RESULT(); #endif @@ -76,7 +79,12 @@ void main_loop(void *arg) { assert(available); res = do_msg_read(server.fd, &server.msg, echo_read, 0, NULL, NULL); - if (res != -1) echo_read += res; + if (res == 0) { + perror("server closed"); + finish(EXIT_FAILURE); + } else if (res != -1) { + echo_read += res; + } // once we've read the entire message, validate it if (echo_read >= server.msg.length) { @@ -89,7 +97,12 @@ void main_loop(void *arg) { } res = do_msg_write(server.fd, &echo_msg, echo_wrote, 0, NULL, 0); - if (res != -1) echo_wrote += res; + if (res == 0) { + perror("server closed"); + finish(EXIT_FAILURE); + } else if (res != -1) { + echo_wrote += res; + } // once we're done writing the message, read it back if (echo_wrote >= echo_msg.length) { diff --git a/tests/sockets/test_sockets_echo_server.c b/tests/sockets/test_sockets_echo_server.c index f01004c3..dbc912a6 100644 --- a/tests/sockets/test_sockets_echo_server.c +++ b/tests/sockets/test_sockets_echo_server.c @@ -1,6 +1,7 @@ #include <assert.h> #include <errno.h> #include <fcntl.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -38,8 +39,14 @@ server_t server; client_t client; void cleanup() { - if (server.fd) close(server.fd); - if (client.fd) close(client.fd); + if (client.fd) { + close(client.fd); + client.fd = 0; + } + if (server.fd) { + close(server.fd); + server.fd = 0; + } } void main_loop(void *arg) { @@ -83,7 +90,13 @@ void main_loop(void *arg) { } res = do_msg_read(fd, &client.msg, client.read, 0, (struct sockaddr *)&client.addr, &addrlen); - if (res != -1) client.read += res; + if (res == 0) { + // client disconnected + memset(&client, 0, sizeof(client_t)); + return; + } else if (res != -1) { + client.read += res; + } // once we've read the entire message, echo it back if (client.read >= client.msg.length) { @@ -96,12 +109,22 @@ void main_loop(void *arg) { } res = do_msg_write(fd, &client.msg, client.wrote, 0, (struct sockaddr *)&client.addr, sizeof(client.addr)); - if (res != -1) client.wrote += res; + if (res == 0) { + // client disconnected + memset(&client, 0, sizeof(client_t)); + return; + } else if (res != -1) { + client.wrote += res; + } - // close the client once we've echo'd back the entire message if (client.wrote >= client.msg.length) { + client.wrote = 0; + client.state = MSG_READ; + +#if CLOSE_CLIENT_AFTER_ECHO close(client.fd); memset(&client, 0, sizeof(client_t)); +#endif } } } @@ -111,7 +134,7 @@ int main() { int res; atexit(cleanup); - //signal(SIGTERM, cleanup); + signal(SIGTERM, cleanup); memset(&server, 0, sizeof(server_t)); memset(&client, 0, sizeof(client_t)); diff --git a/tests/sockets/test_sockets_msg.h b/tests/sockets/test_sockets_msg.h index 30094d65..0e68803c 100644 --- a/tests/sockets/test_sockets_msg.h +++ b/tests/sockets/test_sockets_msg.h @@ -17,9 +17,10 @@ int do_msg_read(int sockfd, msg_t *msg, int offset, int length, struct sockaddr return res; } assert(res != 0); - msg->buffer = malloc(msg->length); printf("do_msg_read: allocating %d bytes for message\n", msg->length); + + msg->buffer = malloc(msg->length); } // read the actual message @@ -52,6 +53,7 @@ int do_msg_write(int sockfd, msg_t *msg, int offset, int length, struct sockaddr assert(errno == EAGAIN); return res; } + printf("do_msg_write: sending message header for %d bytes\n", msg->length); assert(res == sizeof(int)); } diff --git a/tests/sockets/test_sockets_partial_server.c b/tests/sockets/test_sockets_partial_server.c index 44ad40a3..19f7f2af 100644 --- a/tests/sockets/test_sockets_partial_server.c +++ b/tests/sockets/test_sockets_partial_server.c @@ -1,6 +1,7 @@ #include <assert.h> #include <errno.h> #include <fcntl.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -18,8 +19,14 @@ int serverfd = 0; int clientfd = 0; void cleanup() { - if (serverfd) close(serverfd); - if (clientfd) close(clientfd); + if (serverfd) { + close(serverfd); + serverfd = 0; + } + if (clientfd) { + close(clientfd); + clientfd = 0; + } } void do_send(int sockfd) { @@ -86,7 +93,7 @@ int main() { int res; atexit(cleanup); - //signal(SIGTERM, cleanup); + signal(SIGTERM, cleanup); // create the socket serverfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); diff --git a/tests/sockets/test_sockets_select_server_closes_connection_client_rw.c b/tests/sockets/test_sockets_select_server_closes_connection_client_rw.c index 198ad232..25dcdd05 100644 --- a/tests/sockets/test_sockets_select_server_closes_connection_client_rw.c +++ b/tests/sockets/test_sockets_select_server_closes_connection_client_rw.c @@ -89,7 +89,12 @@ void main_loop(void *arg) { // read a single byte transferAmount = do_msg_read(sockfd, &readmsg, readPos, 1, NULL, NULL); - if (transferAmount != -1) readPos += transferAmount; + if (transferAmount == 0) { + perror("server closed"); + finish(EXIT_FAILURE); + } else if (transferAmount != -1) { + readPos += transferAmount; + } // if successfully reading 1 byte, switch to next state if (readPos >= 1) { @@ -122,7 +127,12 @@ void main_loop(void *arg) { // read a single byte transferAmount = do_msg_read(sockfd, &readmsg, readPos, 1, NULL, NULL); - if (transferAmount != -1) readPos += transferAmount; + if (transferAmount == 0) { + perror("server closed"); + finish(EXIT_FAILURE); + } else if (transferAmount != -1) { + readPos += transferAmount; + } // with 10 bytes read the inQueue is empty => switch state if (readPos >= readmsg.length) { diff --git a/tests/sockets/test_sockets_select_server_down_client.c b/tests/sockets/test_sockets_select_server_down_client.c index e05bd4c8..27e200e0 100644 --- a/tests/sockets/test_sockets_select_server_down_client.c +++ b/tests/sockets/test_sockets_select_server_down_client.c @@ -48,7 +48,7 @@ void iter(void *arg) { char buffer[1024]; int n = recv(sockfd, buffer, sizeof(buffer), 0); if (n == -1 && retries++ > 10) { - perror("revv failed"); + perror("recv failed"); finish(EXIT_FAILURE); } else if (!n) { perror("Connection to websocket server failed as expected."); diff --git a/tests/termios/test_tcgetattr.c b/tests/termios/test_tcgetattr.c new file mode 100644 index 00000000..2b3780ee --- /dev/null +++ b/tests/termios/test_tcgetattr.c @@ -0,0 +1,61 @@ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +static void create_file(const char *path, const char *buffer, int mode) { + int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, mode); + assert(fd >= 0); + + int err = write(fd, buffer, sizeof(char) * strlen(buffer)); + assert(err == (sizeof(char) * strlen(buffer))); + + close(fd); +} + +void setup() { + create_file("test.txt", "abcdefg", 0666); +} + +void cleanup() { + unlink("test.txt"); +} + +void test() { + struct termios tc; + int ret; + int fd; + + fd = open("test.txt", O_RDONLY); + + ret = tcgetattr(fd, &tc); + assert(ret == -1); + assert(errno = ENOTTY); + + ret = tcgetattr(STDIN_FILENO, &tc); + assert(!ret); + + ret = tcsetattr(fd, 0, &tc); + assert(ret == -1); + assert(errno = ENOTTY); + + ret = tcsetattr(STDIN_FILENO, 0, &tc); + assert(!ret); + + close(fd); + + puts("success"); +} + +int main() { + atexit(cleanup); + signal(SIGABRT, cleanup); + setup(); + test(); + return EXIT_SUCCESS; +} diff --git a/tests/test_browser.py b/tests/test_browser.py index a35ce177..7f085f15 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -794,6 +794,9 @@ If manually bisecting: def test_glut_touchevents(self): self.btest('glut_touchevents.c', '1') + def test_emscripten_get_now(self): + self.btest('emscripten_get_now.cpp', '1') + def test_file_db(self): secret = str(time.time()) open('moar.txt', 'w').write(secret) @@ -1404,3 +1407,8 @@ If manually bisecting: def test_emscripten_async_wget2(self): self.btest('http.cpp', expected='0', args=['-I' + path_from_root('tests')]) + + def test_module(self): + Popen([PYTHON, EMCC, path_from_root('tests', 'browser_module.cpp'), '-o', 'module.js', '-O2', '-s', 'SIDE_MODULE=1', '-s', 'DLOPEN_SUPPORT=1', '-s', 'EXPORTED_FUNCTIONS=["_one", "_two"]']).communicate() + self.btest('browser_main.cpp', args=['-O2', '-s', 'MAIN_MODULE=1', '-s', 'DLOPEN_SUPPORT=1'], expected='8') + diff --git a/tests/test_core.py b/tests/test_core.py index 17707c44..6bb5ccb6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3734,6 +3734,13 @@ def process(filename): Settings.EXPORT_ALL = 1 self.do_run(src, 'hello world!\n*100*\n*fivesix*\nmann\n', post_build=check) + def test_emscripten_get_now(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('requires ta2') + + if self.run_name == 'o2': + self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.do_run(open(path_from_root('tests', 'emscripten_get_now.cpp')).read(), 'Timer resolution is good.') + def test_inlinejs(self): if Settings.ASM_JS: return self.skip('asm does not support random code, TODO: something that works in asm') src = r''' @@ -4670,6 +4677,10 @@ The current type of b is: 9 expected = open(path_from_root('tests', 'pthread', 'specific.c.txt'), 'r').read() self.do_run(src, expected, force_c=True) + def test_tcgetattr(self): + src = open(path_from_root('tests', 'termios', 'test_tcgetattr.c'), 'r').read() + self.do_run(src, 'success', force_c=True) + def test_time(self): # XXX Not sure what the right output is here. Looks like the test started failing with daylight savings changes. Modified it to pass again. src = open(path_from_root('tests', 'time', 'src.c'), 'r').read() @@ -5569,11 +5580,53 @@ The current type of b is: 9 Settings.RUNTIME_LINKED_LIBS = ['liblib.so']; self.do_run(main, 'supp: 54,2\nmain: 56\nsupp see: 543\nmain see: 76\nok.') - def test_dlfcn_basic(self): - if Settings.ASM_JS: return self.skip('TODO: dlopen in asm') + def can_dlfcn(self): + if self.emcc_args and '--memory-init-file' in self.emcc_args: + for i in range(len(self.emcc_args)): + if self.emcc_args[i] == '--memory-init-file': + self.emcc_args = self.emcc_args[:i] + self.emcc_args[i+2:] + break - Settings.NAMED_GLOBALS = 1 + if Settings.ASM_JS: + Settings.DLOPEN_SUPPORT = 1 + else: + Settings.NAMED_GLOBALS = 1 + + if not self.is_le32(): + self.skip('need le32 for dlfcn support') + return False + else: + return True + + def prep_dlfcn_lib(self): + if Settings.ASM_JS: + Settings.MAIN_MODULE = 0 + Settings.SIDE_MODULE = 1 + else: + Settings.BUILD_AS_SHARED_LIB = 1 + Settings.INCLUDE_FULL_LIBRARY = 0 + def prep_dlfcn_main(self): + if Settings.ASM_JS: + Settings.MAIN_MODULE = 1 + Settings.SIDE_MODULE = 0 + else: + Settings.BUILD_AS_SHARED_LIB = 0 + Settings.INCLUDE_FULL_LIBRARY = 1 + + dlfcn_post_build = ''' +def process(filename): + src = open(filename, 'r').read().replace( + '// {{PRE_RUN_ADDITIONS}}', + "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" + ) + open(filename, 'w').write(src) +''' + + def test_dlfcn_basic(self): + if not self.can_dlfcn(): return + + self.prep_dlfcn_lib() lib_src = ''' #include <cstdio> @@ -5588,10 +5641,10 @@ The current type of b is: 9 ''' dirname = self.get_dir() filename = os.path.join(dirname, 'liblib.cpp') - Settings.BUILD_AS_SHARED_LIB = 1 self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + self.prep_dlfcn_main() src = ''' #include <cstdio> #include <dlfcn.h> @@ -5610,28 +5663,17 @@ The current type of b is: 9 return 0; } ''' - Settings.BUILD_AS_SHARED_LIB = 0 - add_pre_run_and_checks = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" - ) - open(filename, 'w').write(src) -''' self.do_run(src, 'Constructing main object.\nConstructing lib object.\n', - post_build=add_pre_run_and_checks) + post_build=self.dlfcn_post_build) def test_dlfcn_qsort(self): - if self.emcc_args is None: return self.skip('requires emcc') - if Settings.ASM_JS: return self.skip('TODO: dlopen in asm') - - Settings.LINKABLE = 1 - Settings.NAMED_GLOBALS = 1 + if not self.can_dlfcn(): return if Settings.USE_TYPED_ARRAYS == 2: Settings.CORRECT_SIGNS = 1 # Needed for unsafe optimizations + self.prep_dlfcn_lib() + Settings.EXPORTED_FUNCTIONS = ['_get_cmp'] lib_src = ''' int lib_cmp(const void* left, const void* right) { const int* a = (const int*) left; @@ -5649,11 +5691,11 @@ def process(filename): ''' dirname = self.get_dir() filename = os.path.join(dirname, 'liblib.cpp') - Settings.BUILD_AS_SHARED_LIB = 1 - Settings.EXPORTED_FUNCTIONS = ['_get_cmp'] self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + self.prep_dlfcn_main() + Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc'] src = ''' #include <stdio.h> #include <stdlib.h> @@ -5675,6 +5717,13 @@ def process(filename): CMP_TYPE lib_cmp_ptr; int arr[5] = {4, 2, 5, 1, 3}; + qsort((void*)arr, 5, sizeof(int), main_cmp); + printf("Sort with main comparison: "); + for (int i = 0; i < 5; i++) { + printf("%d ", arr[i]); + } + printf("\\n"); + lib_handle = dlopen("liblib.so", RTLD_NOW); if (lib_handle == NULL) { printf("Could not load lib.\\n"); @@ -5686,14 +5735,6 @@ def process(filename): return 1; } lib_cmp_ptr = getter_ptr(); - - qsort((void*)arr, 5, sizeof(int), main_cmp); - printf("Sort with main comparison: "); - for (int i = 0; i < 5; i++) { - printf("%d ", arr[i]); - } - printf("\\n"); - qsort((void*)arr, 5, sizeof(int), lib_cmp_ptr); printf("Sort with lib comparison: "); for (int i = 0; i < 5; i++) { @@ -5704,27 +5745,22 @@ def process(filename): return 0; } ''' - Settings.BUILD_AS_SHARED_LIB = 0 - Settings.EXPORTED_FUNCTIONS = ['_main'] - add_pre_run_and_checks = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" - ) - open(filename, 'w').write(src) -''' self.do_run(src, 'Sort with main comparison: 5 4 3 2 1 *Sort with lib comparison: 1 2 3 4 5 *', output_nicerizer=lambda x, err: x.replace('\n', '*'), - post_build=add_pre_run_and_checks) + post_build=self.dlfcn_post_build) + + if Settings.ASM_JS and os.path.exists(SPIDERMONKEY_ENGINE[0]): + out = run_js('liblib.so', engine=SPIDERMONKEY_ENGINE, full_output=True, stderr=STDOUT) + assert 'asm' in out + self.validate_asmjs(out) def test_dlfcn_data_and_fptr(self): - if Settings.ASM_JS: return self.skip('TODO: dlopen in asm') - if Building.LLVM_OPTS: return self.skip('LLVM opts will optimize out parent_func') + if Settings.ASM_JS: return self.skip('this is not a valid case - libraries should not be able to access their parents globals willy nilly') + if not self.can_dlfcn(): return - Settings.LINKABLE = 1 - Settings.NAMED_GLOBALS = 1 + if Building.LLVM_OPTS: return self.skip('LLVM opts will optimize out parent_func') + self.prep_dlfcn_lib() lib_src = ''' #include <stdio.h> @@ -5749,21 +5785,23 @@ def process(filename): ''' dirname = self.get_dir() filename = os.path.join(dirname, 'liblib.cpp') - Settings.BUILD_AS_SHARED_LIB = 1 Settings.EXPORTED_FUNCTIONS = ['_func'] Settings.EXPORTED_GLOBALS = ['_global'] self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + self.prep_dlfcn_main() + Settings.LINKABLE = 1 src = ''' #include <stdio.h> #include <dlfcn.h> + #include <emscripten.h> typedef void (*FUNCTYPE(int, void(*)()))(); FUNCTYPE func; - void parent_func() { + void EMSCRIPTEN_KEEPALIVE parent_func() { printf("parent_func called from child\\n"); } @@ -5807,23 +5845,14 @@ def process(filename): return 0; } ''' - Settings.BUILD_AS_SHARED_LIB = 0 Settings.EXPORTED_FUNCTIONS = ['_main'] Settings.EXPORTED_GLOBALS = [] - add_pre_run_and_checks = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" - ) - open(filename, 'w').write(src) -''' self.do_run(src, 'In func: 13*First calling main_fptr from lib.*Second calling lib_fptr from main.*parent_func called from child*parent_func called from child*Var: 42*', output_nicerizer=lambda x, err: x.replace('\n', '*'), - post_build=add_pre_run_and_checks) + post_build=self.dlfcn_post_build) def test_dlfcn_alias(self): - if Settings.ASM_JS: return self.skip('TODO: dlopen in asm') + if Settings.ASM_JS: return self.skip('this is not a valid case - libraries should not be able to access their parents globals willy nilly') Settings.LINKABLE = 1 Settings.NAMED_GLOBALS = 1 @@ -5865,29 +5894,23 @@ def process(filename): Settings.BUILD_AS_SHARED_LIB = 0 Settings.INCLUDE_FULL_LIBRARY = 1 Settings.EXPORTED_FUNCTIONS = ['_main'] - add_pre_run_and_checks = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" - ) - open(filename, 'w').write(src) -''' self.do_run(src, 'Parent global: 123.*Parent global: 456.*', output_nicerizer=lambda x, err: x.replace('\n', '*'), - post_build=add_pre_run_and_checks, + post_build=self.dlfcn_post_build, extra_emscripten_args=['-H', 'libc/fcntl.h,libc/sys/unistd.h,poll.h,libc/math.h,libc/time.h,libc/langinfo.h']) Settings.INCLUDE_FULL_LIBRARY = 0 def test_dlfcn_varargs(self): - if Settings.ASM_JS: return self.skip('TODO: dlopen in asm') + if Settings.ASM_JS: return self.skip('this is not a valid case - libraries should not be able to access their parents globals willy nilly') + + if not self.can_dlfcn(): return Settings.LINKABLE = 1 - Settings.NAMED_GLOBALS = 1 if Building.LLVM_OPTS == 2: return self.skip('LLVM LTO will optimize things that prevent shared objects from working') if Settings.QUANTUM_SIZE == 1: return self.skip('FIXME: Add support for this') + self.prep_dlfcn_lib() lib_src = r''' void print_ints(int n, ...); extern "C" void func() { @@ -5896,15 +5919,16 @@ def process(filename): ''' dirname = self.get_dir() filename = os.path.join(dirname, 'liblib.cpp') - Settings.BUILD_AS_SHARED_LIB = 1 Settings.EXPORTED_FUNCTIONS = ['_func'] self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + self.prep_dlfcn_main() src = r''' #include <stdarg.h> #include <stdio.h> #include <dlfcn.h> + #include <assert.h> void print_ints(int n, ...) { va_list args; @@ -5922,24 +5946,16 @@ def process(filename): print_ints(2, 100, 200); lib_handle = dlopen("liblib.so", RTLD_NOW); + assert(lib_handle); fptr = (void (*)())dlsym(lib_handle, "func"); fptr(); return 0; } ''' - Settings.BUILD_AS_SHARED_LIB = 0 Settings.EXPORTED_FUNCTIONS = ['_main'] - add_pre_run_and_checks = ''' -def process(filename): - src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" - ) - open(filename, 'w').write(src) -''' self.do_run(src, '100\n200\n13\n42\n', - post_build=add_pre_run_and_checks) + post_build=self.dlfcn_post_build) def test_dlfcn_self(self): if Settings.USE_TYPED_ARRAYS == 1: return self.skip('Does not work with USE_TYPED_ARRAYS=1') @@ -5948,14 +5964,15 @@ def process(filename): src = r''' #include <stdio.h> #include <dlfcn.h> +#include <emscripten.h> -int global = 123; +int EMSCRIPTEN_KEEPALIVE global = 123; -extern "C" __attribute__((noinline)) void foo(int x) { +extern "C" EMSCRIPTEN_KEEPALIVE void foo(int x) { printf("%d\n", x); } -extern "C" __attribute__((noinline)) void repeatable() { +extern "C" EMSCRIPTEN_KEEPALIVE void repeatable() { void* self = dlopen(NULL, RTLD_LAZY); int* global_ptr = (int*)dlsym(self, "global"); void (*foo_ptr)(int) = (void (*)(int))dlsym(self, "foo"); @@ -5976,15 +5993,239 @@ return 0; break else: raise Exception('Could not find symbol table!') - import json - table = json.loads(table[table.find('{'):table.rfind('}')+1]) - actual = list(sorted(table.keys())) + table = table[table.find('{'):table.rfind('}')+1] # ensure there aren't too many globals; we don't want unnamed_addr - assert actual == ['_foo', '_global', '_main', '_repeatable'], \ - "Symbol table does not match: %s" % actual - + assert table.count(',') <= 4 self.do_run(src, '123\n123', post_build=(None, post)) + + def test_dlfcn_unique_sig(self): + if not self.can_dlfcn(): return + + self.prep_dlfcn_lib() + lib_src = ''' + #include <stdio.h> + + int myfunc(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m) { + return 13; + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_myfunc'] + dirname = self.get_dir() + filename = os.path.join(dirname, 'liblib.c') + self.build(lib_src, dirname, filename) + shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + + self.prep_dlfcn_main() + src = ''' + #include <assert.h> + #include <stdio.h> + #include <dlfcn.h> + + typedef int (*FUNCTYPE)(int, int, int, int, int, int, int, int, int, int, int, int, int); + + int main() { + void *lib_handle; + FUNCTYPE func_ptr; + + lib_handle = dlopen("liblib.so", RTLD_NOW); + assert(lib_handle != NULL); + + func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc"); + assert(func_ptr != NULL); + assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13); + + puts("success"); + + return 0; + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc'] + self.do_run(src, 'success', force_c=True, post_build=self.dlfcn_post_build) + + def test_dlfcn_stacks(self): + if not self.can_dlfcn(): return + + self.prep_dlfcn_lib() + lib_src = ''' + #include <assert.h> + #include <stdio.h> + #include <string.h> + + int myfunc(const char *input) { + char bigstack[1024] = { 0 }; + + // make sure we didn't just trample the stack! + assert(!strcmp(input, "foobar")); + + snprintf(bigstack, sizeof(bigstack), input); + return strlen(bigstack); + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_myfunc'] + dirname = self.get_dir() + filename = os.path.join(dirname, 'liblib.c') + self.build(lib_src, dirname, filename) + shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + + self.prep_dlfcn_main() + src = ''' + #include <assert.h> + #include <stdio.h> + #include <dlfcn.h> + + typedef int (*FUNCTYPE)(const char *); + + int main() { + void *lib_handle; + FUNCTYPE func_ptr; + char str[128]; + + snprintf(str, sizeof(str), "foobar"); + + lib_handle = dlopen("liblib.so", RTLD_NOW); + assert(lib_handle != NULL); + + func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc"); + assert(func_ptr != NULL); + assert(func_ptr(str) == 6); + + puts("success"); + + return 0; + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc'] + self.do_run(src, 'success', force_c=True, post_build=self.dlfcn_post_build) + + def test_dlfcn_funcs(self): + if not self.can_dlfcn(): return + + self.prep_dlfcn_lib() + lib_src = r''' + #include <assert.h> + #include <stdio.h> + #include <string.h> + + typedef void (*voidfunc)(); + typedef void (*intfunc)(int); + + void callvoid(voidfunc f) { f(); } + void callint(voidfunc f, int x) { f(x); } + + void void_0() { printf("void 0\n"); } + void void_1() { printf("void 1\n"); } + voidfunc getvoid(int i) { + switch(i) { + case 0: return void_0; + case 1: return void_1; + default: return NULL; + } + } + + void int_0(int x) { printf("int 0 %d\n", x); } + void int_1(int x) { printf("int 1 %d\n", x); } + intfunc getint(int i) { + switch(i) { + case 0: return int_0; + case 1: return int_1; + default: return NULL; + } + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_callvoid', '_callint', '_getvoid', '_getint'] + dirname = self.get_dir() + filename = os.path.join(dirname, 'liblib.c') + self.build(lib_src, dirname, filename) + shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + + self.prep_dlfcn_main() + src = r''' + #include <assert.h> + #include <stdio.h> + #include <dlfcn.h> + + typedef void (*voidfunc)(); + typedef void (*intfunc)(int); + + typedef void (*voidcaller)(voidfunc); + typedef void (*intcaller)(intfunc, int); + + typedef voidfunc (*voidgetter)(int); + typedef intfunc (*intgetter)(int); + + void void_main() { printf("main.\n"); } + void int_main(int x) { printf("main %d\n", x); } + + int main() { + printf("go\n"); + void *lib_handle; + lib_handle = dlopen("liblib.so", RTLD_NOW); + assert(lib_handle != NULL); + + voidcaller callvoid = (voidcaller)dlsym(lib_handle, "callvoid"); + assert(callvoid != NULL); + callvoid(void_main); + + intcaller callint = (intcaller)dlsym(lib_handle, "callint"); + assert(callint != NULL); + callint(int_main, 201); + + voidgetter getvoid = (voidgetter)dlsym(lib_handle, "getvoid"); + assert(getvoid != NULL); + callvoid(getvoid(0)); + callvoid(getvoid(1)); + + intgetter getint = (intgetter)dlsym(lib_handle, "getint"); + assert(getint != NULL); + callint(getint(0), 54); + callint(getint(1), 9000); + + assert(getint(1000) == NULL); + + puts("ok"); + return 0; + } + ''' + Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc'] + self.do_run(src, '''go +main. +main 201 +void 0 +void 1 +int 0 54 +int 1 9000 +ok +''', force_c=True, post_build=self.dlfcn_post_build) + + def test_dlfcn_mallocs(self): + if not Settings.ASM_JS: return self.skip('needs asm') + + if not self.can_dlfcn(): return + + Settings.TOTAL_MEMORY = 64*1024*1024 # will be exhausted without functional malloc/free + + self.prep_dlfcn_lib() + lib_src = r''' + #include <assert.h> + #include <stdio.h> + #include <string.h> + #include <stdlib.h> + + void *mallocproxy(int n) { return malloc(n); } + void freeproxy(void *p) { free(p); } + ''' + Settings.EXPORTED_FUNCTIONS = ['_mallocproxy', '_freeproxy'] + dirname = self.get_dir() + filename = os.path.join(dirname, 'liblib.c') + self.build(lib_src, dirname, filename) + shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) + + self.prep_dlfcn_main() + src = open(path_from_root('tests', 'dlmalloc_proxy.c')).read() + Settings.EXPORTED_FUNCTIONS = ['_main', '_malloc', '_free'] + self.do_run(src, '''*294,153*''', force_c=True, post_build=self.dlfcn_post_build) + def test_rand(self): return self.skip('rand() is now random') # FIXME @@ -7428,168 +7669,6 @@ def process(filename): ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) - def test_inet(self): - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - - int main() { - printf("*%x,%x,%x,%x,%x,%x*\n", htonl(0xa1b2c3d4), htonl(0xfe3572e0), htonl(0x07abcdf0), htons(0xabcd), ntohl(0x43211234), ntohs(0xbeaf)); - in_addr_t i = inet_addr("190.180.10.78"); - printf("%x\n", i); - return 0; - } - ''' - self.do_run(src, '*d4c3b2a1,e07235fe,f0cdab07,cdab,34122143,afbe*\n4e0ab4be\n') - - def test_inet2(self): - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - - int main() { - struct in_addr x, x2; - int *y = (int*)&x; - *y = 0x12345678; - printf("%s\n", inet_ntoa(x)); - int r = inet_aton(inet_ntoa(x), &x2); - printf("%s\n", inet_ntoa(x2)); - return 0; - } - ''' - self.do_run(src, '120.86.52.18\n120.86.52.18\n') - - def test_inet3(self): - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - #include <sys/socket.h> - int main() { - char dst[64]; - struct in_addr x, x2; - int *y = (int*)&x; - *y = 0x12345678; - printf("%s\n", inet_ntop(AF_INET,&x,dst,sizeof dst)); - int r = inet_aton(inet_ntoa(x), &x2); - printf("%s\n", inet_ntop(AF_INET,&x2,dst,sizeof dst)); - return 0; - } - ''' - self.do_run(src, '120.86.52.18\n120.86.52.18\n') - - def test_inet4(self): - if Settings.USE_TYPED_ARRAYS != 2: return self.skip('requires ta2') - - src = r''' - #include <stdio.h> - #include <arpa/inet.h> - #include <sys/socket.h> - - void test(char *test_addr){ - char str[40]; - struct in6_addr addr; - unsigned char *p = (unsigned char*)&addr; - int ret; - ret = inet_pton(AF_INET6,test_addr,&addr); - if(ret == -1) return; - if(ret == 0) return; - if(inet_ntop(AF_INET6,&addr,str,sizeof(str)) == NULL ) return; - printf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x - %s\n", - p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15],str); - } - int main(){ - test("::"); - test("::1"); - test("::1.2.3.4"); - test("::17.18.19.20"); - test("::ffff:1.2.3.4"); - test("1::ffff"); - test("::255.255.255.255"); - test("0:ff00:1::"); - test("0:ff::"); - test("abcd::"); - test("ffff::a"); - test("ffff::a:b"); - test("ffff::a:b:c"); - test("ffff::a:b:c:d"); - test("ffff::a:b:c:d:e"); - test("::1:2:0:0:0"); - test("0:0:1:2:3::"); - test("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); - test("1::255.255.255.255"); - - //below should fail and not produce results.. - test("1.2.3.4"); - test(""); - test("-"); - } - ''' - self.do_run(src, - "0000:0000:0000:0000:0000:0000:0000:0000 - ::\n" - "0000:0000:0000:0000:0000:0000:0000:0001 - ::1\n" - "0000:0000:0000:0000:0000:0000:0102:0304 - ::1.2.3.4\n" - "0000:0000:0000:0000:0000:0000:1112:1314 - ::17.18.19.20\n" - "0000:0000:0000:0000:0000:ffff:0102:0304 - ::ffff:1.2.3.4\n" - "0001:0000:0000:0000:0000:0000:0000:ffff - 1::ffff\n" - "0000:0000:0000:0000:0000:0000:ffff:ffff - ::255.255.255.255\n" - "0000:ff00:0001:0000:0000:0000:0000:0000 - 0:ff00:1::\n" - "0000:00ff:0000:0000:0000:0000:0000:0000 - 0:ff::\n" - "abcd:0000:0000:0000:0000:0000:0000:0000 - abcd::\n" - "ffff:0000:0000:0000:0000:0000:0000:000a - ffff::a\n" - "ffff:0000:0000:0000:0000:0000:000a:000b - ffff::a:b\n" - "ffff:0000:0000:0000:0000:000a:000b:000c - ffff::a:b:c\n" - "ffff:0000:0000:0000:000a:000b:000c:000d - ffff::a:b:c:d\n" - "ffff:0000:0000:000a:000b:000c:000d:000e - ffff::a:b:c:d:e\n" - "0000:0000:0000:0001:0002:0000:0000:0000 - ::1:2:0:0:0\n" - "0000:0000:0001:0002:0003:0000:0000:0000 - 0:0:1:2:3::\n" - "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\n" - "0001:0000:0000:0000:0000:0000:ffff:ffff - 1::ffff:ffff\n" - ) - - def test_gethostbyname(self): - if Settings.USE_TYPED_ARRAYS != 2: return self.skip("assume t2 in gethostbyname") - - src = r''' - #include <netdb.h> - #include <stdio.h> - - void test(char *hostname) { - hostent *host = gethostbyname(hostname); - if (!host) { - printf("no such thing\n"); - return; - } - printf("%s : %d : %d\n", host->h_name, host->h_addrtype, host->h_length); - char **name = host->h_aliases; - while (*name) { - printf("- %s\n", *name); - name++; - } - name = host->h_addr_list; - while (name && *name) { - printf("* "); - for (int i = 0; i < host->h_length; i++) - printf("%d.", (*name)[i]); - printf("\n"); - name++; - } - } - - int main() { - test("www.cheezburger.com"); - test("fail.on.this.never.work"); // we will "work" on this - because we are just making aliases of names to ips - test("localhost"); - return 0; - } - ''' - self.do_run(src, '''www.cheezburger.com : 2 : 4 -* -84.29.1.0. -fail.on.this.never.work : 2 : 4 -* -84.29.2.0. -localhost : 2 : 4 -* -84.29.3.0. -''') - def test_799(self): src = open(path_from_root('tests', '799.cpp'), 'r').read() self.do_run(src, '''Set PORT family: 0, port: 3979 diff --git a/tests/test_other.py b/tests/test_other.py index a6813b07..c6f5c333 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -929,20 +929,14 @@ f.close() self.assertContained('libf1\nlibf2\n', run_js(os.path.join(self.get_dir(), 'a.out.js'))) def test_stdin(self): - open('main.cpp', 'w').write(r''' -#include <stdio.h> -int main(int argc, char const *argv[]) -{ - char str[10] = {0}; - scanf("%10s", str); - printf("%s\n", str); - return 0; -} -''') - Building.emcc('main.cpp', output_filename='a.out.js') - open('in.txt', 'w').write('abc') - # node's stdin support is broken - self.assertContained('abc', Popen(listify(SPIDERMONKEY_ENGINE) + ['a.out.js'], stdin=open('in.txt'), stdout=PIPE, stderr=PIPE).communicate()[0]) + Building.emcc(path_from_root('tests', 'module', 'test_stdin.c'), output_filename='a.out.js') + open('in.txt', 'w').write('abcdef\nghijkl') + + for engine in JS_ENGINES: + print >> sys.stderr, engine + if engine == NODE_JS: continue # FIXME + if engine == V8_ENGINE: continue # no stdin support in v8 shell + self.assertContained('abcdef\nghijkl\neof', run_js(os.path.join(self.get_dir(), 'a.out.js'), engine=engine, stdin=open('in.txt'))) def test_ungetc_fscanf(self): open('main.cpp', 'w').write(r''' @@ -1890,3 +1884,12 @@ you should see two lines of text in different colors and a blue rectangle SDL_Quit called (and ignored) done. ''' in output, output + + def test_preprocess(self): + self.clear() + + out, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-E'], stdout=PIPE).communicate() + assert not os.path.exists('a.out.js') + assert '''tests/hello_world.c"''' in out + assert '''printf("hello, world!''' in out + diff --git a/tests/test_sockets.py b/tests/test_sockets.py index 82ddc6fe..d2bc46a2 100644 --- a/tests/test_sockets.py +++ b/tests/test_sockets.py @@ -34,11 +34,11 @@ def make_relay_server(port1, port2): return proc class WebsockifyServerHarness: - def __init__(self, filename, args, listen_port, target_port): + def __init__(self, filename, args, listen_port): self.pids = [] self.filename = filename - self.target_port = target_port self.listen_port = listen_port + self.target_port = listen_port-1 self.args = args or [] def __enter__(self): @@ -48,7 +48,7 @@ class WebsockifyServerHarness: # NOTE empty filename support is a hack to support # the current test_enet if self.filename: - Popen([CLANG_CC, path_from_root('tests', self.filename), '-o', 'server'] + self.args).communicate() + Popen([CLANG_CC, path_from_root('tests', self.filename), '-o', 'server', '-DSOCKK=%d' % self.target_port] + self.args).communicate() process = Popen([os.path.abspath('server')]) self.pids.append(process.pid) @@ -71,17 +71,22 @@ class WebsockifyServerHarness: class CompiledServerHarness: - def __init__(self, filename, args): + def __init__(self, filename, args, listen_port): self.pids = [] self.filename = filename + self.listen_port = listen_port self.args = args or [] def __enter__(self): - import socket, websockify + # assuming this is only used for WebSocket tests at the moment, validate that + # the ws module is installed + child = Popen(listify(NODE_JS) + ['-e', 'require("ws");']) + child.communicate() + assert child.returncode == 0, 'ws module for Node.js not installed. Please run \'npm install\' from %s' % EMSCRIPTEN_ROOT # compile the server - Popen([PYTHON, EMCC, path_from_root('tests', self.filename), '-o', 'server.js'] + self.args).communicate() - process = Popen([NODE_JS, 'server.js']) + Popen([PYTHON, EMCC, path_from_root('tests', self.filename), '-o', 'server.js', '-DSOCKK=%d' % self.listen_port] + self.args).communicate() + process = Popen(listify(NODE_JS) + ['server.js']) self.pids.append(process.pid) def __exit__(self, *args, **kwargs): @@ -96,72 +101,196 @@ class CompiledServerHarness: # proper listen server support. class sockets(BrowserCore): + def test_inet(self): + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + + int main() { + printf("*%x,%x,%x,%x,%x,%x*\n", htonl(0xa1b2c3d4), htonl(0xfe3572e0), htonl(0x07abcdf0), htons(0xabcd), ntohl(0x43211234), ntohs(0xbeaf)); + in_addr_t i = inet_addr("190.180.10.78"); + printf("%x\n", i); + return 0; + } + ''' + self.do_run(src, '*d4c3b2a1,e07235fe,f0cdab07,cdab,34122143,afbe*\n4e0ab4be\n') + + def test_inet2(self): + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + + int main() { + struct in_addr x, x2; + int *y = (int*)&x; + *y = 0x12345678; + printf("%s\n", inet_ntoa(x)); + int r = inet_aton(inet_ntoa(x), &x2); + printf("%s\n", inet_ntoa(x2)); + return 0; + } + ''' + self.do_run(src, '120.86.52.18\n120.86.52.18\n') + + def test_inet3(self): + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + #include <sys/socket.h> + int main() { + char dst[64]; + struct in_addr x, x2; + int *y = (int*)&x; + *y = 0x12345678; + printf("%s\n", inet_ntop(AF_INET,&x,dst,sizeof dst)); + int r = inet_aton(inet_ntoa(x), &x2); + printf("%s\n", inet_ntop(AF_INET,&x2,dst,sizeof dst)); + return 0; + } + ''' + self.do_run(src, '120.86.52.18\n120.86.52.18\n') + + def test_inet4(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('requires ta2') + + src = r''' + #include <stdio.h> + #include <arpa/inet.h> + #include <sys/socket.h> + + void test(char *test_addr){ + char str[40]; + struct in6_addr addr; + unsigned char *p = (unsigned char*)&addr; + int ret; + ret = inet_pton(AF_INET6,test_addr,&addr); + if(ret == -1) return; + if(ret == 0) return; + if(inet_ntop(AF_INET6,&addr,str,sizeof(str)) == NULL ) return; + printf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x - %s\n", + p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15],str); + } + int main(){ + test("::"); + test("::1"); + test("::1.2.3.4"); + test("::17.18.19.20"); + test("::ffff:1.2.3.4"); + test("1::ffff"); + test("::255.255.255.255"); + test("0:ff00:1::"); + test("0:ff::"); + test("abcd::"); + test("ffff::a"); + test("ffff::a:b"); + test("ffff::a:b:c"); + test("ffff::a:b:c:d"); + test("ffff::a:b:c:d:e"); + test("::1:2:0:0:0"); + test("0:0:1:2:3::"); + test("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + test("1::255.255.255.255"); + + //below should fail and not produce results.. + test("1.2.3.4"); + test(""); + test("-"); + } + ''' + self.do_run(src, + "0000:0000:0000:0000:0000:0000:0000:0000 - ::\n" + "0000:0000:0000:0000:0000:0000:0000:0001 - ::1\n" + "0000:0000:0000:0000:0000:0000:0102:0304 - ::1.2.3.4\n" + "0000:0000:0000:0000:0000:0000:1112:1314 - ::17.18.19.20\n" + "0000:0000:0000:0000:0000:ffff:0102:0304 - ::ffff:1.2.3.4\n" + "0001:0000:0000:0000:0000:0000:0000:ffff - 1::ffff\n" + "0000:0000:0000:0000:0000:0000:ffff:ffff - ::255.255.255.255\n" + "0000:ff00:0001:0000:0000:0000:0000:0000 - 0:ff00:1::\n" + "0000:00ff:0000:0000:0000:0000:0000:0000 - 0:ff::\n" + "abcd:0000:0000:0000:0000:0000:0000:0000 - abcd::\n" + "ffff:0000:0000:0000:0000:0000:0000:000a - ffff::a\n" + "ffff:0000:0000:0000:0000:0000:000a:000b - ffff::a:b\n" + "ffff:0000:0000:0000:0000:000a:000b:000c - ffff::a:b:c\n" + "ffff:0000:0000:0000:000a:000b:000c:000d - ffff::a:b:c:d\n" + "ffff:0000:0000:000a:000b:000c:000d:000e - ffff::a:b:c:d:e\n" + "0000:0000:0000:0001:0002:0000:0000:0000 - ::1:2:0:0:0\n" + "0000:0000:0001:0002:0003:0000:0000:0000 - 0:0:1:2:3::\n" + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\n" + "0001:0000:0000:0000:0000:0000:ffff:ffff - 1::ffff:ffff\n" + ) + + def test_getaddrinfo(self): + self.do_run(open(path_from_root('tests', 'sockets', 'test_getaddrinfo.c')).read(), 'success') + + def test_getnameinfo(self): + self.do_run(open(path_from_root('tests', 'sockets', 'test_getnameinfo.c')).read(), 'success') + + def test_gethostbyname(self): + self.do_run(open(path_from_root('tests', 'sockets', 'test_gethostbyname.c')).read(), 'success') + def test_sockets_echo(self): sockets_include = '-I'+path_from_root('tests', 'sockets') - for datagram in [0]: - dgram_define = '-DTEST_DGRAM=%d' % datagram + # Websockify-proxied servers can't run dgram tests + harnesses = [ + (WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 49160), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=0'], 49161), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=1'], 49162), 1) + ] - for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=8990', dgram_define, sockets_include], 8991, 8990) - # CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=8990', dgram_define, sockets_include]) - ]: - with harness: - self.btest(os.path.join('sockets', 'test_sockets_echo_client.c'), expected='0', args=['-DSOCKK=8991', dgram_define, sockets_include]) + for harness, datagram in harnesses: + with harness: + self.btest(os.path.join('sockets', 'test_sockets_echo_client.c'), expected='0', args=['-DSOCKK=%d' % harness.listen_port, '-DTEST_DGRAM=%d' % datagram, sockets_include]) def test_sockets_echo_bigdata(self): sockets_include = '-I'+path_from_root('tests', 'sockets') - for datagram in [0]: - dgram_define = '-DTEST_DGRAM=%d' % datagram + # generate a large string literal to use as our message + message = '' + for i in range(256*256*2): + message += str(unichr(ord('a') + (i % 26))) - # generate a large string literal to use as our message - message = '' - for i in range(256*256*2): - message += str(unichr(ord('a') + (i % 26))) + # re-write the client test with this literal (it's too big to pass via command line) + input_filename = path_from_root('tests', 'sockets', 'test_sockets_echo_client.c') + input = open(input_filename).read() + output = input.replace('#define MESSAGE "pingtothepong"', '#define MESSAGE "%s"' % message) - # re-write the client test with this literal (it's too big to pass via command line) - input_filename = path_from_root('tests', 'sockets', 'test_sockets_echo_client.c') - input = open(input_filename).read() - output = input.replace('#define MESSAGE "pingtothepong"', '#define MESSAGE "%s"' % message) + harnesses = [ + (WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 49170), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=0'], 49171), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=1'], 49172), 1) + ] - for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=8992', dgram_define, sockets_include], 8993, 8992) - ]: - with harness: - self.btest(output, expected='0', args=['-DSOCKK=8993', dgram_define, sockets_include], force_c=True) + for harness, datagram in harnesses: + with harness: + self.btest(output, expected='0', args=[sockets_include, '-DSOCKK=%d' % harness.listen_port, '-DTEST_DGRAM=%d' % datagram], force_c=True) def test_sockets_partial(self): for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_partial_server.c'), ['-DSOCKK=8994'], 8995, 8994) + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_partial_server.c'), [], 49180), + CompiledServerHarness(os.path.join('sockets', 'test_sockets_partial_server.c'), [], 49181) ]: with harness: - self.btest(os.path.join('sockets', 'test_sockets_partial_client.c'), expected='165', args=['-DSOCKK=8995']) - - # TODO add support for gethostbyaddr to re-enable this test - # def test_sockets_gethostbyname(self): - # self.btest(os.path.join('sockets', 'test_sockets_gethostbyname.c'), expected='0', args=['-O2', '-DSOCKK=8997']) + self.btest(os.path.join('sockets', 'test_sockets_partial_client.c'), expected='165', args=['-DSOCKK=%d' % harness.listen_port]) def test_sockets_select_server_down(self): for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_select_server_down_server.c'), ['-DSOCKK=9002'], 9003, 9002) + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_select_server_down_server.c'), [], 49190), + CompiledServerHarness(os.path.join('sockets', 'test_sockets_select_server_down_server.c'), [], 49191) ]: with harness: - self.btest(os.path.join('sockets', 'test_sockets_select_server_down_client.c'), expected='266', args=['-DSOCKK=9003']) + self.btest(os.path.join('sockets', 'test_sockets_select_server_down_client.c'), expected='266', args=['-DSOCKK=%d' % harness.listen_port]) def test_sockets_select_server_closes_connection_rw(self): sockets_include = '-I'+path_from_root('tests', 'sockets') for harness in [ - WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), ['-DSOCKK=9004', sockets_include], 9005, 9004) + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DCLOSE_CLIENT_AFTER_ECHO'], 49200), + CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DCLOSE_CLIENT_AFTER_ECHO'], 49201) ]: with harness: - self.btest(os.path.join('sockets', 'test_sockets_select_server_closes_connection_client_rw.c'), expected='266', args=['-DSOCKK=9005', sockets_include]) + self.btest(os.path.join('sockets', 'test_sockets_select_server_closes_connection_client_rw.c'), expected='266', args=[sockets_include, '-DSOCKK=%d' % harness.listen_port]) - # TODO remove this once we have proper listen server support built into emscripten. - # being that enet uses datagram sockets, we can't proxy to a native server with - # websockify, so we're emulating the listen server in the browser and relaying - # between two TCP servers. def test_enet(self): try_delete(self.in_dir('enet')) shutil.copytree(path_from_root('tests', 'enet'), self.in_dir('enet')) @@ -171,20 +300,18 @@ class sockets(BrowserCore): Popen([PYTHON, path_from_root('emmake'), 'make']).communicate() enet = [self.in_dir('enet', '.libs', 'libenet.a'), '-I'+path_from_root('tests', 'enet', 'include')] os.chdir(pwd) - Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_enet_server.c'), '-o', 'server.html', '-DSOCKK=2235'] + enet).communicate() - - with WebsockifyServerHarness('', [], 2235, 2234): - with WebsockifyServerHarness('', [], 2237, 2236): - pids = [] - try: - proc = make_relay_server(2234, 2236) - pids.append(proc.pid) - self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=['-DSOCKK=2237', '-DUSE_IFRAME'] + enet) - finally: - clean_pids(pids); - - # TODO use this once we have listen server support - # def test_enet(self): + + for harness in [ + CompiledServerHarness(os.path.join('sockets', 'test_enet_server.c'), enet, 49210) + ]: + with harness: + self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=enet + ['-DSOCKK=%d' % harness.listen_port]) + + # This test is no longer in use for WebSockets as we can't truly emulate + # a server in the browser (in the past, there were some hacks to make it + # somewhat work, but those have been removed). However, with WebRTC it + # should be able to resurect this test. + # def test_enet_in_browser(self): # try_delete(self.in_dir('enet')) # shutil.copytree(path_from_root('tests', 'enet'), self.in_dir('enet')) # pwd = os.getcwd() @@ -193,12 +320,17 @@ class sockets(BrowserCore): # Popen([PYTHON, path_from_root('emmake'), 'make']).communicate() # enet = [self.in_dir('enet', '.libs', 'libenet.a'), '-I'+path_from_root('tests', 'enet', 'include')] # os.chdir(pwd) - - # for harness in [ - # self.CompiledServerHarness(os.path.join('sockets', 'test_enet_server.c'), ['-DSOCKK=9010'] + enet, 9011, 9010) - # ]: - # with harness: - # self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=['-DSOCKK=9011'] + enet) + # Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_enet_server.c'), '-o', 'server.html', '-DSOCKK=2235'] + enet).communicate() + + # with WebsockifyServerHarness('', [], 2235, 2234): + # with WebsockifyServerHarness('', [], 2237, 2236): + # pids = [] + # try: + # proc = make_relay_server(2234, 2236) + # pids.append(proc.pid) + # self.btest(os.path.join('sockets', 'test_enet_client.c'), expected='0', args=['-DSOCKK=2237', '-DUSE_IFRAME=1'] + enet) + # finally: + # clean_pids(pids); def test_webrtc(self): host_src = 'webrtc_host.c' @@ -266,4 +398,5 @@ class sockets(BrowserCore): Popen([PYTHON, EMCC, temp_peer_filepath, '-o', peer_outfile] + ['-s', 'GL_TESTING=1', '--pre-js', 'peer_pre.js', '-s', 'SOCKET_WEBRTC=1', '-s', 'SOCKET_DEBUG=1']).communicate() expected = '1' - self.run_browser(host_outfile, '.', ['/report_result?' + e for e in expected])
\ No newline at end of file + self.run_browser(host_outfile, '.', ['/report_result?' + e for e in expected]) + diff --git a/tools/jsrun.py b/tools/jsrun.py index 571e9cee..6f77ce51 100644 --- a/tools/jsrun.py +++ b/tools/jsrun.py @@ -10,15 +10,17 @@ def timeout_run(proc, timeout, note='unnamed process', full_output=False): proc.kill() # XXX bug: killing emscripten.py does not kill it's child process! raise Exception("Timed out: " + note) out = proc.communicate() + out = map(lambda o: '' if o is None else o, out) return '\n'.join(out) if full_output else out[0] -def run_js(filename, engine=None, args=[], check_timeout=False, stdout=PIPE, stderr=None, cwd=None, full_output=False): +def run_js(filename, engine=None, args=[], check_timeout=False, stdin=None, stdout=PIPE, stderr=None, cwd=None, full_output=False): if type(engine) is not list: engine = [engine] command = engine + [filename] + (['--'] if 'd8' in engine[0] or 'jsc' in engine[0] else []) + args return timeout_run( Popen( command, + stdin=stdin, stdout=stdout, stderr=stderr, cwd=cwd), diff --git a/tools/shared.py b/tools/shared.py index 3ee5db23..b0b3985e 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -717,6 +717,10 @@ class Settings2(type): return ret @classmethod + def copy(self, values): + self.attrs = values + + @classmethod def apply_opt_level(self, opt_level, noisy=False): if opt_level >= 1: self.attrs['ASM_JS'] = 1 @@ -843,6 +847,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e raise del env['EMMAKEN_JUST_CONFIGURE'] if process.returncode is not 0: + logging.error('Configure step failed with non-zero return code ' + str(process.returncode) + '! Command line: ' + str(args)) raise subprocess.CalledProcessError(cmd=args, returncode=process.returncode) @staticmethod @@ -893,12 +898,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e # except: # pass env = Building.get_building_env(native) + log_to_file = os.getenv('EM_BUILD_VERBOSE') == None or int(os.getenv('EM_BUILD_VERBOSE')) == 0 for k, v in env_init.iteritems(): env[k] = v if configure: # Useful in debugging sometimes to comment this out (and the lines below up to and including the |link| call) try: - Building.configure(configure + configure_args, stdout=open(os.path.join(project_dir, 'configure_'), 'w'), - stderr=open(os.path.join(project_dir, 'configure_err'), 'w'), env=env) + Building.configure(configure + configure_args, env=env, stdout=open(os.path.join(project_dir, 'configure_'), 'w') if log_to_file else None, + stderr=open(os.path.join(project_dir, 'configure_err'), 'w') if log_to_file else None) except subprocess.CalledProcessError, e: pass # Ignore exit code != 0 def open_make_out(i, mode='r'): @@ -911,8 +917,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e with open_make_out(i, 'w') as make_out: with open_make_err(i, 'w') as make_err: try: - Building.make(make + make_args, stdout=make_out, - stderr=make_err, env=env) + Building.make(make + make_args, stdout=make_out if log_to_file else None, + stderr=make_err if log_to_file else None, env=env) except subprocess.CalledProcessError, e: pass # Ignore exit code != 0 try: @@ -924,10 +930,11 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e break except Exception, e: if i > 0: - # Due to the ugly hack above our best guess is to output the first run - with open_make_err(0) as ferr: - for line in ferr: - sys.stderr.write(line) + if log_to_file: + # Due to the ugly hack above our best guess is to output the first run + with open_make_err(0) as ferr: + for line in ferr: + sys.stderr.write(line) raise Exception('could not build library ' + name + ' due to exception ' + str(e)) if old_dir: os.chdir(old_dir) @@ -1176,8 +1183,6 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e def get_safe_internalize(): if not Building.can_build_standalone(): return [] # do not internalize anything exps = expand_response(Settings.EXPORTED_FUNCTIONS) - if '_malloc' not in exps: exps.append('_malloc') # needed internally, even if user did not add to EXPORTED_FUNCTIONS - if '_free' not in exps: exps.append('_free') exports = ','.join(map(lambda exp: exp[1:], exps)) # internalize carefully, llvm 3.2 will remove even main if not told not to return ['-internalize', '-internalize-public-api-list=' + exports] @@ -1426,7 +1431,7 @@ JCache = cache.JCache(Cache) chunkify = cache.chunkify class JS: - memory_initializer_pattern = '/\* memory initializer \*/ allocate\(([\d,\.concat\(\)\[\]\\n ]+)"i8", ALLOC_NONE, ([\dRuntime\.GLOBAL_BASE+]+)\)' + memory_initializer_pattern = '/\* memory initializer \*/ allocate\(([\d,\.concat\(\)\[\]\\n ]+)"i8", ALLOC_NONE, ([\dRuntime\.GLOBAL_BASEH+]+)\)' no_memory_initializer_pattern = '/\* no memory initializer \*/' memory_staticbump_pattern = 'STATICTOP = STATIC_BASE \+ (\d+);' @@ -1438,11 +1443,32 @@ class JS: return ident.replace('%', '$').replace('@', '_') @staticmethod + def make_extcall(sig, named=True): + args = ','.join(['a' + str(i) for i in range(1, len(sig))]) + args = 'index' + (',' if args else '') + args + # C++ exceptions are numbers, and longjmp is a string 'longjmp' + ret = '''function%s(%s) { + %sModule["dynCall_%s"](%s); +}''' % ((' extCall_' + sig) if named else '', args, 'return ' if sig[0] != 'v' else '', sig, args) + + if Settings.DLOPEN_SUPPORT and Settings.ASSERTIONS: + # guard against cross-module stack leaks + ret = ret.replace(') {\n', ''') { + try { + var preStack = asm.stackSave(); +''').replace(';\n}', '''; + } finally { + assert(asm.stackSave() == preStack); + } +}''') + return ret + + @staticmethod def make_invoke(sig, named=True): args = ','.join(['a' + str(i) for i in range(1, len(sig))]) args = 'index' + (',' if args else '') + args # C++ exceptions are numbers, and longjmp is a string 'longjmp' - return '''function%s(%s) { + ret = '''function%s(%s) { try { %sModule["dynCall_%s"](%s); } catch(e) { @@ -1451,6 +1477,17 @@ class JS: } }''' % ((' invoke_' + sig) if named else '', args, 'return ' if sig[0] != 'v' else '', sig, args) + if Settings.DLOPEN_SUPPORT and Settings.ASSERTIONS: + # guard against cross-module stack leaks + ret = ret.replace(' try {', ''' var preStack = asm.stackSave(); + try { +''').replace(' }\n}', ''' } finally { + assert(asm.stackSave() == preStack); + } +}''') + + return ret + @staticmethod def align(x, by): while x % by != 0: x += 1 |