blob: 8fea858e38fdddf78f04114c7bd4fc4366297643 [file] [log] [blame]
# Copyright 2013 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
from __future__ import print_function
import glob
import hashlib
import json
import os
import random
import re
import shutil
import sys
import time
import unittest
from textwrap import dedent
if __name__ == '__main__':
raise Exception('do not run this file directly; do something like: tests/runner.py')
from tools.shared import Building, STDOUT, PIPE, run_js, run_process, try_delete
from tools.shared import NODE_JS, V8_ENGINE, JS_ENGINES, SPIDERMONKEY_ENGINE, PYTHON, EMCC, EMAR, WINDOWS, AUTODEBUGGER
from tools import jsrun, shared
from runner import RunnerCore, path_from_root, core_test_modes, get_bullet_library, get_freetype_library, get_poppler_library
from runner import skip_if, no_wasm_backend, needs_dlfcn, no_windows, env_modify, with_env_modify, is_slow_test, create_test_file
# decorators for limiting which modes a test can run in
def asm_simd(f):
def decorated(self):
if self.is_emterpreter():
self.skipTest('simd not supported in emterpreter yet')
if self.is_wasm():
self.skipTest('asm.js simd not compatible with wasm yet')
self.use_all_engines = True # checks both native in spidermonkey and polyfill in others
f(self)
return decorated
def wasm_simd(f):
def decorated(self):
if self.is_emterpreter():
self.skipTest('simd not supported in empterpreter yet')
if not self.is_wasm_backend():
self.skipTest('wasm simd not compatible with asm.js or asm2wasm')
if not V8_ENGINE or V8_ENGINE not in JS_ENGINES:
self.skipTest('wasm simd only supported in d8 for now')
self.set_setting('SIMD', 1)
self.emcc_args.append('-fno-lax-vector-conversions')
f(self, js_engines=[V8_ENGINE + ['--experimental-wasm-simd']])
return decorated
def no_emterpreter(f):
return skip_if(f, 'is_emterpreter')
def no_wasm(note=''):
def decorated(f):
return skip_if(f, 'is_wasm', note)
return decorated
# Async wasm compilation can't work in some tests, they are set up synchronously
def sync(f):
def decorated(self):
if self.is_wasm():
self.emcc_args += ['-s', 'BINARYEN_ASYNC_COMPILATION=0'] # test is set up synchronously
f(self)
return decorated
def also_with_noderawfs(func):
def decorated(self):
orig_args = self.emcc_args[:]
func(self)
self.emcc_args = orig_args + ['-s', 'NODERAWFS=1', '-DNODERAWFS']
func(self, js_engines=[NODE_JS])
return decorated
# A simple check whether the compiler arguments cause optimization.
def is_optimizing(args):
return '-O' in str(args) and '-O0' not in args
class TestCoreBase(RunnerCore):
# whether the test mode supports duplicate function elimination in js
def supports_js_dfe(self):
# wasm does this when optimizing anyhow
if self.is_wasm():
return False
supported_opt_levels = ['-O2', '-O3', '-Oz', '-Os']
for opt_level in supported_opt_levels:
if opt_level in self.emcc_args:
return True
return False
# Use closure in some tests for some additional coverage
def maybe_closure(self):
if '-O2' in self.emcc_args or '-Os' in self.emcc_args:
self.emcc_args += ['--closure', '1']
return True
return False
def do_run_in_out_file_test(self, *path, **kwargs):
test_path = path_from_root(*path)
def find_files(*ext_list):
ret = None
count = 0
for ext in ext_list:
if os.path.isfile(test_path + ext):
ret = test_path + ext
count += 1
assert count > 0, ("No file found at {} with extension {}"
.format(test_path, ext_list))
assert count <= 1, ("Test file {} found with multiple valid extensions {}"
.format(test_path, ext_list))
return ret
src = find_files('.c', '.cpp', '.cc')
output = find_files('.out', '.txt')
self.do_run_from_file(src, output, **kwargs)
def test_hello_world(self):
self.do_run_in_out_file_test('tests', 'core', 'test_hello_world')
src = open('src.cpp.o.js').read()
assert 'EMSCRIPTEN_GENERATED_FUNCTIONS' not in src, 'must not emit this unneeded internal thing'
def test_intvars(self):
self.do_run_in_out_file_test('tests', 'core', 'test_intvars')
def test_sintvars(self):
self.do_run_in_out_file_test('tests', 'core', 'test_sintvars',
force_c=True)
def test_i64(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64')
def test_i64_2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_2')
def test_i64_3(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_3')
def test_i64_4(self):
# stuff that also needs sign corrections
self.do_run_in_out_file_test('tests', 'core', 'test_i64_4')
def test_i64_b(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_b')
def test_i64_cmp(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_cmp')
def test_i64_cmp2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_cmp2')
def test_i64_double(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_double')
def test_i64_umul(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_umul')
def test_i64_precise(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_precise')
def test_i64_precise_needed(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_precise_needed')
def test_i64_llabs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_llabs')
def test_i64_zextneg(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_zextneg')
def test_i64_7z(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_i64_7z',
args=['hallo'])
def test_i64_i16(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_i16')
def test_i64_qdouble(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_qdouble')
def test_i64_varargs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i64_varargs',
args='waka fleefl asdfasdfasdfasdf'
.split(' '))
def test_vararg_copy(self):
self.do_run_in_out_file_test('tests', 'va_arg', 'test_va_copy')
def test_llvm_fabs(self):
self.set_setting('PRECISE_F32', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_llvm_fabs')
def test_double_varargs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_double_varargs')
def test_struct_varargs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_struct_varargs')
def zzztest_nested_struct_varargs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_nested_struct_varargs')
def test_i32_mul_precise(self):
self.do_run_in_out_file_test('tests', 'core', 'test_i32_mul_precise')
def test_i16_emcc_intrinsic(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_i16_emcc_intrinsic')
def test_double_i64_conversion(self):
self.do_run_in_out_file_test('tests', 'core', 'test_double_i64_conversion')
def test_float32_precise(self):
self.set_setting('PRECISE_F32', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_float32_precise')
def test_negative_zero(self):
self.do_run_in_out_file_test('tests', 'core', 'test_negative_zero')
def test_line_endings(self):
self.build(open(path_from_root('tests', 'hello_world.cpp')).read(), self.get_dir(), 'hello_world.cpp')
def test_literal_negative_zero(self):
self.do_run_in_out_file_test('tests', 'core', 'test_literal_negative_zero')
@no_wasm_backend('test uses calls to expected js imports, rather than using llvm intrinsics directly')
def test_llvm_intrinsics(self):
self.do_run_in_out_file_test('tests', 'core', 'test_llvm_intrinsics')
@no_wasm_backend('test looks for js impls of intrinsics')
def test_lower_intrinsics(self):
self.emcc_args += ['-g1']
self.do_run_in_out_file_test('tests', 'core', 'test_lower_intrinsics')
# intrinsics should be lowered out
js = open('src.cpp.o.js').read()
assert ('llvm_' not in js) == is_optimizing(self.emcc_args) or not self.is_wasm(), 'intrinsics must be lowered when optimizing'
def test_bswap64(self):
test_path = path_from_root('tests', 'core', 'test_bswap64')
src, output = (test_path + s for s in ('.c', '.out'))
# extra coverages
for emulate_casts in [0, 1]:
for emulate_fps in [0, 1, 2]:
print(emulate_casts, emulate_fps)
self.set_setting('EMULATE_FUNCTION_POINTER_CASTS', emulate_casts)
self.set_setting('EMULATED_FUNCTION_POINTERS', emulate_fps)
self.do_run_from_file(src, output)
def test_sha1(self):
self.do_run(open(path_from_root('tests', 'sha1.c')).read(), 'SHA1=15dd99a1991e0b3826fede3deffc1feba42278e6')
@no_wasm_backend('test checks that __asmjs__ is #defined')
def test_asmjs_unknown_emscripten(self):
# No other configuration is supported, so always run this.
self.do_run(open(path_from_root('tests', 'asmjs-unknown-emscripten.c')).read(), '')
def test_cube2md5(self):
self.emcc_args += ['--embed-file', 'cube2md5.txt']
shutil.copyfile(path_from_root('tests', 'cube2md5.txt'), 'cube2md5.txt')
self.do_run(open(path_from_root('tests', 'cube2md5.cpp')).read(), open(path_from_root('tests', 'cube2md5.ok')).read())
def test_cube2hash(self):
# A good test of i64 math
self.do_run('', 'Usage: hashstring <seed>',
libraries=self.get_library('cube2hash', ['cube2hash.bc'], configure=None),
includes=[path_from_root('tests', 'cube2hash')])
for text, output in [('fleefl', '892BDB6FD3F62E863D63DA55851700FDE3ACF30204798CE9'),
('fleefl2', 'AA2CC5F96FC9D540CA24FDAF1F71E2942753DB83E8A81B61'),
('64bitisslow', '64D8470573635EC354FEE7B7F87C566FCAF1EFB491041670')]:
self.do_run('', 'hash value: ' + output, [text], no_build=True)
def test_unaligned(self):
self.skipTest('LLVM marks the reads of s as fully aligned, making this test invalid')
src = r'''
#include <stdio.h>
struct S {
double x;
int y;
};
int main() {
// the 64-bit value here will not be 8-byte aligned
S s0[3] = { {0x12a751f430142, 22}, {0x17a5c85bad144, 98}, {1, 1}};
char buffer[10*sizeof(S)];
int b = int(buffer);
S *s = (S*)(b + 4-b%8);
s[0] = s0[0];
s[1] = s0[1];
s[2] = s0[2];
printf("*%d : %d : %d\n", sizeof(S), ((unsigned int)&s[0]) % 8 != ((unsigned int)&s[1]) % 8,
((unsigned int)&s[1]) - ((unsigned int)&s[0]));
s[0].x++;
s[0].y++;
s[1].x++;
s[1].y++;
printf("%.1f,%d,%.1f,%d\n", s[0].x, s[0].y, s[1].x, s[1].y);
return 0;
}
'''
# TODO: A version of this with int64s as well
self.do_run(src, '*12 : 1 : 12\n328157500735811.0,23,416012775903557.0,99\n')
return # TODO: continue to the next part here
# Test for undefined behavior in C. This is not legitimate code, but does exist
src = r'''
#include <stdio.h>
int main()
{
int x[10];
char *p = (char*)&x[0];
p++;
short *q = (short*)p;
*q = 300;
printf("*%d:%d*\n", *q, ((int)q)%2);
int *r = (int*)p;
*r = 515559;
printf("*%d*\n", *r);
long long *t = (long long*)p;
*t = 42949672960;
printf("*%lld*\n", *t);
return 0;
}
'''
try:
self.do_run(src, '*300:1*\n*515559*\n*42949672960*\n')
except Exception as e:
assert 'must be aligned' in str(e), e # expected to fail without emulation
def test_align64(self):
src = r'''
#include <stdio.h>
// inspired by poppler
enum Type {
A = 10,
B = 20
};
struct Object {
Type type;
union {
int intg;
double real;
char *name;
};
};
struct Principal {
double x;
Object a;
double y;
};
int main(int argc, char **argv)
{
int base = argc-1;
Object *o = NULL;
printf("%d,%d\n", sizeof(Object), sizeof(Principal));
printf("%d,%d,%d,%d\n", (int)&o[base].type, (int)&o[base].intg, (int)&o[base].real, (int)&o[base].name);
printf("%d,%d,%d,%d\n", (int)&o[base+1].type, (int)&o[base+1].intg, (int)&o[base+1].real, (int)&o[base+1].name);
Principal p, q;
p.x = p.y = q.x = q.y = 0;
p.a.type = A;
p.a.real = 123.456;
*(&q.a) = p.a;
printf("%.2f,%d,%.2f,%.2f : %.2f,%d,%.2f,%.2f\n", p.x, p.a.type, p.a.real, p.y, q.x, q.a.type, q.a.real, q.y);
return 0;
}
'''
self.do_run(src, '''16,32
0,8,8,8
16,24,24,24
0.00,10,123.46,0.00 : 0.00,10,123.46,0.00
''')
def test_aligned_alloc(self):
self.do_run(open(path_from_root('tests', 'test_aligned_alloc.c')).read(), '', assert_returncode=0)
def test_unsigned(self):
src = '''
#include <stdio.h>
const signed char cvals[2] = { -1, -2 }; // compiler can store this is a string, so -1 becomes \\FF, and needs re-signing
int main()
{
{
unsigned char x = 200;
printf("*%d*\\n", x);
unsigned char y = -22;
printf("*%d*\\n", y);
}
int varey = 100;
unsigned int MAXEY = -1, MAXEY2 = -77;
printf("*%u,%d,%u*\\n", MAXEY, varey >= MAXEY, MAXEY2); // 100 >= -1? not in unsigned!
int y = cvals[0];
printf("*%d,%d,%d,%d*\\n", cvals[0], cvals[0] < 0, y, y < 0);
y = cvals[1];
printf("*%d,%d,%d,%d*\\n", cvals[1], cvals[1] < 0, y, y < 0);
// zext issue - see mathop in jsifier
unsigned char x8 = -10;
unsigned long hold = 0;
hold += x8;
int y32 = hold+50;
printf("*%u,%u*\\n", hold, y32);
// Comparisons
x8 = 0;
for (int i = 0; i < 254; i++) x8++; // make it an actual 254 in JS - not a -2
printf("*%d,%d*\\n", x8+1 == 0xff, x8+1 != 0xff); // 0xff may be '-1' in the bitcode
return 0;
}
'''
self.do_run(src, '*4294967295,0,4294967219*\n*-1,1,-1,1*\n*-2,1,-2,1*\n*246,296*\n*1,0*')
self.emcc_args.append('-Wno-constant-conversion')
src = '''
#include <stdio.h>
int main()
{
{
unsigned char x;
unsigned char *y = &x;
*y = -1;
printf("*%d*\\n", x);
}
{
unsigned short x;
unsigned short *y = &x;
*y = -1;
printf("*%d*\\n", x);
}
/*{ // This case is not checked. The hint for unsignedness is just the %u in printf, and we do not analyze that
unsigned int x;
unsigned int *y = &x;
*y = -1;
printf("*%u*\\n", x);
}*/
{
char x;
char *y = &x;
*y = 255;
printf("*%d*\\n", x);
}
{
char x;
char *y = &x;
*y = 65535;
printf("*%d*\\n", x);
}
{
char x;
char *y = &x;
*y = 0xffffffff;
printf("*%d*\\n", x);
}
return 0;
}
'''
self.do_run(src, '*255*\n*65535*\n*-1*\n*-1*\n*-1*')
def test_bitfields(self):
self.do_run_in_out_file_test('tests', 'core', 'test_bitfields')
def test_floatvars(self):
self.do_run_in_out_file_test('tests', 'core', 'test_floatvars')
def test_closebitcasts(self):
self.do_run_in_out_file_test('tests', 'core', 'closebitcasts')
def test_fast_math(self):
Building.COMPILER_TEST_OPTS += ['-ffast-math']
self.do_run_in_out_file_test('tests', 'core', 'test_fast_math',
args=['5', '6', '8'])
def test_zerodiv(self):
self.do_run_in_out_file_test('tests', 'core', 'test_zerodiv')
def test_zero_multiplication(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_zero_multiplication')
def test_isnan(self):
self.do_run_in_out_file_test('tests', 'core', 'test_isnan')
def test_globaldoubles(self):
self.do_run_in_out_file_test('tests', 'core', 'test_globaldoubles')
def test_math(self):
self.do_run_in_out_file_test('tests', 'core', 'test_math')
def test_erf(self):
self.do_run_in_out_file_test('tests', 'core', 'test_erf')
def test_math_hyperbolic(self):
src = open(path_from_root('tests', 'hyperbolic', 'src.c')).read()
expected = open(path_from_root('tests', 'hyperbolic', 'output.txt')).read()
self.do_run(src, expected)
def test_math_lgamma(self):
test_path = path_from_root('tests', 'math', 'lgamma')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output)
if self.get_setting('ALLOW_MEMORY_GROWTH') == 0 and not self.is_wasm():
print('main module')
self.set_setting('MAIN_MODULE', 1)
self.do_run_from_file(src, output)
# Test that fmodf with -s PRECISE_F32=1 properly validates as asm.js (% operator cannot take in f32, only f64)
def test_math_fmodf(self):
test_path = path_from_root('tests', 'math', 'fmodf')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output)
def test_frexp(self):
self.do_run_in_out_file_test('tests', 'core', 'test_frexp')
def test_rounding(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
for precise_f32 in [0, 1]:
print(precise_f32)
self.set_setting('PRECISE_F32', precise_f32)
self.do_run_in_out_file_test('tests', 'core', 'test_rounding')
def test_fcvt(self):
self.do_run_in_out_file_test('tests', 'core', 'test_fcvt')
def test_llrint(self):
self.do_run_in_out_file_test('tests', 'core', 'test_llrint')
def test_getgep(self):
# Generated code includes getelementptr (getelementptr, 0, 1), i.e., GEP as the first param to GEP
self.do_run_in_out_file_test('tests', 'core', 'test_getgep')
def test_multiply_defined_symbols(self):
create_test_file('a1.c', 'int f() { return 1; }')
create_test_file('a2.c', 'void x() {}')
create_test_file('b1.c', 'int f() { return 2; }')
create_test_file('b2.c', 'void y() {}')
create_test_file('main.c', r'''
#include <stdio.h>
int f();
int main() {
printf("result: %d\n", f());
return 0;
}
''')
Building.emcc('a1.c')
Building.emcc('a2.c')
Building.emcc('b1.c')
Building.emcc('b2.c')
Building.emcc('main.c')
Building.emar('cr', 'liba.a', ['a1.c.o', 'a2.c.o'])
Building.emar('cr', 'libb.a', ['b1.c.o', 'b2.c.o'])
Building.link_to_object(['main.c.o', 'liba.a', 'libb.a'], 'all.o')
self.do_ll_run('all.o', 'result: 1')
def test_if(self):
self.do_run_in_out_file_test('tests', 'core', 'test_if')
def test_if_else(self):
self.do_run_in_out_file_test('tests', 'core', 'test_if_else')
def test_loop(self):
self.do_run_in_out_file_test('tests', 'core', 'test_loop')
def test_stack(self):
self.set_setting('INLINING_LIMIT', 50)
self.do_run_in_out_file_test('tests', 'core', 'test_stack')
def test_stack_align(self):
src = path_from_root('tests', 'core', 'test_stack_align.cpp')
def test():
self.do_run(open(src).read(), ['''align 4: 0
align 8: 0
align 16: 0
align 32: 0
base align: 0, 0, 0, 0'''])
test()
if '-O' in str(self.emcc_args) and not self.is_wasm_backend():
print('outlining')
self.set_setting('OUTLINING_LIMIT', 60)
test()
@no_emterpreter
def test_stack_restore(self):
if self.is_wasm():
self.skipTest('generated code not available in wasm')
self.emcc_args += ['-g3'] # to be able to find the generated code
test_path = path_from_root('tests', 'core', 'test_stack_restore')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output)
generated = open('src.cpp.o.js').read()
def ensure_stack_restore_count(function_name, expected_count):
code = generated[generated.find(function_name):]
code = code[:code.find('\n}') + 2]
actual_count = code.count('STACKTOP = sp')
assert actual_count == expected_count, ('Expected %d stack restorations, got %d' % (expected_count, actual_count)) + ': ' + code
ensure_stack_restore_count('function _no_stack_usage', 0)
ensure_stack_restore_count('function _alloca_gets_restored', 1)
ensure_stack_restore_count('function _stack_usage', 1)
def test_strings(self):
test_path = path_from_root('tests', 'core', 'test_strings')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output, ['wowie', 'too', '74'])
def test_strcmp_uni(self):
self.do_run_in_out_file_test('tests', 'core', 'test_strcmp_uni')
def test_strndup(self):
self.do_run_in_out_file_test('tests', 'core', 'test_strndup')
def test_errar(self):
self.do_run_in_out_file_test('tests', 'core', 'test_errar')
def test_mainenv(self):
self.do_run_in_out_file_test('tests', 'core', 'test_mainenv')
def test_funcs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_funcs')
def test_structs(self):
test_path = path_from_root('tests', 'core', 'test_structs')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output)
gen_struct_src = '''
#include <stdio.h>
#include <stdlib.h>
#include "emscripten.h"
struct S
{
int x, y;
};
int main()
{
S* a = {{gen_struct}};
a->x = 51; a->y = 62;
printf("*%d,%d*\\n", a->x, a->y);
{{del_struct}}(a);
return 0;
}
'''
def test_mallocstruct(self):
self.do_run(self.gen_struct_src.replace('{{gen_struct}}', '(S*)malloc(sizeof(S))').replace('{{del_struct}}', 'free'), '*51,62*')
def test_emmalloc(self):
# in newer clang+llvm, the internal calls to malloc in emmalloc may be optimized under
# the assumption that they are external, so like in system_libs.py where we build
# malloc, we need to disable builtin here too
self.emcc_args += ['-fno-builtin']
def test():
self.do_run(open(path_from_root('system', 'lib', 'emmalloc.cpp')).read() + open(path_from_root('tests', 'core', 'test_emmalloc.cpp')).read(),
open(path_from_root('tests', 'core', 'test_emmalloc.txt')).read())
print('normal')
test()
print('debug')
self.emcc_args += ['-DEMMALLOC_DEBUG']
test()
print('debug log')
self.emcc_args += ['-DEMMALLOC_DEBUG_LOG']
self.emcc_args += ['-DRANDOM_ITERS=130']
test()
def test_newstruct(self):
self.do_run(self.gen_struct_src.replace('{{gen_struct}}', 'new S').replace('{{del_struct}}', 'delete'), '*51,62*')
def test_addr_of_stacked(self):
self.do_run_in_out_file_test('tests', 'core', 'test_addr_of_stacked')
def test_globals(self):
self.do_run_in_out_file_test('tests', 'core', 'test_globals')
def test_linked_list(self):
self.do_run_in_out_file_test('tests', 'core', 'test_linked_list')
def test_sup(self):
src = '''
#include <stdio.h>
struct S4 { int x; }; // size: 4
struct S4_2 { short x, y; }; // size: 4, but for alignment purposes, 2
struct S6 { short x, y, z; }; // size: 6
struct S6w { char x[6]; }; // size: 6 also
struct S6z { int x; short y; }; // size: 8, since we align to a multiple of the biggest - 4
struct C___ { S6 a, b, c; int later; };
struct Carr { S6 a[3]; int later; }; // essentially the same, but differently defined
struct C__w { S6 a; S6w b; S6 c; int later; }; // same size, different struct
struct Cp1_ { int pre; short a; S6 b, c; int later; }; // fillers for a
struct Cp2_ { int a; short pre; S6 b, c; int later; }; // fillers for a (get addr of the other filler)
struct Cint { S6 a; int b; S6 c; int later; }; // An int (different size) for b
struct C4__ { S6 a; S4 b; S6 c; int later; }; // Same size as int from before, but a struct
struct C4_2 { S6 a; S4_2 b; S6 c; int later; }; // Same size as int from before, but a struct with max element size 2
struct C__z { S6 a; S6z b; S6 c; int later; }; // different size, 8 instead of 6
int main()
{
#define TEST(struc) \\
{ \\
struc *s = 0; \\
printf("*%s: %d,%d,%d,%d<%d*\\n", #struc, (int)&(s->a), (int)&(s->b), (int)&(s->c), (int)&(s->later), sizeof(struc)); \\
}
#define TEST_ARR(struc) \\
{ \\
struc *s = 0; \\
printf("*%s: %d,%d,%d,%d<%d*\\n", #struc, (int)&(s->a[0]), (int)&(s->a[1]), (int)&(s->a[2]), (int)&(s->later), sizeof(struc)); \\
}
printf("sizeofs:%d,%d\\n", sizeof(S6), sizeof(S6z));
TEST(C___);
TEST_ARR(Carr);
TEST(C__w);
TEST(Cp1_);
TEST(Cp2_);
TEST(Cint);
TEST(C4__);
TEST(C4_2);
TEST(C__z);
return 0;
}
'''
self.do_run(src, 'sizeofs:6,8\n*C___: 0,6,12,20<24*\n*Carr: 0,6,12,20<24*\n*C__w: 0,6,12,20<24*\n*Cp1_: 4,6,12,20<24*\n*Cp2_: 0,6,12,20<24*\n*Cint: 0,8,12,20<24*\n*C4__: 0,8,12,20<24*\n*C4_2: 0,6,10,16<20*\n*C__z: 0,8,16,24<28*')
def test_assert(self):
self.do_run_in_out_file_test('tests', 'core', 'test_assert')
def test_wcslen(self):
self.do_run_in_out_file_test('tests', 'core', 'test_wcslen')
def test_regex(self):
self.do_run_in_out_file_test('tests', 'core', 'test_regex')
def test_longjmp(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp')
def test_longjmp2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp2')
def test_longjmp3(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp3')
def test_longjmp4(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp4')
def test_longjmp_funcptr(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_funcptr')
def test_longjmp_repeat(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_repeat')
def test_longjmp_stacked(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_stacked')
def test_longjmp_exc(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_exc')
def test_longjmp_throw(self):
for disable_throw in [0, 1]:
print(disable_throw)
self.set_setting('DISABLE_EXCEPTION_CATCHING', disable_throw)
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_throw')
def test_longjmp_unwind(self):
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_unwind')
def test_longjmp_i64(self):
self.emcc_args += ['-g']
self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_i64')
def test_siglongjmp(self):
self.do_run_in_out_file_test('tests', 'core', 'test_siglongjmp')
def test_setjmp_many(self):
src = r'''
#include <stdio.h>
#include <setjmp.h>
int main(int argc, char** argv) {
jmp_buf buf;
for (int i = 0; i < NUM; i++) printf("%d\n", setjmp(buf));
if (argc-- == 1131) longjmp(buf, 11);
return 0;
}
'''
for num in [1, 5, 20, 1000]:
print('NUM=%d' % num)
self.do_run(src.replace('NUM', str(num)), '0\n' * num)
def test_setjmp_many_2(self):
src = r'''
#include <setjmp.h>
#include <stdio.h>
jmp_buf env;
void luaWork(int d){
int x;
printf("d is at %d\n", d);
longjmp(env, 1);
}
int main()
{
const int ITERATIONS=25;
for(int i = 0; i < ITERATIONS; i++){
if(!setjmp(env)){
luaWork(i);
}
}
return 0;
}
'''
self.do_run(src, r'''d is at 24''')
def test_setjmp_noleak(self):
src = r'''
#include <setjmp.h>
#include <stdio.h>
#include <assert.h>
jmp_buf env;
void luaWork(int d){
int x;
printf("d is at %d\n", d);
longjmp(env, 1);
}
#include <malloc.h>
#include <stdlib.h>
void dump() {
struct mallinfo m = mallinfo();
printf("dump: %d , %d\n", m.arena, m.uordblks);
}
void work(int n)
{
printf("work %d\n", n);
dump();
if(!setjmp(env)){
luaWork(n);
}
if (n > 0) work(n-1);
}
int main() {
struct mallinfo m1 = mallinfo();
dump();
work(10);
dump();
struct mallinfo m2 = mallinfo();
assert(m1.uordblks == m2.uordblks);
printf("ok.\n");
}
'''
self.do_run(src, r'''ok.''')
def test_exceptions(self):
self.set_setting('EXCEPTION_DEBUG', 1)
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.maybe_closure()
for support_longjmp in [0, 1]:
src = '''
#include <stdio.h>
void thrower() {
printf("infunc...");
throw(99);
printf("FAIL");
}
int main() {
try {
printf("*throw...");
throw(1);
printf("FAIL");
} catch(...) {
printf("caught!");
}
try {
thrower();
} catch(...) {
printf("done!*\\n");
}
return 0;
}
'''
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.set_setting('SUPPORT_LONGJMP', support_longjmp)
self.do_run(src, '*throw...caught!infunc...done!*')
self.set_setting('DISABLE_EXCEPTION_CATCHING', 1)
self.do_run(src, 'exception')
def test_exceptions_custom(self):
self.set_setting('EXCEPTION_DEBUG', 1)
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.maybe_closure()
src = '''
#include <iostream>
class MyException
{
public:
MyException(){ std::cout << "Construct..."; }
MyException( const MyException & ) { std::cout << "Copy..."; }
~MyException(){ std::cout << "Destruct..."; }
};
int function()
{
std::cout << "Throw...";
throw MyException();
}
int function2()
{
return function();
}
int main()
{
try
{
function2();
}
catch (MyException & e)
{
std::cout << "Caught...";
}
try
{
function2();
}
catch (MyException e)
{
std::cout << "Caught...";
}
return 0;
}
'''
self.do_run(src, 'Throw...Construct...Caught...Destruct...Throw...Construct...Copy...Caught...Destruct...Destruct...')
def test_exceptions_2(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
for safe in [0, 1]:
print(safe)
self.set_setting('SAFE_HEAP', safe)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_2')
def test_exceptions_3(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
src = r'''
#include <iostream>
#include <stdexcept>
int main(int argc, char **argv)
{
if (argc != 2) {
std::cout << "need an arg" << std::endl;
return 1;
}
int arg = argv[1][0] - '0';
try {
if (arg == 0) throw "a c string";
if (arg == 1) throw std::exception();
if (arg == 2) throw std::runtime_error("Hello");
} catch(const char * ex) {
std::cout << "Caught C string: " << ex << std::endl;
} catch(const std::exception &ex) {
std::cout << "Caught exception: " << ex.what() << std::endl;
} catch(...) {
std::cout << "Caught something else" << std::endl;
}
std::cout << "Done.\n";
}
'''
print('0')
self.do_run(src, 'Caught C string: a c string\nDone.', ['0'])
print('1')
self.do_run(src, 'Caught exception: std::exception\nDone.', ['1'], no_build=True)
print('2')
self.do_run(src, 'Caught exception: Hello\nDone.', ['2'], no_build=True)
def test_exceptions_white_list(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 2)
# Wasm does not add an underscore to function names. For wasm, the
# mismatches are fixed in fixImports() function in JS glue code.
if not self.is_wasm_backend():
self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["__Z12somefunctionv"])
else:
self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["_Z12somefunctionv"])
# otherwise it is inlined and not identified
self.set_setting('INLINING_LIMIT', 50)
test_path = path_from_root('tests', 'core', 'test_exceptions_white_list')
src, output = (test_path + s for s in ('.cpp', '.out'))
self.do_run_from_file(src, output)
size = len(open('src.cpp.o.js').read())
shutil.copyfile('src.cpp.o.js', 'orig.js')
# check that an empty whitelist works properly (as in, same as exceptions disabled)
empty_output = path_from_root('tests', 'core', 'test_exceptions_white_list_empty.out')
self.set_setting('EXCEPTION_CATCHING_WHITELIST', [])
self.do_run_from_file(src, empty_output)
empty_size = len(open('src.cpp.o.js').read())
shutil.copyfile('src.cpp.o.js', 'empty.js')
self.set_setting('EXCEPTION_CATCHING_WHITELIST', ['fake'])
self.do_run_from_file(src, empty_output)
fake_size = len(open('src.cpp.o.js').read())
shutil.copyfile('src.cpp.o.js', 'fake.js')
self.set_setting('DISABLE_EXCEPTION_CATCHING', 1)
self.do_run_from_file(src, empty_output)
disabled_size = len(open('src.cpp.o.js').read())
shutil.copyfile('src.cpp.o.js', 'disabled.js')
if not self.is_wasm():
print(size, empty_size, fake_size, disabled_size)
assert size - empty_size > 0.0025 * size, [empty_size, size] # big change when we disable entirely
assert size - fake_size > 0.0025 * size, [fake_size, size]
assert abs(empty_size - fake_size) < 0.007 * size, [empty_size, fake_size]
assert empty_size - disabled_size < 0.007 * size, [empty_size, disabled_size] # full disable removes a little bit more
assert fake_size - disabled_size < 0.007 * size, [disabled_size, fake_size]
def test_exceptions_white_list_2(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 2)
# Wasm does not add an underscore to function names. For wasm, the
# mismatches are fixed in fixImports() function in JS glue code.
if not self.is_wasm_backend():
self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["_main"])
else:
self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["main"])
# otherwise it is inlined and not identified
self.set_setting('INLINING_LIMIT', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_white_list_2')
def test_exceptions_uncaught(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
src = r'''
#include <stdio.h>
#include <exception>
struct X {
~X() {
printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no");
}
};
int main() {
printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no");
try {
X x;
throw 1;
} catch(...) {
printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no");
}
printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no");
return 0;
}
'''
self.do_run(src, 'exception? no\nexception? yes\nexception? no\nexception? no\n')
src = r'''
#include <fstream>
#include <iostream>
int main() {
std::ofstream os("test");
os << std::unitbuf << "foo"; // trigger a call to std::uncaught_exception from
// std::basic_ostream::sentry::~sentry
std::cout << "success";
}
'''
self.do_run(src, 'success')
def test_exceptions_uncaught_2(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
src = r'''
#include <iostream>
#include <exception>
int main() {
try {
throw std::exception();
} catch(std::exception) {
try {
throw;
} catch(std::exception) {}
}
if (std::uncaught_exception())
std::cout << "ERROR: uncaught_exception still set.";
else
std::cout << "OK";
}
'''
self.do_run(src, 'OK\n')
def test_exceptions_typed(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.emcc_args += ['-s', 'SAFE_HEAP=0'] # Throwing null will cause an ignorable null pointer access.
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_typed')
def test_exceptions_virtual_inheritance(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_virtual_inheritance')
def test_exceptions_convert(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_convert')
def test_exceptions_multi(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_multi')
def test_exceptions_std(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.emcc_args += ['-s', 'SAFE_HEAP=0']
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_std')
def test_exceptions_alias(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_alias')
def test_exceptions_rethrow(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_rethrow')
def test_exceptions_resume(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.set_setting('EXCEPTION_DEBUG', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_resume')
def test_exceptions_destroy_virtual(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_destroy_virtual')
def test_exceptions_refcount(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_refcount')
def test_exceptions_primary(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_primary')
def test_exceptions_simplify_cfg(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_simplify_cfg')
def test_exceptions_libcxx(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_libcxx')
def test_exceptions_multiple_inherit(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_multiple_inherit')
def test_exceptions_multiple_inherit_rethrow(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_multiple_inherit_rethrow')
def test_bad_typeid(self):
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.do_run(r'''
// exception example
#include <iostream> // std::cerr
#include <typeinfo> // operator typeid
#include <exception> // std::exception
class Polymorphic {virtual void member(){}};
int main () {
try
{
Polymorphic * pb = 0;
const std::type_info& ti = typeid(*pb); // throws a bad_typeid exception
}
catch (std::exception& e)
{
std::cerr << "exception caught: " << e.what() << '\n';
}
return 0;
}
''', 'exception caught: std::bad_typeid')
def test_iostream_ctors(self):
# iostream stuff must be globally constructed before user global
# constructors, so iostream works in global constructors
self.do_run(r'''
#include <iostream>
struct A {
A() { std::cout << "bug"; }
};
A a;
int main() {
std::cout << "free code" << std::endl;
return 0;
}
''', 'bugfree code')
def test_class(self):
self.do_run_in_out_file_test('tests', 'core', 'test_class')
def test_inherit(self):
self.do_run_in_out_file_test('tests', 'core', 'test_inherit')
def test_isdigit_l(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_isdigit_l')
def test_iswdigit(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_iswdigit')
def test_polymorph(self):
self.do_run_in_out_file_test('tests', 'core', 'test_polymorph')
def test_complex(self):
self.do_run_in_out_file_test('tests', 'core', 'test_complex', force_c=True)
def test_float_builtins(self):
# tests wasm_libc_rt
if not self.is_wasm_backend():
self.skipTest('no __builtin_fmin support in JSBackend')
self.do_run_in_out_file_test('tests', 'core', 'test_float_builtins')
def test_segfault(self):
self.set_setting('SAFE_HEAP', 1)
for addr in ['0', 'new D2()']:
print(addr)
src = r'''
#include <stdio.h>
struct Classey {
virtual void doIt() = 0;
};
struct D1 : Classey {
virtual void doIt() { printf("fleefl\n"); }
};
struct D2 : Classey {
virtual void doIt() { printf("marfoosh\n"); }
};
int main(int argc, char **argv)
{
Classey *p = argc == 100 ? new D1() : (Classey*)%s;
p->doIt();
return 0;
}
''' % addr
self.do_run(src, 'segmentation fault' if addr.isdigit() else 'marfoosh')
def test_dynamic_cast(self):
self.do_run_in_out_file_test('tests', 'core', 'test_dynamic_cast')
def test_dynamic_cast_b(self):
self.do_run_in_out_file_test('tests', 'core', 'test_dynamic_cast_b')
def test_dynamic_cast_2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_dynamic_cast_2')
def test_funcptr(self):
self.do_run_in_out_file_test('tests', 'core', 'test_funcptr')
def test_mathfuncptr(self):
self.do_run_in_out_file_test('tests', 'core', 'test_mathfuncptr')
if self.is_emterpreter():
print('emterpreter f32')
self.set_setting('PRECISE_F32', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_mathfuncptr')
def test_funcptrfunc(self):
self.do_run_in_out_file_test('tests', 'core', 'test_funcptrfunc')
def test_funcptr_namecollide(self):
self.do_run_in_out_file_test('tests', 'core', 'test_funcptr_namecollide')
def test_emptyclass(self):
self.do_run_in_out_file_test('tests', 'core', 'test_emptyclass')
def test_alloca(self):
self.do_run_in_out_file_test('tests', 'core', 'test_alloca')
def test_rename(self):
self.do_run_in_out_file_test('tests', 'stdio', 'test_rename', force_c=True)
def test_remove(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'cstdio', 'test_remove')
def test_alloca_stack(self):
self.do_run_in_out_file_test('tests', 'core', 'test_alloca_stack')
def test_stack_byval(self):
self.do_run_in_out_file_test('tests', 'core', 'test_stack_byval')
def test_stack_varargs(self):
# in node.js we allocate argv[0] on the stack, which means the length
# of the program directory influences how much stack we need, and so
# long random temp dir names can lead to random failures. The stack
# size was increased here to avoid that.
self.set_setting('INLINING_LIMIT', 50)
self.set_setting('TOTAL_STACK', 8 * 1024)
self.do_run_in_out_file_test('tests', 'core', 'test_stack_varargs')
def test_stack_varargs2(self):
# in node.js we allocate argv[0] on the stack, which means the length
# of the program directory influences how much stack we need, and so
# long random temp dir names can lead to random failures. The stack
# size was increased here to avoid that.
self.set_setting('TOTAL_STACK', 8 * 1024)
src = r'''
#include <stdio.h>
#include <stdlib.h>
void func(int i) {
}
int main() {
for (int i = 0; i < 7000; i++) {
printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i);
}
printf("ok!\n");
return 0;
}
'''
self.do_run(src, 'ok!')
print('with return')
src = r'''
#include <stdio.h>
#include <stdlib.h>
int main() {
for (int i = 0; i < 7000; i++) {
int j = printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i);
printf(" (%d)\n", j);
}
printf("ok!\n");
return 0;
}
'''
self.do_run(src, 'ok!')
print('with definitely no return')
src = r'''
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void vary(const char *s, ...)
{
va_list v;
va_start(v, s);
char d[20];
vsnprintf(d, 20, s, v);
puts(d);
// Try it with copying
va_list tempva;
va_copy(tempva, v);
vsnprintf(d, 20, s, tempva);
puts(d);
va_end(v);
}
int main() {
for (int i = 0; i < 7000; i++) {
int j = printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i);
printf(" (%d)\n", j);
vary("*cheez: %d+%d*", 99, 24);
vary("*albeit*");
}
printf("ok!\n");
return 0;
}
'''
self.do_run(src, 'ok!')
def test_stack_void(self):
self.set_setting('INLINING_LIMIT', 50)
self.do_run_in_out_file_test('tests', 'core', 'test_stack_void')
# Fails in wasm because of excessive slowness in the wasm-shell
@no_wasm()
def test_life(self):
self.emcc_args += ['-std=c99']
src = open(path_from_root('tests', 'life.c')).read()
self.do_run(src, '''--------------------------------
[] [] [][][]
[] [] [] [][] [] [] []
[] [][] [][] [][][] []
[] [] [] [] [][] [] []
[] [][] [] [] [] [] [][][][]
[][] [][] [] [][][] [] []
[] [][] [][] [][] [][][]
[][] [][][] [] []
[][] [][] []
[][][]
[]
[][][]
[] [][] [][]
[][] [] [][] [][]
[][] [][]
[]
[][]
[][] []
[] [][] []
[][][] []
[] [][]
[] [] []
[]
[] [] []
[][][]
[]
[][][] []
--------------------------------
''', ['2'], force_c=True) # noqa
def test_array2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_array2')
def test_array2b(self):
self.do_run_in_out_file_test('tests', 'core', 'test_array2b')
def test_constglobalstructs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_constglobalstructs')
def test_conststructs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_conststructs')
def test_bigarray(self):
self.do_run_in_out_file_test('tests', 'core', 'test_bigarray')
def test_mod_globalstruct(self):
self.do_run_in_out_file_test('tests', 'core', 'test_mod_globalstruct')
@no_wasm_backend('long doubles are f128s in wasm backend')
def test_pystruct(self):
def test():
self.do_run_in_out_file_test('tests', 'test_pystruct')
test()
print('relocatable') # this tests recursive global structs => nontrivial postSets for relocation
assert self.get_setting('RELOCATABLE') == self.get_setting('EMULATED_FUNCTION_POINTERS') == 0
self.set_setting('RELOCATABLE', 1)
self.set_setting('EMULATED_FUNCTION_POINTERS', 1)
test()
def test_sizeof(self):
# Has invalid writes between printouts
self.set_setting('SAFE_HEAP', 0)
self.do_run_in_out_file_test('tests', 'core', 'test_sizeof')
def test_llvm_used(self):
Building.LLVM_OPTS = 3
self.do_run_in_out_file_test('tests', 'core', 'test_llvm_used')
def test_set_align(self):
self.set_setting('SAFE_HEAP', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_set_align')
def test_emscripten_api(self):
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_save_me_aimee'])
self.do_run_in_out_file_test('tests', 'core', 'test_emscripten_api')
# test EXPORT_ALL
self.set_setting('EXPORTED_FUNCTIONS', [])
self.set_setting('EXPORT_ALL', 1)
self.set_setting('LINKABLE', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_emscripten_api')
def test_emscripten_run_script_string_utf8(self):
src = r'''
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <emscripten.h>
int main() {
const char *str = emscripten_run_script_string("'\\u2603 \\u2603 \\u2603 Hello!'");
printf("length of returned string: %d. Position of substring 'Hello': %d\n", strlen(str), strstr(str, "Hello")-str);
return 0;
}
'''
self.do_run(src, '''length of returned string: 18. Position of substring 'Hello': 12''')
def test_emscripten_get_now(self):
self.banned_js_engines = [V8_ENGINE] # timer limitations in v8 shell
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
if self.run_name == 'asm2':
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_emscripten_get_compiler_setting(self):
test_path = path_from_root('tests', 'core', 'emscripten_get_compiler_setting')
src, output = (test_path + s for s in ('.c', '.out'))
old = self.get_setting('ASSERTIONS')
# with assertions, a nice message is shown
self.set_setting('ASSERTIONS', 1)
self.do_run(open(src).read(), 'You must build with -s RETAIN_COMPILER_SETTINGS=1')
self.set_setting('ASSERTIONS', old)
self.set_setting('RETAIN_COMPILER_SETTINGS', 1)
self.do_run(open(src).read(), open(output).read().replace('waka', shared.EMSCRIPTEN_VERSION))
# TODO: test only worked in non-fastcomp
def test_inlinejs(self):
self.skipTest('non-fastcomp is deprecated and fails in 3.5') # only supports EM_ASM
self.do_run_in_out_file_test('tests', 'core', 'test_inlinejs')
if self.emcc_args == []:
# opts will eliminate the comments
out = open('src.cpp.o.js').read()
for i in range(1, 5):
assert ('comment%d' % i) in out
# TODO: test only worked in non-fastcomp
def test_inlinejs2(self):
self.skipTest('non-fastcomp is deprecated and fails in 3.5') # only supports EM_ASM
self.do_run_in_out_file_test('tests', 'core', 'test_inlinejs2')
def test_inlinejs3(self):
if self.is_wasm():
self.skipTest('wasm requires a proper asm module')
test_path = path_from_root('tests', 'core', 'test_inlinejs3')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output)
print('no debugger, check validation')
src = open(src).read().replace('emscripten_debugger();', '')
self.do_run(src, open(output).read())
def test_inlinejs4(self):
self.do_run(r'''
#include <emscripten.h>
#define TO_STRING_INNER(x) #x
#define TO_STRING(x) TO_STRING_INNER(x)
#define assert_msg(msg, file, line) EM_ASM( throw 'Assert (' + msg + ') failed in ' + file + ':' + line + '!'; )
#define assert(expr) { \
if (!(expr)) { \
assert_msg(#expr, TO_STRING(__FILE__), TO_STRING(__LINE__)); \
} \
}
int main(int argc, char **argv) {
assert(argc != 17);
assert(false);
return 0;
}
''', 'false')
def test_em_asm(self):
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm')
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm', force_c=True)
# Tests various different ways to invoke the EM_ASM(), EM_ASM_INT() and EM_ASM_DOUBLE() macros.
def test_em_asm_2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2')
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2', force_c=True)
# Tests various different ways to invoke the MAIN_THREAD_EM_ASM(), MAIN_THREAD_EM_ASM_INT() and MAIN_THREAD_EM_ASM_DOUBLE() macros.
# This test is identical to test_em_asm_2, just search-replaces EM_ASM to MAIN_THREAD_EM_ASM on the test file. That way if new
# test cases are added to test_em_asm_2.cpp for EM_ASM, they will also get tested in MAIN_THREAD_EM_ASM form.
def test_main_thread_em_asm(self):
src = open(path_from_root('tests', 'core', 'test_em_asm_2.cpp')).read()
create_test_file('src.cpp', src.replace('EM_ASM', 'MAIN_THREAD_EM_ASM'))
expected_result = open(path_from_root('tests', 'core', 'test_em_asm_2.out')).read()
create_test_file('result.out', expected_result.replace('EM_ASM', 'MAIN_THREAD_EM_ASM'))
self.do_run_from_file('src.cpp', 'result.out')
self.do_run_from_file('src.cpp', 'result.out', force_c=True)
def test_main_thread_async_em_asm(self):
self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm')
self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm', force_c=True)
# Tests MAIN_THREAD_EM_ASM_INT() function call with different signatures.
def test_main_thread_em_asm_signatures(self):
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_signatures')
def test_em_asm_unicode(self):
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_unicode')
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_unicode', force_c=True)
def test_em_asm_unused_arguments(self):
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_unused_arguments')
# Verify that EM_ASM macros support getting called with multiple arities.
# Maybe tests will later be joined into larger compilation units?
# Then this must still be compiled separately from other code using EM_ASM
# macros with arities 1-3. Otherwise this may incorrectly report a success.
def test_em_asm_parameter_pack(self):
Building.COMPILER_TEST_OPTS += ['-std=c++11']
self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_parameter_pack')
def test_em_js(self):
self.do_run_in_out_file_test('tests', 'core', 'test_em_js')
self.do_run_in_out_file_test('tests', 'core', 'test_em_js', force_c=True)
def test_runtime_stacksave(self):
src = open(path_from_root('tests', 'core', 'test_runtime_stacksave.c')).read()
self.do_run(src, 'success')
def test_memorygrowth(self):
self.maybe_closure()
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=0'] # start with 0
# With typed arrays in particular, it is dangerous to use more memory than TOTAL_MEMORY,
# since we then need to enlarge the heap(s).
src = open(path_from_root('tests', 'core', 'test_memorygrowth.c')).read()
# Fail without memory growth
self.do_run(src, 'OOM')
fail = open('src.cpp.o.js').read()
# Win with it
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH']
self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*')
win = open('src.cpp.o.js').read()
if '-O2' in self.emcc_args and not self.is_wasm():
# Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized)
possible_starts = ['// EMSCRIPTEN_START_FUNCS', 'var TOTAL_MEMORY']
code_start = None
for s in possible_starts:
if fail.find(s) >= 0:
code_start = s
break
assert code_start is not None, 'Generated code must contain one of ' + str(possible_starts)
fail = fail[fail.find(code_start):]
win = win[win.find(code_start):]
assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)])
# Tracing of memory growths should work
self.set_setting('EMSCRIPTEN_TRACING', 1)
self.emcc_args += ['--tracing']
self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*')
def test_memorygrowth_2(self):
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=0'] # start with 0
emcc_args = self.emcc_args[:]
def test():
self.emcc_args = emcc_args[:]
# With typed arrays in particular, it is dangerous to use more memory than TOTAL_MEMORY,
# since we then need to enlarge the heap(s).
src = open(path_from_root('tests', 'core', 'test_memorygrowth_2.c')).read()
# Fail without memory growth
self.do_run(src, 'OOM')
fail = open('src.cpp.o.js').read()
# Win with it
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=1']
self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*')
win = open('src.cpp.o.js').read()
if '-O2' in self.emcc_args and not self.is_wasm():
# Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized)
code_start = 'var TOTAL_MEMORY'
fail = fail[fail.find(code_start):]
win = win[win.find(code_start):]
assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)])
test()
def test_memorygrowth_3(self):
# checks handling of malloc failure properly
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=0', '-s', 'ABORTING_MALLOC=0', '-s', 'SAFE_HEAP']
self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_3')
def test_memorygrowth_wasm_mem_max(self):
if not self.is_wasm():
self.skipTest('wasm memory specific test')
# check that memory growth does not exceed the wasm mem max limit
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'TOTAL_MEMORY=64Mb', '-s', 'WASM_MEM_MAX=100Mb']
self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_wasm_mem_max')
def test_memorygrowth_3_force_fail_reallocBuffer(self):
self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'TEST_MEMORY_GROWTH_FAILS=1']
self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_3')
def test_ssr(self): # struct self-ref
src = '''
#include <stdio.h>
// see related things in openjpeg
typedef struct opj_mqc_state {
unsigned int qeval;
int mps;
struct opj_mqc_state *nmps;
struct opj_mqc_state *nlps;
} opj_mqc_state_t;
static opj_mqc_state_t mqc_states[4] = {
{0x5600, 0, &mqc_states[2], &mqc_states[3]},
{0x5602, 1, &mqc_states[3], &mqc_states[2]},
};
int main() {
printf("*%d*\\n", (int)(mqc_states+1)-(int)mqc_states);
for (int i = 0; i < 2; i++)
printf("%d:%d,%d,%d,%d\\n", i, mqc_states[i].qeval, mqc_states[i].mps,
(int)mqc_states[i].nmps-(int)mqc_states, (int)mqc_states[i].nlps-(int)mqc_states);
return 0;
}
'''
self.do_run(src, '''*16*\n0:22016,0,32,48\n1:22018,1,48,32\n''')
def test_tinyfuncstr(self):
self.do_run_in_out_file_test('tests', 'core', 'test_tinyfuncstr')
def test_llvmswitch(self):
self.do_run_in_out_file_test('tests', 'core', 'test_llvmswitch')
# By default, when user has not specified a -std flag, Emscripten should always build .cpp files using the C++03 standard,
# i.e. as if "-std=c++03" had been passed on the command line. On Linux with Clang 3.2 this is the case, but on Windows
# with Clang 3.2 -std=c++11 has been chosen as default, because of
# < jrose> clb: it's deliberate, with the idea that for people who don't care about the standard, they should be using the "best" thing we can offer on that platform
def test_cxx03_do_run(self):
self.do_run_in_out_file_test('tests', 'core', 'test_cxx03_do_run')
@no_emterpreter
def test_bigswitch(self):
src = open(path_from_root('tests', 'bigswitch.cpp')).read()
self.do_run(src, '''34962: GL_ARRAY_BUFFER (0x8892)
26214: what?
35040: GL_STREAM_DRAW (0x88E0)
3060: what?
''', args=['34962', '26214', '35040', str(0xbf4)])
@no_emterpreter
@is_slow_test
def test_biggerswitch(self):
if self.is_wasm_backend() and not is_optimizing(self.emcc_args):
self.skipTest('nodejs takes >6GB to compile this if the wasm is not optimized, which OOMs, see https://github.com/emscripten-core/emscripten/issues/7928#issuecomment-458308453')
num_cases = 20000
switch_case = run_process([PYTHON, path_from_root('tests', 'gen_large_switchcase.py'), str(num_cases)], stdout=PIPE, stderr=PIPE).stdout
self.do_run(switch_case, '''58996: 589965899658996
59297: 592975929759297
59598: default
59899: 598995989959899
Success!''')
@no_wasm_backend('no implementation of computed gotos')
def test_indirectbr(self):
Building.COMPILER_TEST_OPTS = [x for x in Building.COMPILER_TEST_OPTS if x != '-g']
self.do_run_in_out_file_test('tests', 'core', 'test_indirectbr')
@no_wasm_backend('no implementation of computed gotos')
def test_indirectbr_many(self):
self.do_run_in_out_file_test('tests', 'core', 'test_indirectbr_many')
def test_pack(self):
src = '''
#include <stdio.h>
#include <string.h>
#pragma pack(push,1)
typedef struct header
{
unsigned char id;
unsigned short colour;
unsigned char desc;
} header;
#pragma pack(pop)
typedef struct fatheader
{
unsigned char id;
unsigned short colour;
unsigned char desc;
} fatheader;
int main( int argc, const char *argv[] ) {
header h, *ph = 0;
fatheader fh, *pfh = 0;
printf("*%d,%d,%d*\\n", sizeof(header), (int)((int)&h.desc - (int)&h.id), (int)(&ph[1])-(int)(&ph[0]));
printf("*%d,%d,%d*\\n", sizeof(fatheader), (int)((int)&fh.desc - (int)&fh.id), (int)(&pfh[1])-(int)(&pfh[0]));
return 0;
}
'''
self.do_run(src, '*4,3,4*\n*6,4,6*')
def test_varargs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_varargs')
@no_wasm_backend('Calling varargs across function calls is undefined behavior in C,'
' and asmjs and wasm implement it differently.')
def test_varargs_multi(self):
self.do_run_in_out_file_test('tests', 'core', 'test_varargs_multi')
@unittest.skip('clang cannot compile this code with that target yet')
def test_varargs_byval(self):
src = r'''
#include <stdio.h>
#include <stdarg.h>
typedef struct type_a {
union {
double f;
void *p;
int i;
short sym;
} value;
} type_a;
enum mrb_vtype {
MRB_TT_FALSE = 0, /* 0 */
MRB_TT_CLASS = 9 /* 9 */
};
typedef struct type_b {
enum mrb_vtype tt:8;
} type_b;
void print_type_a(int argc, ...);
void print_type_b(int argc, ...);
int main(int argc, char *argv[])
{
type_a a;
type_b b;
a.value.p = (void*) 0x12345678;
b.tt = MRB_TT_CLASS;
printf("The original address of a is: %p\n", a.value.p);
printf("The original type of b is: %d\n", b.tt);
print_type_a(1, a);
print_type_b(1, b);
return 0;
}
void print_type_a(int argc, ...) {
va_list ap;
type_a a;
va_start(ap, argc);
a = va_arg(ap, type_a);
va_end(ap);
printf("The current address of a is: %p\n", a.value.p);
}
void print_type_b(int argc, ...) {
va_list ap;
type_b b;
va_start(ap, argc);
b = va_arg(ap, type_b);
va_end(ap);
printf("The current type of b is: %d\n", b.tt);
}
'''
self.do_run(src, '''The original address of a is: 0x12345678
The original type of b is: 9
The current address of a is: 0x12345678
The current type of b is: 9
''')
def test_functionpointer_libfunc_varargs(self):
self.do_run_in_out_file_test('tests', 'core', 'test_functionpointer_libfunc_varargs')
def test_structbyval(self):
self.set_setting('INLINING_LIMIT', 50)
# part 1: make sure that normally, passing structs by value works
src = r'''
#include <stdio.h>
struct point
{
int x, y;
};
void dump(struct point p) {
p.x++; // should not modify
p.y++; // anything in the caller!
printf("dump: %d,%d\n", p.x, p.y);
}
void dumpmod(struct point *p) {
p->x++; // should not modify
p->y++; // anything in the caller!
printf("dump: %d,%d\n", p->x, p->y);
}
int main( int argc, const char *argv[] ) {
point p = { 54, 2 };
printf("pre: %d,%d\n", p.x, p.y);
dump(p);
void (*dp)(point p) = dump; // And, as a function pointer
dp(p);
printf("post: %d,%d\n", p.x, p.y);
dumpmod(&p);
dumpmod(&p);
printf("last: %d,%d\n", p.x, p.y);
return 0;
}
'''
self.do_run(src, 'pre: 54,2\ndump: 55,3\ndump: 55,3\npost: 54,2\ndump: 55,3\ndump: 56,4\nlast: 56,4')
# Check for lack of warning in the generated code (they should appear in part 2)
generated = open('src.cpp.o.js').read()
assert 'Casting a function pointer type to another with a different number of arguments.' not in generated, 'Unexpected warning'
# part 2: make sure we warn about mixing c and c++ calling conventions here
if self.emcc_args != []:
# Optimized code is missing the warning comments
return
create_test_file('header.h', r'''
struct point
{
int x, y;
};
''')
create_test_file('supp.cpp', r'''
#include <stdio.h>
#include "header.h"
void dump(struct point p) {
p.x++; // should not modify
p.y++; // anything in the caller!
printf("dump: %d,%d\n", p.x, p.y);
}
''')
create_test_file('main.cpp', r'''
#include <stdio.h>
#include "header.h"
#ifdef __cplusplus
extern "C" {
#endif
void dump(struct point p);
#ifdef __cplusplus
}
#endif
int main( int argc, const char *argv[] ) {
struct point p = { 54, 2 };
printf("pre: %d,%d\n", p.x, p.y);
dump(p);
void (*dp)(struct point p) = dump; // And, as a function pointer
dp(p);
printf("post: %d,%d\n", p.x, p.y);
return 0;
}
''')
Building.emcc('supp.cpp')
Building.emcc('main.cpp')
Building.link_to_object(['supp.cpp.o', 'main.cpp.o'], 'all.o')
# This will fail! See explanation near the warning we check for, in the compiler source code
run_process([PYTHON, EMCC, 'all.o'] + self.emcc_args, check=False, stderr=PIPE)
# Check for warning in the generated code
generated = open('src.cpp.o.js').read()
print('skipping C/C++ conventions warning check, since not i386-pc-linux-gnu', file=sys.stderr)
def test_stdlibs(self):
# safe heap prints a warning that messes up our output.
self.set_setting('SAFE_HEAP', 0)
# needs atexit
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_stdlibs')
def test_stdbool(self):
src = r'''
#include <stdio.h>
#include <stdbool.h>
int main() {
bool x = true;
bool y = false;
printf("*%d*\n", x != y);
return 0;
}
'''
self.do_run(src, '*1*', force_c=True)
def test_strtoll_hex(self):
# tests strtoll for hex strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_hex')
def test_strtoll_dec(self):
# tests strtoll for decimal strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_dec')
def test_strtoll_bin(self):
# tests strtoll for binary strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_bin')
def test_strtoll_oct(self):
# tests strtoll for decimal strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_oct')
def test_strtol_hex(self):
# tests strtoll for hex strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtol_hex')
def test_strtol_dec(self):
# tests strtoll for decimal strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtol_dec')
def test_strtol_bin(self):
# tests strtoll for binary strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtol_bin')
def test_strtol_oct(self):
# tests strtoll for decimal strings (0x...)
self.do_run_in_out_file_test('tests', 'core', 'test_strtol_oct')
def test_atexit(self):
# Confirms they are called in reverse order
# needs atexits
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_atexit')
def test_pthread_specific(self):
src = open(path_from_root('tests', 'pthread', 'specific.c')).read()
expected = open(path_from_root('tests', 'pthread', 'specific.c.txt')).read()
self.do_run(src, expected, force_c=True)
def test_pthread_equal(self):
self.do_run_in_out_file_test('tests', 'pthread', 'test_pthread_equal')
def test_tcgetattr(self):
src = open(path_from_root('tests', 'termios', 'test_tcgetattr.c')).read()
self.do_run(src, 'success', force_c=True)
def test_time(self):
src = open(path_from_root('tests', 'time', 'src.cpp')).read()
expected = open(path_from_root('tests', 'time', 'output.txt')).read()
self.do_run(src, expected)
if 'TZ' in os.environ:
print('TZ set in environment, skipping extra time zone checks')
else:
try:
for tz in ['EST+05EDT', 'UTC+0']:
print('extra tz test:', tz)
# Run the test with different time zone settings if
# possible. It seems that the TZ environment variable does not
# work all the time (at least it's not well respected by
# Node.js on Windows), but it does no harm either.
os.environ['TZ'] = tz
self.do_run(src, expected)
finally:
del os.environ['TZ']
def test_timeb(self):
# Confirms they are called in reverse order
self.do_run_in_out_file_test('tests', 'core', 'test_timeb')
def test_time_c(self):
self.do_run_in_out_file_test('tests', 'core', 'test_time_c')
def test_gmtime(self):
self.do_run_in_out_file_test('tests', 'core', 'test_gmtime')
def test_strptime_tm(self):
self.do_run_in_out_file_test('tests', 'core', 'test_strptime_tm')
def test_strptime_days(self):
self.do_run_in_out_file_test('tests', 'core', 'test_strptime_days')
def test_strptime_reentrant(self):
# needs to flush stdio streams
self.set_setting('EXIT_RUNTIME', 1)
self.do_run_in_out_file_test('tests', 'core', 'test_strptime_reentrant')
def test_strftime(self):
self.do_run_in_out_file_test('tests', 'core', 'test_strftime')
@no_wasm_backend("wasm backend doesn't compile intentional segfault into an abort() call. "
"It also doesn't segfault.")
def test_intentional_fault(self):
# Some programs intentionally segfault themselves, we should compile that into a throw
src = open(path_from_root('tests', 'core', 'test_intentional_fault.c')).read()
self.do_run(src, 'abort()' if self.run_name != 'asm2g' else 'abort("segmentation fault')
def test_trickystring(self):
self.do_run_in_out_file_test('tests', 'core', 'test_trickystring')
def test_statics(self):
self.do_run_in_out_file_test('tests', 'core', 'test_statics')
def test_copyop(self):
# clang generated code is vulnerable to this, as it uses
# memcpy for assignments, with hardcoded numbers of bytes
# (llvm-gcc copies items one by one). See QUANTUM_SIZE in
# settings.js.
test_path = path_from_root('tests', 'core', 'test_copyop')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output)
def test_memcpy_memcmp(self):
self.banned_js_engines = [V8_ENGINE] # Currently broken under V8_ENGINE but not node
test_path = path_from_root('tests', 'core', 'test_memcpy_memcmp')
src, output = (test_path + s for s in ('.c', '.out'))
def check(result, err):
result = result.replace('\n \n', '\n') # remove extra node output
return hashlib.sha1(result.encode('utf-8')).hexdigest()
self.do_run_from_file(src, output, output_nicerizer=check)
def test_memcpy2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_memcpy2')
def test_memcpy3(self):
self.do_run_in_out_file_test('tests', 'core', 'test_memcpy3')
def test_memcpy_alignment(self):
self.do_run(open(path_from_root('tests', 'test_memcpy_alignment.cpp')).read(), 'OK.')
def test_memset_alignment(self):
self.do_run(open(path_from_root('tests', 'test_memset_alignment.cpp')).read(), 'OK.')
def test_memset(self):
self.do_run_in_out_file_test('tests', 'core', 'test_memset')
def test_getopt(self):
test_path = path_from_root('tests', 'core', 'test_getopt')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output, args=['-t', '12', '-n', 'foobar'])
def test_getopt_long(self):
test_path = path_from_root('tests', 'core', 'test_getopt_long')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output, args=['--file', 'foobar', '-b'])
def test_memmove(self):
self.do_run_in_out_file_test('tests', 'core', 'test_memmove')
def test_memmove2(self):
self.do_run_in_out_file_test('tests', 'core', 'test_memmove2')
def test_memmove3(self):
self.do_run_in_out_file_test('tests', 'core', 'test_memmove3')
def test_flexarray_struct(self):
self.do_run_in_out_file_test('tests', 'core', 'test_flexarray_struct')
def test_bsearch(self):
self.do_run_in_out_file_test('tests', 'core', 'test_bsearch')
@no_wasm_backend("wasm backend has no support for fastcomp's -emscripten-assertions flag")
def test_stack_overflow(self):
self.set_setting('ASSERTIONS', 1)
self.do_run(open(path_from_root('tests', 'core', 'stack_overflow.cpp')).read(), 'Stack overflow!')
def test_stackAlloc(self):
self.do_run_in_out_file_test('tests', 'core', 'stackAlloc')
def test_nestedstructs(self):
src = '''
#include <stdio.h>
#include "emscripten.h"
struct base {
int x;
float y;
union {
int a;
float b;
};
char c;
};
struct hashtableentry {
int key;
base data;
};
struct hashset {
typedef hashtableentry entry;
struct chain { entry elem; chain *next; };
// struct chainchunk { chain chains[100]; chainchunk *next; };
};
struct hashtable : hashset {
hashtable() {
base *b = NULL;
entry *e = NULL;
chain *c = NULL;
printf("*%d,%d,%d,%d,%d,%d|%d,%d,%d,%d,%d,%d,%d,%d|%d,%d,%d,%d,%d,%d,%d,%d,%d,%d*\\n",
sizeof(base),
int(&(b->x)), int(&(b->y)), int(&(b->a)), int(&(b->b)), int(&(b->c)),
sizeof(hashtableentry),
int(&(e->key)), int(&(e->data)), int(&(e->data.x)), int(&(e->data.y)), int(&(e->data.a)), int(&(e->data.b)), int(&(e->data.c)),
sizeof(hashset::chain),
int(&(c->elem)), int(&(c->next)), int(&(c->elem.key)), int(&(c->elem.data)), int(&(c->elem.data.x)), int(&(c->elem.data.y)), int(&(c->elem.data.a)), int(&(c->elem.data.b)), int(&(c->elem.data.c))
);
}
};
struct B { char buffer[62]; int last; char laster; char laster2; };
struct Bits {
unsigned short A : 1;
unsigned short B : 1;
unsigned short C : 1;
unsigned short D : 1;
unsigned short x1 : 1;
unsigned short x2 : 1;
unsigned short x3 : 1;
unsigned short x4 : 1;
};
int main() {
hashtable t;
// Part 2 - the char[] should be compressed, BUT have a padding space at the end so the next
// one is aligned properly. Also handle char; char; etc. properly.
B *b = NULL;
printf("*%d,%d,%d,%d,%d,%d,%d,%d,%d*\\n", int(b), int(&(b->buffer)), int(&(b->buffer[0])), int(&(b->buffer[1])), int(&(b->buffer[2])),
int(&(b->last)), int(&(b->laster)), int(&(b->laster2)), sizeof(B));
// Part 3 - bitfields, and small structures
Bits *b2 = NULL;
printf("*%d*\\n", sizeof(Bits));
return 0;
}
'''
# Bloated memory; same layout as C/C++
self.do_run(src, '*16,0,4,8,8,12|20,0,4,4,8,12,12,16|24,0,20,0,4,4,8,12,12,16*\n*0,0,0,1,2,64,68,69,72*\n*2*')
def prep_dlfcn_lib(self):
self.clear_setting('MAIN_MODULE')
self.set_setting('SIDE_MODULE')
self.set_setting('EXPORT_ALL')
def prep_dlfcn_main(self):
self.set_setting('MAIN_MODULE')
self.clear_setting('SIDE_MODULE')
self.set_setting('EXPORT_ALL')
create_test_file('lib_so_pre.js', '''
if (!Module['preRun']) Module['preRun'] = [];
Module['preRun'].push(function() { FS.createDataFile('/', 'liblib.so', %s, true, false, false); });
''' % str(list(bytearray(open('liblib.so', 'rb').read()))))
self.emcc_args += ['--pre-js', 'lib_so_pre.js']
def build_dlfcn_lib(self, lib_src, dirname, filename):
if self.get_setting('WASM'):
# emcc emits a wasm in this case
self.build(lib_src, dirname, filename, js_outfile=False)
shutil.move(filename + '.o.wasm', os.path.join(dirname, 'liblib.so'))
else:
self.build(lib_src, dirname, filename)
shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so'))
@needs_dlfcn
def test_dlfcn_missing(self):
self.set_setting('MAIN_MODULE', 1)
self.set_setting('ASSERTIONS', 1)
src = r'''
#include <dlfcn.h>
#include <stdio.h>
#include <assert.h>
int main() {
void* lib_handle = dlopen("libfoo.so", RTLD_NOW);
assert(!lib_handle);
printf("error: %s\n", dlerror());
return 0;
}
'''
self.do_run(src, 'error: Could not load dynamic lib: libfoo.so\nError: No such file or directory')
print('without assertions, the error is less clear')
self.set_setting('ASSERTIONS', 0)
self.do_run(src, 'error: Could not load dynamic lib: libfoo.so\nError: FS error')
@needs_dlfcn
def test_dlfcn_basic(self):
self.prep_dlfcn_lib()
lib_src = '''
#include <cstdio>
class Foo {
public:
Foo() {
puts("Constructing lib object.");
}
};
Foo global;
'''
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.cpp')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
src = '''
#include <cstdio>
#include <dlfcn.h>
class Bar {
public:
Bar() {
puts("Constructing main object.");
}
};
Bar global;
int main() {
dlopen("liblib.so", RTLD_NOW);
return 0;
}
'''
self.do_run(src, 'Constructing main object.\nConstructing lib object.\n')
@needs_dlfcn
def test_dlfcn_i64(self):
# avoid using asm2wasm imports, which don't work in side modules yet (should they?)
self.set_setting('BINARYEN_TRAP_MODE', 'clamp')
self.prep_dlfcn_lib()
self.set_setting('EXPORTED_FUNCTIONS', ['_foo'])
lib_src = '''
int foo(int x) {
return (long long)x / (long long)1234;
}
'''
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.c')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
self.set_setting('EXPORTED_FUNCTIONS', ['_main'])
src = r'''
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
typedef int (*intfunc)(int);
void *p;
int main() {
p = malloc(1024);
void *lib_handle = dlopen("liblib.so", 0);
if (!lib_handle) {
puts(dlerror());
abort();
}
printf("load %p\n", lib_handle);
intfunc x = (intfunc)dlsym(lib_handle, "foo");
printf("foo func %p\n", x);
if (p == 0) return 1;
printf("|%d|\n", x(81234567));
return 0;
}
'''
self.do_run(src, '|65830|')
@no_wasm # TODO: EM_ASM in shared wasm modules, stored inside the wasm somehow
@needs_dlfcn
def test_dlfcn_em_asm(self):
self.prep_dlfcn_lib()
lib_src = '''
#include <emscripten.h>
class Foo {
public:
Foo() {
EM_ASM( out("Constructing lib object.") );
}
};
Foo global;
'''
filename = 'liblib.cpp'
self.build_dlfcn_lib(lib_src, self.get_dir(), filename)
self.prep_dlfcn_main()
src = '''
#include <emscripten.h>
#include <dlfcn.h>
class Bar {
public:
Bar() {
EM_ASM( out("Constructing main object.") );
}
};
Bar global;
int main() {
dlopen("liblib.so", RTLD_NOW);
EM_ASM( out("All done.") );
return 0;
}
'''
self.do_run(src, 'Constructing main object.\nConstructing lib object.\nAll done.\n')
@needs_dlfcn
def test_dlfcn_qsort(self):
self.prep_dlfcn_lib()
self.set_setting('EXPORTED_FUNCTIONS', ['_get_cmp'])
lib_src = '''
int lib_cmp(const void* left, const void* right) {
const int* a = (const int*) left;
const int* b = (const int*) right;
if(*a > *b) return 1;
else if(*a == *b) return 0;
else return -1;
}
typedef int (*CMP_TYPE)(const void*, const void*);
extern "C" CMP_TYPE get_cmp() {
return lib_cmp;
}
'''
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.cpp')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc'])
src = '''
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
typedef int (*CMP_TYPE)(const void*, const void*);
int main_cmp(const void* left, const void* right) {
const int* a = (const int*) left;
const int* b = (const int*) right;
if(*a < *b) return 1;
else if(*a == *b) return 0;
else return -1;
}
int main() {
void* lib_handle;
CMP_TYPE (*getter_ptr)();
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");
return 1;
}
getter_ptr = (CMP_TYPE (*)()) dlsym(lib_handle, "get_cmp");
if (getter_ptr == NULL) {
printf("Could not find func.\\n");
return 1;
}
lib_cmp_ptr = getter_ptr();
qsort((void*)arr, 5, sizeof(int), lib_cmp_ptr);
printf("Sort with lib comparison: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\\n");
return 0;
}
'''
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', '*'))
if self.get_setting('ASM_JS') and SPIDERMONKEY_ENGINE and os.path.exists(SPIDERMONKEY_ENGINE[0]) and not self.is_wasm():
out = run_js('liblib.so', engine=SPIDERMONKEY_ENGINE, full_output=True, stderr=STDOUT)
if 'asm' in out:
self.validate_asmjs(out)
@needs_dlfcn
def test_dlfcn_data_and_fptr(self):
# Failing under v8 since: https://chromium-review.googlesource.com/712595
if self.is_wasm():
self.banned_js_engines = [V8_ENGINE]
if Building.LLVM_OPTS:
self.skipTest('LLVM opts will optimize out parent_func')
self.prep_dlfcn_lib()
lib_src = '''
#include <stdio.h>
int global = 42;
extern void parent_func(); // a function that is defined in the parent
void lib_fptr() {
printf("Second calling lib_fptr from main.\\n");
parent_func();
// call it also through a pointer, to check indexizing
void (*p_f)();
p_f = parent_func;
p_f();
}
extern "C" void (*func(int x, void(*fptr)()))() {
printf("In func: %d\\n", x);
fptr();
return lib_fptr;
}
'''
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.cpp')
self.set_setting('EXPORTED_FUNCTIONS', ['_func'])
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
src = '''
#include <stdio.h>
#include <dlfcn.h>
#include <emscripten.h>
typedef void (*FUNCTYPE(int, void(*)()))();
FUNCTYPE func;
void EMSCRIPTEN_KEEPALIVE parent_func() {
printf("parent_func called from child\\n");
}
void main_fptr() {
printf("First calling main_fptr from lib.\\n");
}
int main() {
void* lib_handle;
FUNCTYPE* func_fptr;
// Test basic lib loading.
lib_handle = dlopen("liblib.so", RTLD_NOW);
if (lib_handle == NULL) {
printf("Could not load lib.\\n");
return 1;
}
// Test looked up function.
func_fptr = (FUNCTYPE*) dlsym(lib_handle, "func");
// Load twice to test cache.
func_fptr = (FUNCTYPE*) dlsym(lib_handle, "func");
if (func_fptr == NULL) {
printf("Could not find func.\\n");
return 1;
}
// Test passing function pointers across module bounds.
void (*fptr)() = func_fptr(13, main_fptr);
fptr();
// Test global data.
int* global = (int*) dlsym(lib_handle, "global");
if (global == NULL) {
printf("Could not find global.\\n");
return 1;
}
printf("Var: %d\\n", *global);
return 0;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_main'])
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', '*'))
@needs_dlfcn
def test_dlfcn_varargs(self):
# this test is not actually valid - it fails natively. the child should fail
# to be loaded, not load and successfully see the parent print_ints func
self.prep_dlfcn_lib()
lib_src = r'''
void print_ints(int n, ...);
extern "C" void func() {
print_ints(2, 13, 42);
}
'''
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.cpp')
self.set_setting('EXPORTED_FUNCTIONS', ['_func'])
self.build_dlfcn_lib(lib_src, dirname, filename)
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;
va_start(args, n);
for (int i = 0; i < n; i++) {
printf("%d\n", va_arg(args, int));
}
va_end(args);
}
int main() {
void* lib_handle;
void (*fptr)();
print_ints(2, 100, 200);
lib_handle = dlopen("liblib.so", RTLD_NOW);
assert(lib_handle);
fptr = (void (*)())dlsym(lib_handle, "func");
fptr();
return 0;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_main'])
self.do_run(src, '100\n200\n13\n42\n')
@needs_dlfcn
def test_dlfcn_alignment_and_zeroing(self):
self.prep_dlfcn_lib()
self.set_setting('TOTAL_MEMORY', 16 * 1024 * 1024)
lib_src = r'''
extern "C" {
int prezero = 0;
__attribute__((aligned(1024))) int superAligned = 12345;
int postzero = 0;
}
'''
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.cpp')
self.build_dlfcn_lib(lib_src, dirname, filename)
for i in range(10):
curr = '%d.so' % i
shutil.copyfile('liblib.so', curr)
self.emcc_args += ['--embed-file', curr]
self.prep_dlfcn_main()
self.set_setting('TOTAL_MEMORY', 128 * 1024 * 1024)
src = r'''
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <assert.h>
#include <emscripten.h>
int main() {
printf("'prepare' memory with non-zero inited stuff\n");
int num = 120 * 1024 * 1024; // total is 128; we'll use 5*5 = 25 at least, so allocate pretty much all of it
void* mem = malloc(num);
assert(mem);
printf("setting this range to non-zero: %d - %d\n", int(mem), int(mem) + num);
memset(mem, 1, num);
EM_ASM({
var value = HEAP8[64*1024*1024];
out('verify middle of memory is non-zero: ' + value);
assert(value === 1);
});
free(mem);
for (int i = 0; i < 10; i++) {
char* curr = "?.so";
curr[0] = '0' + i;
printf("loading %s\n", curr);
void* lib_handle = dlopen(curr, RTLD_NOW);
if (!lib_handle) {
puts(dlerror());
assert(0);
}
printf("getting superAligned\n");
int* superAligned = (int*)dlsym(lib_handle, "superAligned");
assert(superAligned);
assert(int(superAligned) % 1024 == 0); // alignment
printf("checking value of superAligned, at %d\n", superAligned);
assert(*superAligned == 12345); // value
printf("getting prezero\n");
int* prezero = (int*)dlsym(lib_handle, "prezero");
assert(prezero);
printf("checking value of prezero, at %d\n", prezero);
assert(*prezero == 0);
*prezero = 1;
assert(*prezero != 0);
printf("getting postzero\n");
int* postzero = (int*)dlsym(lib_handle, "postzero");
printf("checking value of postzero, at %d\n", postzero);
assert(postzero);
printf("checking value of postzero\n");
assert(*postzero == 0);
*postzero = 1;
assert(*postzero != 0);
}
printf("success.\n");
return 0;
}
'''
self.do_run(src, 'success.\n')
@no_wasm # TODO: this needs to add JS functions to a wasm Table, need to figure that out
@needs_dlfcn
def test_dlfcn_self(self):
self.prep_dlfcn_main()
def post(filename):
with open(filename) as f:
for line in f:
if 'var NAMED_GLOBALS' in line:
table = line
break
else:
self.fail('Could not find symbol table!')
table = table[table.find('{'):table.find('}') + 1]
# ensure there aren't too many globals; we don't want unnamed_addr
assert table.count(',') <= 30, table.count(',')
test_path = path_from_root('tests', 'core', 'test_dlfcn_self')
src, output = (test_path + s for s in ('.c', '.out'))
self.do_run_from_file(src, output, post_build=post)
@needs_dlfcn
def test_dlfcn_unique_sig(self):
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;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_myfunc'])
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.c')
self.build_dlfcn_lib(lib_src, dirname, filename)
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;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc'])
self.do_run(src, 'success', force_c=True)
@needs_dlfcn
def test_dlfcn_info(self):
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;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_myfunc'])
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.c')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
src = '''
#include <assert.h>
#include <stdio.h>
#include <string.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);
/* Verify that we don't corrupt func_ptr when calling dladdr. */
Dl_info info;
memset(&info, 0, sizeof(info));
dladdr(func_ptr, &info);
assert(func_ptr != NULL);
assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13);
/* Verify something useful lives in info. */
assert(info.dli_fname != NULL);
assert(info.dli_fbase == NULL);
assert(info.dli_sname == NULL);
assert(info.dli_saddr == NULL);
puts("success");
return 0;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc'])
self.do_run(src, 'success', force_c=True)
@needs_dlfcn
def test_dlfcn_stacks(self):
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);
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_myfunc'])
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.c')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
src = '''
#include <assert.h>
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
typedef int (*FUNCTYPE)(const char *);
int main() {
void *lib_handle;
FUNCTYPE func_ptr;
char str[128];
snprintf(str, sizeof(str), "foobar");
// HACK: Use strcmp in the main executable so that it doesn't get optimized out and the dynamic library
// is able to use it.
assert(!strcmp(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;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_strcmp'])
self.do_run(src, 'success', force_c=True)
@needs_dlfcn
def test_dlfcn_funcs(self):
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;
}
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_callvoid', '_callint', '_getvoid', '_getint'])
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.c')
self.build_dlfcn_lib(lib_src, dirname, filename)
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;
}
'''
self.set_setting('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)
@needs_dlfcn
def test_dlfcn_mallocs(self):
# will be exhausted without functional malloc/free
self.set_setting('TOTAL_MEMORY', 64 * 1024 * 1024)
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); }
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_mallocproxy', '_freeproxy'])
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.c')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
src = open(path_from_root('tests', 'dlmalloc_proxy.c')).read()
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_free'])
self.do_run(src, '''*294,153*''', force_c=True)
@needs_dlfcn
def test_dlfcn_longjmp(self):
self.prep_dlfcn_lib()
lib_src = r'''
#include <setjmp.h>
#include <stdio.h>
void jumpy(jmp_buf buf) {
static int i = 0;
i++;
if (i == 10) longjmp(buf, i);
printf("pre %d\n", i);
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_jumpy'])
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.c')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
src = r'''
#include <assert.h>
#include <stdio.h>
#include <dlfcn.h>
#include <setjmp.h>
typedef void (*jumpfunc)(jmp_buf);
int main() {
printf("go!\n");
void *lib_handle;
lib_handle = dlopen("liblib.so", RTLD_NOW);
assert(lib_handle != NULL);
jumpfunc jumpy = (jumpfunc)dlsym(lib_handle, "jumpy");
assert(jumpy);
jmp_buf buf;
int jmpval = setjmp(buf);
if (jmpval == 0) {
while (1) jumpy(buf);
} else {
printf("out!\n");
}
return 0;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_free'])
self.do_run(src, '''go!
pre 1
pre 2
pre 3
pre 4
pre 5
pre 6
pre 7
pre 8
pre 9
out!
''', force_c=True)
@needs_dlfcn
def zzztest_dlfcn_exceptions(self): # TODO: make this work. need to forward tempRet0 across modules
self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
self.prep_dlfcn_lib()
lib_src = r'''
extern "C" {
int ok() {
return 65;
}
int fail() {
throw 123;
}
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_ok', '_fail'])
dirname = self.get_dir()
filename = os.path.join(dirname, 'liblib.cpp')
self.build_dlfcn_lib(lib_src, dirname, filename)
self.prep_dlfcn_main()
src = r'''
#include <assert.h>
#include <stdio.h>
#include <dlfcn.h>
typedef int (*intfunc)();
int main() {
printf("go!\n");
void *lib_handle;
lib_handle = dlopen("liblib.so", RTLD_NOW);
assert(lib_handle != NULL);
intfunc okk = (intfunc)dlsym(lib_handle, "ok");
intfunc faill = (intfunc)dlsym(lib_handle, "fail");
assert(okk && faill);
try {
printf("ok: %d\n", okk());
} catch(...) {
printf("wha\n");
}
try {
printf("fail: %d\n", faill());
} catch(int x) {
printf("int %d\n", x);
}
try {
printf("fail: %d\n", faill());
} catch(double x) {
printf("caught %f\n", x);
}
return 0;
}
'''
self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_free'])
self.do_run(src, '''go!
ok: 65
int 123
ok
''')
@needs_dlfcn
def test_dlfcn_handle_alloc(self):
# verify that dlopen does not allocate already used handles
dirname = self.get_dir()
def indir(name):
return os.path.join(dirname, name)
libecho = r'''
#include <stdio.h>
static struct %(libname)s {
%(libname)s() {
puts("%(libname)s: loaded");
}
} _;
'''
self.prep_dlfcn_lib()
self.build_dlfcn_lib(libecho % {'libname': 'a'}, dirname, indir('a.cpp'))
shutil.move(indir('liblib.so'), indir('liba.so'))
self.build_dlfcn_lib(libecho % {'libname': 'b'}, dirname, indir('b.cpp'))
shutil.move(indir('liblib.so'), indir('libb.so'))
self.set_setting('MAIN_MODULE')
self.clear_setting('SIDE_MODULE')
self.set_setting('EXPORT_ALL')
self.emcc_args += ['--embed-file', '.@/']
# XXX in wasm each lib load currently takes 5MB; default TOTAL_MEMORY=16MB is thus not enough
self.set_setting('TOTAL_MEMORY', 32 * 1024 * 1024)
src = r'''
#include <dlfcn.h>
#include <assert.h>
#include <stddef.h>
int main() {
void *liba, *libb, *liba2;
int err;
liba = dlopen("liba.so", RTLD_NOW);
assert(liba != NULL);
libb = dlopen("libb.so", RTLD_NOW);
assert(liba != NULL);
err = dlclose(liba);
assert(!err);
liba2 = dlopen("liba.so", RTLD_NOW);
assert(liba2 != libb);
return 0;
}
'''
self.do_run(src, 'a: loaded\nb: loaded\na: loaded\n')
def dylink_test(self, main, side, expected, header=None, main_emcc_args=[], force_c=False, need_reverse=True, auto_load=True):
# shared settings
self.set_setting('EXPORT_ALL', 1)
if header:
create_test_file('header.h', header)
old_args = self.emcc_args[:]
# side settings
self.clear_setting('MAIN_MODULE')
self.set_setting('SIDE_MODULE')
print(self.is_wasm())
side_suffix = 'wasm' if self.is_wasm() else 'js'
if isinstance(side, list):
# side is just a library
try_delete('liblib.cpp.o.' + side_suffix)
run_process([PYTHON, EMCC] + side + self.emcc_args + self.serialize_settings() + ['-o', os.path.join(self.get_dir(), 'liblib.cpp.o.' + side_suffix)])
else:
base = 'liblib.cpp' if not force_c else 'liblib.c'
try_delete(base + '.o.' + side_suffix)
self.build(side, self.get_dir(), base, js_outfile=(side_suffix == 'js'))
if force_c:
shutil.move(base + '.o.' + side_suffix, 'liblib.cpp.o.' + side_suffix)
if SPIDERMONKEY_ENGINE and os.path.exists(SPIDERMONKEY_ENGINE[0]) and not self.is_wasm():
out = run_js('liblib.cpp.o.js', engine=SPIDERMONKEY_ENGINE, full_output=True, stderr=STDOUT)
if 'asm' in out:
self.validate_asmjs(out)
shutil.move('liblib.cpp.o.' + side_suffix, 'liblib.so')
# main settings
self.set_setting('MAIN_MODULE')
self.clear_setting('SIDE_MODULE')
if auto_load:
self.set_setting('RUNTIME_LINKED_LIBS', ['liblib.so'])
self.emcc_args += main_emcc_args
if isinstance(main, list):
# main is just a library
try_delete('src.cpp.o.js')
run_process([PYTHON, EMCC] + main + self.emcc_args + self.serialize_settings() + ['-o', 'src.cpp.o.js'])
self.do_run(None, expected, no_build=True)
else:
self.do_run(main, expected, force_c=force_c)
self.emcc_args = old_args
if need_reverse:
# test the reverse as well
print('flip')
self.dylink_test(side, main, expected, header, main_emcc_args, force_c, need_reverse=False)
@needs_dlfcn
def test_dylink_basics(self):
def test():
self.dylink_test('''
#include <stdio.h>
extern int sidey();
int main() {
printf("other says %d.\\n", sidey());
return 0;
}
''', '''
int sidey() { return 11; }
''', 'other says 11.')
test()
if self.is_wasm():
print('test memory growth with dynamic linking, which works in wasm')
self.set_setting('ALLOW_MEMORY_GROWTH', 1)
test()
@needs_dlfcn
def test_dylink_floats(self):
self.dylink_test('''
#include <stdio.h>
extern float sidey();
int main() {
printf("other says %.2f.\\n", sidey()+1);
return 0;
}
''', '''
float sidey() { return 11.5; }
''', 'other says 12.50')
@needs_dlfcn
def test_dylink_printfs(self):
self.dylink_test(r'''
#include <stdio.h>
extern void sidey();
int main() {
printf("hello from main\n");
sidey();
return 0;
}
''', r'''
#include <stdio.h>
void sidey() { printf("hello from side\n"); }
''', 'hello from main\nhello from side\n')
@needs_dlfcn
def test_dylink_funcpointer(self):
self.dylink_test(r'''
#include <stdio.h>
#include "header.h"
voidfunc sidey(voidfunc f);
void a() { printf("hello from funcptr\n"); }
int main() {
sidey(a)();
return 0;
}
''', '''
#include "header.h"
voidfunc sidey(voidfunc f) { return f; }
''', 'hello from funcptr\n', header='typedef void (*voidfunc)();')
@needs_dlfcn
def test_dylink_funcpointers(self):
self.dylink_test(r'''
#include <stdio.h>
#include "header.h"
int sidey(voidfunc f);
void areturn0() { printf("hello 0\n"); }
void areturn1() { printf("hello 1\n"); }
void areturn2() { printf("hello 2\n"); }
int main(int argc, char **argv) {
voidfunc table[3] = { areturn0, areturn1, areturn2 };
table[sidey(NULL)]();
return 0;
}
''', '''
#include "header.h"
int sidey(voidfunc f) { if (f) f(); return 1; }
''', 'hello 1\n', header='typedef void (*voidfunc)();')
@no_wasm # uses function tables in an asm.js specific way
@needs_dlfcn
def test_dylink_funcpointers2(self):
self.dylink_test(r'''
#include "header.h"
#include <emscripten.h>
void left1() { printf("left1\n"); }
void left2() { printf("left2\n"); }
voidfunc getleft1() { return left1; }
voidfunc getleft2() { return left2; }
int main(int argc, char **argv) {
printf("main\n");
EM_ASM({
// make the function table sizes a non-power-of-two
alignFunctionTables();
Module['FUNCTION_TABLE_v'].push(0, 0, 0, 0, 0);
var newSize = alignFunctionTables();
//out('new size of function tables: ' + newSize);
// when masked, the two function pointers 1 and 2 should not happen to fall back to the right place
assert(((newSize+1) & 3) !== 1 || ((newSize+2) & 3) !== 2);
loadDynamicLibrary('liblib.so');
});
volatilevoidfunc f;
f = (volatilevoidfunc)left1;
f();
f = (volatilevoidfunc)left2;
f();
f = (volatilevoidfunc)getright1();
f();
f = (volatilevoidfunc)getright2();
f();
second();
return 0;
}
''', r'''
#include "header.h"
void right1() { printf("right1\n"); }
void right2() { printf("right2\n"); }
voidfunc getright1() { return right1; }
voidfunc getright2() { return right2; }
void second() {
printf("second\n");
volatilevoidfunc f;
f = (volatilevoidfunc)getleft1();
f();
f = (volatilevoidfunc)getleft2();
f();
f = (volatilevoidfunc)right1;
f();
f = (volatilevoidfunc)right2;
f();
}
''', 'main\nleft1\nleft2\nright1\nright2\nsecond\nleft1\nleft2\nright1\nright2\n', header='''
#include <stdio.h>
typedef void (*voidfunc)();
typedef volatile voidfunc volatilevoidfunc;
voidfunc getleft1();
voidfunc getleft2();
voidfunc getright1();
voidfunc getright2();
void second();
''', need_reverse=False, auto_load=False)
@needs_dlfcn
def test_dylink_funcpointers_wrapper(self):
self.dylink_test(r'''
#include <stdio.h>
#include "header.h"
int main(int argc, char **argv) {
volatile charfunc f = emscripten_run_script;
f("out('one')");
f = get();
f("out('two')");
return 0;
}
''', '''
#include "header.h"
charfunc get() {
return emscripten_run_script;
}
''', 'one\ntwo\n', header='''
#include <emscripten.h>
typedef void (*charfunc)(const char*);
extern charfunc get();
''')
@needs_dlfcn
def test_dylink_funcpointers_float(self):
# avoid using asm2wasm imports, which don't work in side modules yet (should they?)
self.set_setting('BINARYEN_TRAP_MODE', 'clamp')
self.dylink_test(r'''
#include <stdio.h>
#include "header.h"
int sidey(floatfunc f);
float areturn0(float f) { printf("hello 0: %f\n", f); return 0; }
float areturn1(float f) { printf("hello 1: %f\n", f); return 1; }
float areturn2(float f) { printf("hello 2: %f\n", f); return 2; }
int main(int argc, char **argv) {
volatile floatfunc table[3] = { areturn0, areturn1, areturn2 };
printf("got: %d\n", (int)table[sidey(NULL)](12.34));
return 0;
}
''', '''
#include "header.h"
int sidey(floatfunc f) { if (f) f(56.78); return 1; }
''', 'hello 1: 12.340000\ngot: 1\n', header='typedef float (*floatfunc)(float);')
@needs_dlfcn
def test_dylink_global_init(self):
self.dylink_test(r'''
#include <stdio.h>
struct Class {
Class() { printf("a new Class\n"); }
};
static Class c;
int main() {
return 0;
}
''', r'''
void nothing() {}
''', 'a new Class\n')
@needs_dlfcn
def test_dylink_global_inits(self):
def test():
self.dylink_test(header=r'''
#include <stdio.h>
struct Class {
Class(const char *name) { printf("new %s\n", name); }
};
''', main=r'''
#include "header.h"
static Class c("main");
int main() {
return 0;
}
''', side=r'''
#include "header.h"
static Class c("side");
''', expected=['new main\nnew side\n', 'new side\nnew main\n'])
test()
# TODO: this in wasm
if self.get_setting('ASSERTIONS') == 1 and not self.is_wasm():
print('check warnings')
self.set_setting('ASSERTIONS', 2)
test()
full = run_js('src.cpp.o.js', engine=JS_ENGINES[0], full_output=True, stderr=STDOUT)
self.assertNotContained("trying to dynamically load symbol '__ZN5ClassC2EPKc' (from 'liblib.so') that already exists", full)
@needs_dlfcn
def test_dylink_i64(self):
self.dylink_test('''
#include <stdio.h>
#include <stdint.h>
extern int64_t sidey();
int main() {
printf("other says %llx.\\n", sidey());
return 0;
}
''', '''
#include <stdint.h>
int64_t sidey() {
volatile int64_t x = 11;
x = x * x * x * x;
x += x % 17;
x += (x * (1 << 30));
x -= 96;
x = (x + 1000) / ((x % 5) + 1);
volatile uint64_t y = x / 2;
x = y / 3;
y = y * y * y * y;
y += y % 17;
y += (y * (1 << 30));
y -= 121;
y = (y + 1000) / ((y % 5) + 1);
x += y;
return x;
}
''', 'other says 175a1ddee82b8c31.')
@needs_dlfcn
def test_dylink_i64_b(self):
self.dylink_test(r'''
#include <stdio.h>
#include <stdint.h>
extern int64_t sidey();
int main() {
printf("other says %lld.\n", sidey());
return 0;
}
''', '''
#include <stdint.h>
int64_t sidey() {
volatile int64_t x = 0x12345678abcdef12LL;
x += x % 17;
x = 18 - x;
return x;
}
''', 'other says -1311768467750121224.')
@needs_dlfcn
def test_dylink_class(self):
self.dylink_test(header=r'''
#include <stdio.h>
struct Class {
Class(const char *name);
};
''', main=r'''
#include "header.h"
int main() {
Class c("main");
return 0;
}
''', side=r'''
#include "header.h"
Class::Class(const char *name) { printf("new %s\n", name); }
''', expected=['new main\n'])
@needs_dlfcn
def test_dylink_global_var(self):
self.dylink_test(main=r'''
#include <stdio.h>
extern int x;
int main() {
printf("extern is %d.\n", x);
return 0;
}
''', side=r'''
int x = 123;
''', expected=['extern is 123.\n'])
@needs_dlfcn
def test_dylink_global_var_modded(self):