diff options
213 files changed, 5633 insertions, 1735 deletions
diff --git a/HOWTO/DTRACE.md b/HOWTO/DTRACE.md index 8fa2fd9d50..90f4addefd 100644 --- a/HOWTO/DTRACE.md +++ b/HOWTO/DTRACE.md @@ -27,12 +27,10 @@ Goals Supported platforms ------------------- -* OS X 10.6.x / Snow Leopard. It should also work for 10.7 / Lion, - but I haven't personally tested it. +* OS X 10.6.x / Snow Leopard, OS X 10.7.x / Lion and probably newer versions. * Solaris 10. I have done limited testing on Solaris 11 and OpenIndiana release 151a, and both appear to work. -* FreeBSD 9.0, though please see the "FreeBSD 9.0 Release Notes" - section below! +* FreeBSD 9.0 and 10.0. * Linux via SystemTap compatibility. Please see [$ERL_TOP/HOWTO/SYSTEMTAP.md][] for more details. diff --git a/Makefile.in b/Makefile.in index d1fd64632c..d3d81c8817 100644 --- a/Makefile.in +++ b/Makefile.in @@ -992,10 +992,13 @@ $(IBIN_DIR): clean: check_recreate_primary_bootstrap rm -f *~ *.bak config.log config.status prebuilt.files ibin/* - find . -type f -name SKIP -print | xargs $(RM) cd erts && ERL_TOP=$(ERL_TOP) $(MAKE) clean cd lib && ERL_TOP=$(ERL_TOP) $(MAKE) clean BUILD_ALL=true +distclean: clean + find . -type f -name SKIP -print | xargs $(RM) + find . -type f -name SKIP-APPLICATIONS -print | xargs $(RM) + # # Just wipe out emulator, not libraries # diff --git a/bootstrap/lib/compiler/ebin/beam_bsm.beam b/bootstrap/lib/compiler/ebin/beam_bsm.beam Binary files differindex e50bb669cf..1b51483bb1 100644 --- a/bootstrap/lib/compiler/ebin/beam_bsm.beam +++ b/bootstrap/lib/compiler/ebin/beam_bsm.beam diff --git a/bootstrap/lib/compiler/ebin/beam_disasm.beam b/bootstrap/lib/compiler/ebin/beam_disasm.beam Binary files differindex c19e467785..2b645cdf75 100644 --- a/bootstrap/lib/compiler/ebin/beam_disasm.beam +++ b/bootstrap/lib/compiler/ebin/beam_disasm.beam diff --git a/bootstrap/lib/compiler/ebin/beam_validator.beam b/bootstrap/lib/compiler/ebin/beam_validator.beam Binary files differindex 9f154f58b8..8c685feb27 100644 --- a/bootstrap/lib/compiler/ebin/beam_validator.beam +++ b/bootstrap/lib/compiler/ebin/beam_validator.beam diff --git a/bootstrap/lib/compiler/ebin/compile.beam b/bootstrap/lib/compiler/ebin/compile.beam Binary files differindex 5dcacbacc8..1aa96eb292 100644 --- a/bootstrap/lib/compiler/ebin/compile.beam +++ b/bootstrap/lib/compiler/ebin/compile.beam diff --git a/bootstrap/lib/compiler/ebin/core_lib.beam b/bootstrap/lib/compiler/ebin/core_lib.beam Binary files differindex 06edfdc396..4957fbabb7 100644 --- a/bootstrap/lib/compiler/ebin/core_lib.beam +++ b/bootstrap/lib/compiler/ebin/core_lib.beam diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold.beam b/bootstrap/lib/compiler/ebin/sys_core_fold.beam Binary files differindex b8cc09084d..4cfa4db1d5 100644 --- a/bootstrap/lib/compiler/ebin/sys_core_fold.beam +++ b/bootstrap/lib/compiler/ebin/sys_core_fold.beam diff --git a/bootstrap/lib/compiler/ebin/v3_codegen.beam b/bootstrap/lib/compiler/ebin/v3_codegen.beam Binary files differindex f371e3c171..bd2c8f3c3d 100644 --- a/bootstrap/lib/compiler/ebin/v3_codegen.beam +++ b/bootstrap/lib/compiler/ebin/v3_codegen.beam diff --git a/bootstrap/lib/compiler/ebin/v3_core.beam b/bootstrap/lib/compiler/ebin/v3_core.beam Binary files differindex 5d81b6ba31..52a1086004 100644 --- a/bootstrap/lib/compiler/ebin/v3_core.beam +++ b/bootstrap/lib/compiler/ebin/v3_core.beam diff --git a/bootstrap/lib/compiler/ebin/v3_kernel.beam b/bootstrap/lib/compiler/ebin/v3_kernel.beam Binary files differindex 6896fe5421..fd7df78757 100644 --- a/bootstrap/lib/compiler/ebin/v3_kernel.beam +++ b/bootstrap/lib/compiler/ebin/v3_kernel.beam diff --git a/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam b/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam Binary files differindex 907abff30d..1af163078c 100644 --- a/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam +++ b/bootstrap/lib/compiler/ebin/v3_kernel_pp.beam diff --git a/bootstrap/lib/compiler/ebin/v3_life.beam b/bootstrap/lib/compiler/ebin/v3_life.beam Binary files differindex 3934425ad9..c2363fc62d 100644 --- a/bootstrap/lib/compiler/ebin/v3_life.beam +++ b/bootstrap/lib/compiler/ebin/v3_life.beam diff --git a/bootstrap/lib/kernel/ebin/application.beam b/bootstrap/lib/kernel/ebin/application.beam Binary files differindex e39352117f..2c8444fb95 100644 --- a/bootstrap/lib/kernel/ebin/application.beam +++ b/bootstrap/lib/kernel/ebin/application.beam diff --git a/bootstrap/lib/kernel/ebin/application_controller.beam b/bootstrap/lib/kernel/ebin/application_controller.beam Binary files differindex 590b9a54b0..659f29e35e 100644 --- a/bootstrap/lib/kernel/ebin/application_controller.beam +++ b/bootstrap/lib/kernel/ebin/application_controller.beam diff --git a/bootstrap/lib/stdlib/ebin/dets_v8.beam b/bootstrap/lib/stdlib/ebin/dets_v8.beam Binary files differindex 87e3caec1e..eac0a308b7 100644 --- a/bootstrap/lib/stdlib/ebin/dets_v8.beam +++ b/bootstrap/lib/stdlib/ebin/dets_v8.beam diff --git a/bootstrap/lib/stdlib/ebin/edlin_expand.beam b/bootstrap/lib/stdlib/ebin/edlin_expand.beam Binary files differindex 85ce43d1ed..269cf1f2f7 100644 --- a/bootstrap/lib/stdlib/ebin/edlin_expand.beam +++ b/bootstrap/lib/stdlib/ebin/edlin_expand.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_eval.beam b/bootstrap/lib/stdlib/ebin/erl_eval.beam Binary files differindex 19fadc9585..9c97f56e00 100644 --- a/bootstrap/lib/stdlib/ebin/erl_eval.beam +++ b/bootstrap/lib/stdlib/ebin/erl_eval.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam Binary files differindex c8f45fd838..0e72af0e35 100644 --- a/bootstrap/lib/stdlib/ebin/erl_expand_records.beam +++ b/bootstrap/lib/stdlib/ebin/erl_expand_records.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_lint.beam b/bootstrap/lib/stdlib/ebin/erl_lint.beam Binary files differindex 289ff4c812..301785992c 100644 --- a/bootstrap/lib/stdlib/ebin/erl_lint.beam +++ b/bootstrap/lib/stdlib/ebin/erl_lint.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_pp.beam b/bootstrap/lib/stdlib/ebin/erl_pp.beam Binary files differindex f699719358..683a07fbd5 100644 --- a/bootstrap/lib/stdlib/ebin/erl_pp.beam +++ b/bootstrap/lib/stdlib/ebin/erl_pp.beam diff --git a/bootstrap/lib/stdlib/ebin/file_sorter.beam b/bootstrap/lib/stdlib/ebin/file_sorter.beam Binary files differindex 1c0353488e..b5844d5ddb 100644 --- a/bootstrap/lib/stdlib/ebin/file_sorter.beam +++ b/bootstrap/lib/stdlib/ebin/file_sorter.beam diff --git a/bootstrap/lib/stdlib/ebin/lists.beam b/bootstrap/lib/stdlib/ebin/lists.beam Binary files differindex a44b412d23..96eb8f3a86 100644 --- a/bootstrap/lib/stdlib/ebin/lists.beam +++ b/bootstrap/lib/stdlib/ebin/lists.beam diff --git a/bootstrap/lib/stdlib/ebin/qlc_pt.beam b/bootstrap/lib/stdlib/ebin/qlc_pt.beam Binary files differindex 5c89047e67..f53c840702 100644 --- a/bootstrap/lib/stdlib/ebin/qlc_pt.beam +++ b/bootstrap/lib/stdlib/ebin/qlc_pt.beam diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam Binary files differindex d1d1131c06..5fe8747341 100644 --- a/bootstrap/lib/stdlib/ebin/supervisor.beam +++ b/bootstrap/lib/stdlib/ebin/supervisor.beam diff --git a/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam b/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam Binary files differindex 6480117b9e..54d7385738 100644 --- a/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam +++ b/bootstrap/lib/stdlib/ebin/supervisor_bridge.beam diff --git a/erts/configure.in b/erts/configure.in index 8d245252b5..c20bb032f1 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -3848,28 +3848,26 @@ esac if test "$enable_dtrace_test" = "yes" ; then if test "$DTRACE" = "dtrace" ; then AC_CHECK_HEADERS(sys/sdt.h) + AC_MSG_CHECKING([for 1-stage DTrace precompilation]) # The OS X version of dtrace prints a spurious line here. if ! dtrace -h $DTRACE_CPP -Iemulator/beam -o ./foo-dtrace.h -s emulator/beam/erlang_dtrace.d; then AC_MSG_ERROR([Could not precompile erlang_dtrace.d: dtrace -h failed]) fi + AC_MSG_RESULT([yes]) - $RM -f dtest.{o,c} - cat > dtest.c <<_DTEST - #include "foo-dtrace.h" - int main(void) { ERLANG_DIST_PORT_BUSY_ENABLED(); return 0; } -_DTEST - $CC $CFLAGS -c -o dtest.o dtest.c - - $RM -f $DTRACE_2STEP_TEST - if dtrace -G $DTRACE_CPP $DTRACE_BITS_FLAG -Iemulator/beam -o $DTRACE_2STEP_TEST -s emulator/beam/erlang_dtrace.d dtest.o && \ - test -f $DTRACE_2STEP_TEST ; then - rm $DTRACE_2STEP_TEST + AC_MSG_CHECKING([for 2-stage DTrace precompilation]) + AC_TRY_COMPILE([ #include "foo-dtrace.h" ], + [ERLANG_DIST_PORT_BUSY_ENABLED();], + [$RM -f $DTRACE_2STEP_TEST + dtrace -G $DTRACE_CPP $DTRACE_BITS_FLAG -Iemulator/beam -o $DTRACE_2STEP_TEST -s emulator/beam/erlang_dtrace.d conftest.$OBJEXT 2>&AS_MESSAGE_LOG_FD + if test -f $DTRACE_2STEP_TEST; then + rm $DTRACE_2STEP_TEST DTRACE_ENABLED_2STEP=yes - AC_MSG_NOTICE([dtrace precompilation for 2-stage DTrace successful]) - else - AC_MSG_NOTICE([dtrace precompilation for 1-stage DTrace successful]) - fi - $RM -f dtest.{o,c} foo-dtrace.h + fi], + []) + AS_IF([test "x$DTRACE_ENABLED_2STEP" = "xyes"], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no])]) DTRACE_ENABLED=yes case $OPSYS in diff --git a/erts/doc/src/absform.xml b/erts/doc/src/absform.xml index 4acc03b133..835a4fc692 100644 --- a/erts/doc/src/absform.xml +++ b/erts/doc/src/absform.xml @@ -217,6 +217,14 @@ Rep(E) = <c><![CDATA[{record_index,LINE,Name,Rep(Field)}]]></c>.</item> <item>If E is <c><![CDATA[E_0#Name.Field]]></c>, then Rep(E) = <c><![CDATA[{record_field,LINE,Rep(E_0),Name,Rep(Field)}]]></c>.</item> + <item>If E is <c><![CDATA[#{W_1, ..., W_k}]]></c> where each + <c><![CDATA[W_i]]></c> is a map assoc or exact field, then Rep(E) = + <c><![CDATA[{map,LINE,[Rep(W_1), ..., Rep(W_k)]}]]></c>. For Rep(W), see + below.</item> + <item>If E is <c><![CDATA[E_0#{W_1, ..., W_k}]]></c> where + <c><![CDATA[W_i]]></c> is a map assoc or exact field, then Rep(E) = + <c><![CDATA[{map,LINE,Rep(E_0),[Rep(W_1), ..., Rep(W_k)]}]]></c>. For + Rep(W), see below.</item> <item>If E is <c><![CDATA[catch E_0]]></c>, then Rep(E) = <c><![CDATA[{'catch',LINE,Rep(E_0)}]]></c>.</item> <item>If E is <c><![CDATA[E_0(E_1, ..., E_k)]]></c>, then @@ -334,6 +342,21 @@ is an integer, Rep(TS) = <c><![CDATA[{A, Value}]]></c>.</item> </list> </section> + + <section> + <title>Map assoc and exact fields</title> + <p>When W is an assoc or exact field (in the body of a map), then:</p> + <list type="bulleted"> + <item>If W is an assoc field <c><![CDATA[K => V]]></c>, where + <c><![CDATA[K]]></c> and <c><![CDATA[V]]></c> are both expressions, + then Rep(W) = <c><![CDATA[{map_field_assoc,LINE,Rep(K),Rep(V)}]]></c>. + </item> + <item>If W is an exact field <c><![CDATA[K := V]]></c>, where + <c><![CDATA[K]]></c> and <c><![CDATA[V]]></c> are both expressions, + then Rep(W) = <c><![CDATA[{map_field_exact,LINE,Rep(K),Rep(V)}]]></c>. + </item> + </list> + </section> </section> <section> diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index f88a4ccc24..74e0d26cff 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -513,10 +513,9 @@ ifdef DTRACE_ENABLED # global.h causes problems by including dtrace-wrapper.h which includes # the autogenerated erlang_dtrace.h ... so make erlang_dtrace.h very early. DTRACE_HEADERS = $(TARGET)/erlang_dtrace.h -generate: $(DTRACE_HEADERS) $(GENERATE) +GENERATE+= $(DTRACE_HEADERS) else DTRACE_HEADERS = -generate: $(GENERATE) endif ifdef HIPE_ENABLED diff --git a/erts/emulator/beam/beam_emu.c b/erts/emulator/beam/beam_emu.c index 89d9442526..1ae413d46e 100644 --- a/erts/emulator/beam/beam_emu.c +++ b/erts/emulator/beam/beam_emu.c @@ -2355,7 +2355,7 @@ void process_main(void) Next(4+Arg(3)); } - OpCase(update_map_assoc_jddII): { + OpCase(update_map_assoc_jsdII): { Eterm res; Eterm map; @@ -2373,7 +2373,7 @@ void process_main(void) } } - OpCase(update_map_exact_jddII): { + OpCase(update_map_exact_jsdII): { Eterm res; Eterm map; @@ -6614,7 +6614,7 @@ update_map_exact(Process* p, Eterm* reg, Eterm map, BeamInstr* I) */ if (num_old == 0) { - return new_map(p, reg, I+1); + return THE_NON_VALUE; } /* diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c index b589d1c930..915af7fbb1 100644 --- a/erts/emulator/beam/beam_load.c +++ b/erts/emulator/beam/beam_load.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1996-2013. All Rights Reserved. + * Copyright Ericsson AB 1996-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -409,7 +409,7 @@ typedef struct LoaderState { __result = __result << 8 | *Stp->file_p++; \ } \ Dest = __result; \ - } while (0) + } #define GetByte(Stp, Dest) \ if ((Stp)->file_left < 1) { \ diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c index ca7e39041b..64b3e01ed4 100644 --- a/erts/emulator/beam/erl_alloc.c +++ b/erts/emulator/beam/erl_alloc.c @@ -629,8 +629,21 @@ erts_alloc_init(int *argc, char **argv, ErtsAllocInitOpts *eaiop) init.fix_alloc.thr_spec = 0; #endif + /* Make adjustments for carrier migration support */ + init.temp_alloc.init.util.acul = 0; + adjust_carrier_migration_support(&init.sl_alloc); + adjust_carrier_migration_support(&init.std_alloc); + adjust_carrier_migration_support(&init.ll_alloc); + adjust_carrier_migration_support(&init.eheap_alloc); + adjust_carrier_migration_support(&init.binary_alloc); + adjust_carrier_migration_support(&init.ets_alloc); + adjust_carrier_migration_support(&init.driver_alloc); + adjust_carrier_migration_support(&init.fix_alloc); + if (init.erts_alloc_config) { /* Adjust flags that erts_alloc_config won't like */ + + /* No thread specific instances */ init.temp_alloc.thr_spec = 0; init.sl_alloc.thr_spec = 0; init.std_alloc.thr_spec = 0; @@ -639,20 +652,20 @@ erts_alloc_init(int *argc, char **argv, ErtsAllocInitOpts *eaiop) init.binary_alloc.thr_spec = 0; init.ets_alloc.thr_spec = 0; init.driver_alloc.thr_spec = 0; - init.fix_alloc.thr_spec = 0; + init.fix_alloc.thr_spec = 0; + + /* No carrier migration */ + init.temp_alloc.init.util.acul = 0; + init.sl_alloc.init.util.acul = 0; + init.std_alloc.init.util.acul = 0; + init.ll_alloc.init.util.acul = 0; + init.eheap_alloc.init.util.acul = 0; + init.binary_alloc.init.util.acul = 0; + init.ets_alloc.init.util.acul = 0; + init.driver_alloc.init.util.acul = 0; + init.fix_alloc.init.util.acul = 0; } - /* Make adjustments for carrier migration support */ - init.temp_alloc.init.util.acul = 0; - adjust_carrier_migration_support(&init.sl_alloc); - adjust_carrier_migration_support(&init.std_alloc); - adjust_carrier_migration_support(&init.ll_alloc); - adjust_carrier_migration_support(&init.eheap_alloc); - adjust_carrier_migration_support(&init.binary_alloc); - adjust_carrier_migration_support(&init.ets_alloc); - adjust_carrier_migration_support(&init.driver_alloc); - adjust_carrier_migration_support(&init.fix_alloc); - #ifdef ERTS_SMP /* Only temp_alloc can use thread specific interface */ if (init.temp_alloc.thr_spec) diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c index 41e64fcd4f..a5d67571e2 100644 --- a/erts/emulator/beam/erl_db.c +++ b/erts/emulator/beam/erl_db.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1996-2013. All Rights Reserved. + * Copyright Ericsson AB 1996-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -125,6 +125,7 @@ get_meta_main_tab_lock(unsigned slot) static erts_smp_spinlock_t meta_main_tab_main_lock; static Uint meta_main_tab_first_free; /* Index of first free slot */ static int meta_main_tab_cnt; /* Number of active tables */ +static int meta_main_tab_top; /* Highest ever used slot + 1 */ static Uint meta_main_tab_slot_mask; /* The slot index part of an unnamed table id */ static Uint meta_main_tab_seq_incr; static Uint meta_main_tab_seq_cnt = 0; /* To give unique(-ish) table identifiers */ @@ -1469,6 +1470,10 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2) ASSERT(slot>=0 && slot<db_max_tabs); meta_main_tab_first_free = GET_NEXT_FREE_SLOT(slot); meta_main_tab_cnt++; + if (slot >= meta_main_tab_top) { + ASSERT(slot == meta_main_tab_top); + meta_main_tab_top = slot + 1; + } if (is_named) { ret = BIF_ARG_1; @@ -2058,27 +2063,31 @@ BIF_RETTYPE ets_all_0(BIF_ALIST_0) { DbTable* tb; Eterm previous; - int i, j; + int i; Eterm* hp; Eterm* hendp; int t_tabs_cnt; - int t_max_tabs; + int t_top; erts_smp_spin_lock(&meta_main_tab_main_lock); t_tabs_cnt = meta_main_tab_cnt; - t_max_tabs = db_max_tabs; + t_top = meta_main_tab_top; erts_smp_spin_unlock(&meta_main_tab_main_lock); hp = HAlloc(BIF_P, 2*t_tabs_cnt); hendp = hp + 2*t_tabs_cnt; previous = NIL; - j = 0; - for(i = 0; (i < t_max_tabs && j < t_tabs_cnt); i++) { + for(i = 0; i < t_top; i++) { erts_smp_rwmtx_t *mmtl = get_meta_main_tab_lock(i); erts_smp_rwmtx_rlock(mmtl); if (IS_SLOT_ALIVE(i)) { - j++; + if (hp == hendp) { + /* Racing table creator, grab some more heap space */ + t_tabs_cnt = 10; + hp = HAlloc(BIF_P, 2*t_tabs_cnt); + hendp = hp + 2*t_tabs_cnt; + } tb = meta_main_tab[i].u.tb; previous = CONS(hp, tb->common.id, previous); hp += 2; @@ -2849,6 +2858,7 @@ void init_db(void) ERTS_ETS_MISC_MEM_ADD(size); meta_main_tab_cnt = 0; + meta_main_tab_top = 0; for (i=1; i<db_max_tabs; i++) { SET_NEXT_FREE_SLOT(i-1,i); } diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c index c35f1fc2c6..4126df6f34 100644 --- a/erts/emulator/beam/erl_nif.c +++ b/erts/emulator/beam/erl_nif.c @@ -1237,6 +1237,19 @@ static void steal_resource_type(ErlNifResourceType* type) } } +/* The opened_rt_list is used by enif_open_resource_type() + * in order to rollback "creates" and "take-overs" in case the load fails. + */ +struct opened_resource_type +{ + struct opened_resource_type* next; + + ErlNifResourceFlags op; + ErlNifResourceType* type; + ErlNifResourceDtor* new_dtor; +}; +static struct opened_resource_type* opened_rt_list = NULL; + ErlNifResourceType* enif_open_resource_type(ErlNifEnv* env, const char* module_str, @@ -1258,22 +1271,21 @@ enif_open_resource_type(ErlNifEnv* env, if (type == NULL) { if (flags & ERL_NIF_RT_CREATE) { type = erts_alloc(ERTS_ALC_T_NIF, - sizeof(struct enif_resource_type_t)); - type->dtor = dtor; + sizeof(struct enif_resource_type_t)); type->module = module_am; type->name = name_am; erts_refc_init(&type->refc, 1); - type->owner = env->mod_nif; - type->prev = &resource_type_list; - type->next = resource_type_list.next; - type->next->prev = type; - type->prev->next = type; op = ERL_NIF_RT_CREATE; + #ifdef DEBUG + type->dtor = (void*)1; + type->owner = (void*)2; + type->prev = (void*)3; + type->next = (void*)4; + #endif } } else { - if (flags & ERL_NIF_RT_TAKEOVER) { - steal_resource_type(type); + if (flags & ERL_NIF_RT_TAKEOVER) { op = ERL_NIF_RT_TAKEOVER; } else { @@ -1281,12 +1293,13 @@ enif_open_resource_type(ErlNifEnv* env, } } if (type != NULL) { - type->owner = env->mod_nif; - type->dtor = dtor; - if (type->dtor != NULL) { - erts_refc_inc(&type->owner->rt_dtor_cnt, 1); - } - erts_refc_inc(&type->owner->rt_cnt, 1); + struct opened_resource_type* ort = erts_alloc(ERTS_ALC_T_TMP, + sizeof(struct opened_resource_type)); + ort->op = op; + ort->type = type; + ort->new_dtor = dtor; + ort->next = opened_rt_list; + opened_rt_list = ort; } if (tried != NULL) { *tried = op; @@ -1294,6 +1307,51 @@ enif_open_resource_type(ErlNifEnv* env, return type; } +static void commit_opened_resource_types(struct erl_module_nif* lib) +{ + while (opened_rt_list) { + struct opened_resource_type* ort = opened_rt_list; + + ErlNifResourceType* type = ort->type; + + if (ort->op == ERL_NIF_RT_CREATE) { + type->prev = &resource_type_list; + type->next = resource_type_list.next; + type->next->prev = type; + type->prev->next = type; + } + else { /* ERL_NIF_RT_TAKEOVER */ + steal_resource_type(type); + } + + type->owner = lib; + type->dtor = ort->new_dtor; + + if (type->dtor != NULL) { + erts_refc_inc(&lib->rt_dtor_cnt, 1); + } + erts_refc_inc(&lib->rt_cnt, 1); + + opened_rt_list = ort->next; + erts_free(ERTS_ALC_T_TMP, ort); + } +} + +static void rollback_opened_resource_types(void) +{ + while (opened_rt_list) { + struct opened_resource_type* ort = opened_rt_list; + + if (ort->op == ERL_NIF_RT_CREATE) { + erts_free(ERTS_ALC_T_NIF, ort->type); + } + + opened_rt_list = ort->next; + erts_free(ERTS_ALC_T_TMP, ort); + } +} + + static void nif_resource_dtor(Binary* bin) { ErlNifResource* resource = (ErlNifResource*) ERTS_MAGIC_BIN_DATA(bin); @@ -1319,6 +1377,8 @@ void* enif_alloc_resource(ErlNifResourceType* type, size_t size) { Binary* bin = erts_create_magic_binary(SIZEOF_ErlNifResource(size), &nif_resource_dtor); ErlNifResource* resource = ERTS_MAGIC_BIN_DATA(bin); + + ASSERT(type->owner && type->next && type->prev); /* not allowed in load/upgrade */ resource->type = type; erts_refc_inc(&bin->refc, 1); #ifdef DEBUG @@ -2040,9 +2100,15 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) lib->entry = entry; erts_refc_init(&lib->rt_cnt, 0); erts_refc_init(&lib->rt_dtor_cnt, 0); + ASSERT(opened_rt_list == NULL); lib->mod = mod; env.mod_nif = lib; - if (mod->curr.nif != NULL) { /* Reload */ + if (mod->curr.nif != NULL) { /*************** Reload ******************/ + /* + * Repeated load_nif calls from same Erlang module instance ("reload") + * is deprecated and was only ment as a development feature not to + * be used in production systems. (See warning below) + */ int k; lib->priv_data = mod->curr.nif->priv_data; @@ -2074,6 +2140,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) ret = load_nif_error(BIF_P, reload, "Library reload-call unsuccessful."); } else { + commit_opened_resource_types(lib); mod->curr.nif->entry = NULL; /* to prevent 'unload' callback */ erts_unload_nif(mod->curr.nif); reload_warning = 1; @@ -2081,7 +2148,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) } else { lib->priv_data = NULL; - if (mod->old.nif != NULL) { /* Upgrade */ + if (mod->old.nif != NULL) { /**************** Upgrade ***************/ void* prev_old_data = mod->old.nif->priv_data; if (entry->upgrade == NULL) { ret = load_nif_error(BIF_P, upgrade, "Upgrade not supported by this NIF library."); @@ -2094,17 +2161,18 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) mod->old.nif->priv_data = prev_old_data; ret = load_nif_error(BIF_P, upgrade, "Library upgrade-call unsuccessful."); } - /*else if (mod->old_nif->priv_data != prev_old_data) { - refresh_cached_nif_data(mod->old_code, mod->old_nif); - }*/ + else + commit_opened_resource_types(lib); } - else if (entry->load != NULL) { /* Initial load */ + else if (entry->load != NULL) { /********* Initial load ***********/ erts_pre_nif(&env, BIF_P, lib); veto = entry->load(&env, &lib->priv_data, BIF_ARG_2); erts_post_nif(&env); if (veto) { ret = load_nif_error(BIF_P, "load", "Library load-call unsuccessful."); } + else + commit_opened_resource_types(lib); } } if (ret == am_ok) { @@ -2133,6 +2201,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2) } else { error: + rollback_opened_resource_types(); ASSERT(ret != am_ok); if (lib != NULL) { erts_free(ERTS_ALC_T_NIF, lib); diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c index a4cc3435c3..9fb2dbd8bf 100644 --- a/erts/emulator/beam/external.c +++ b/erts/emulator/beam/external.c @@ -1169,6 +1169,7 @@ typedef struct { Eterm* hp_end; int remaining_n; char* remaining_bytes; + Eterm* maps_head; } B2TDecodeContext; typedef struct { @@ -1486,6 +1487,7 @@ static Eterm binary_to_term_int(Process* p, Uint32 flags, Eterm bin, Binary* con ctx->u.dc.hp_start = HAlloc(p, ctx->heap_size); ctx->u.dc.hp = ctx->u.dc.hp_start; ctx->u.dc.hp_end = ctx->u.dc.hp_start + ctx->heap_size; + ctx->u.dc.maps_head = NULL; ctx->state = B2TDecode; /*fall through*/ case B2TDecode: @@ -2878,7 +2880,7 @@ dec_term(ErtsDistExternal *edep, Eterm** hpp, byte* ep, ErlOffHeap* off_heap, int n; ErtsAtomEncoding char_enc; register Eterm* hp; /* Please don't take the address of hp */ - Eterm *maps_head = NULL; /* for validation of maps */ + Eterm *maps_head; /* for validation of maps */ Eterm* next; SWord reds; @@ -2888,6 +2890,7 @@ dec_term(ErtsDistExternal *edep, Eterm** hpp, byte* ep, ErlOffHeap* off_heap, next = ctx->u.dc.next; ep = ctx->u.dc.ep; hpp = &ctx->u.dc.hp; + maps_head = ctx->u.dc.maps_head; if (ctx->state != B2TDecode) { int n_limit = reds; @@ -2968,6 +2971,7 @@ dec_term(ErtsDistExternal *edep, Eterm** hpp, byte* ep, ErlOffHeap* off_heap, reds = ERTS_SWORD_MAX; next = objp; *next = (Eterm) (UWord) NULL; + maps_head = NULL; } hp = *hpp; @@ -3780,6 +3784,7 @@ dec_term_atom_common: ctx->u.dc.ep = ep; ctx->u.dc.next = next; ctx->u.dc.hp = hp; + ctx->u.dc.maps_head = maps_head; ctx->reds = 0; return NULL; } diff --git a/erts/emulator/beam/ops.tab b/erts/emulator/beam/ops.tab index f35997efee..9ea3a3a8ea 100644 --- a/erts/emulator/beam/ops.tab +++ b/erts/emulator/beam/ops.tab @@ -1482,8 +1482,8 @@ put_map_exact F Src Dst Live Size Rest=* => \ update_map_exact F Src Dst Live Size Rest new_map j d I I -update_map_assoc j d d I I -update_map_exact j d d I I +update_map_assoc j s d I I +update_map_exact j s d I I is_map Fail cq => jump Fail diff --git a/erts/emulator/drivers/common/gzio.c b/erts/emulator/drivers/common/gzio.c index 653f3954b1..8ec2c3f762 100644 --- a/erts/emulator/drivers/common/gzio.c +++ b/erts/emulator/drivers/common/gzio.c @@ -73,15 +73,15 @@ typedef struct gz_stream { int transparent; /* 1 if input file is not a .gz file */ char mode; /* 'w' or 'r' */ int position; /* Position (for seek) */ - int (*destroy)OF((struct gz_stream*)); /* Function to destroy + int (*destroy)(struct gz_stream*); /* Function to destroy * this structure. */ } gz_stream; -local ErtsGzFile gz_open OF((const char *path, const char *mode)); -local int get_byte OF((gz_stream *s)); -local void check_header OF((gz_stream *s)); -local int destroy OF((gz_stream *s)); -local uLong getLong OF((gz_stream *s)); +local ErtsGzFile gz_open (const char *path, const char *mode); +local int get_byte (gz_stream *s); +local void check_header (gz_stream *s); +local int destroy (gz_stream *s); +local uLong getLong (gz_stream *s); #ifdef UNIX /* diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c index a5294ad84e..865cb50a56 100644 --- a/erts/emulator/sys/unix/sys.c +++ b/erts/emulator/sys/unix/sys.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1996-2013. All Rights Reserved. + * Copyright Ericsson AB 1996-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -2561,7 +2561,7 @@ void *erts_sys_aligned_alloc(UWord alignment, UWord size) #ifdef HAVE_POSIX_MEMALIGN void *ptr = NULL; int error; - ASSERT(alignment && (alignment & ~alignment) == 0); /* power of 2 */ + ASSERT(alignment && (alignment & (alignment-1)) == 0); /* power of 2 */ error = posix_memalign(&ptr, (size_t) alignment, (size_t) size); #if HAVE_ERTS_MSEG if (error || !ptr) { @@ -2584,7 +2584,7 @@ void *erts_sys_aligned_alloc(UWord alignment, UWord size) void erts_sys_aligned_free(UWord alignment, void *ptr) { - ASSERT(alignment && (alignment & ~alignment) == 0); /* power of 2 */ + ASSERT(alignment && (alignment & (alignment-1)) == 0); /* power of 2 */ free(ptr); } diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c index 5ea4703a7a..a11ac73ebc 100755 --- a/erts/emulator/sys/win32/sys.c +++ b/erts/emulator/sys/win32/sys.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1996-2013. All Rights Reserved. + * Copyright Ericsson AB 1996-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -2757,7 +2757,7 @@ void erts_sys_free(ErtsAlcType_t t, void *x, void *p) void *erts_sys_aligned_alloc(UWord alignment, UWord size) { void *ptr; - ASSERT(alignment && (alignment & ~alignment) == 0); /* power of 2 */ + ASSERT(alignment && (alignment & (alignment-1)) == 0); /* power of 2 */ ptr = _aligned_malloc((size_t) size, (size_t) alignment); ASSERT(!ptr || (((UWord) ptr) & (alignment - 1)) == 0); return ptr; @@ -2765,14 +2765,14 @@ void *erts_sys_aligned_alloc(UWord alignment, UWord size) void erts_sys_aligned_free(UWord alignment, void *ptr) { - ASSERT(alignment && (alignment & ~alignment) == 0); /* power of 2 */ + ASSERT(alignment && (alignment & (alignment-1)) == 0); /* power of 2 */ _aligned_free(ptr); } void *erts_sys_aligned_realloc(UWord alignment, void *ptr, UWord size, UWord old_size) { void *new_ptr; - ASSERT(alignment && (alignment & ~alignment) == 0); /* power of 2 */ + ASSERT(alignment && (alignment & (alignment-1)) == 0); /* power of 2 */ new_ptr = _aligned_realloc(ptr, (size_t) size, (size_t) alignment); ASSERT(!new_ptr || (((UWord) new_ptr) & (alignment - 1)) == 0); return new_ptr; diff --git a/erts/emulator/test/driver_SUITE.erl b/erts/emulator/test/driver_SUITE.erl index 06211406b4..c62bc0c454 100644 --- a/erts/emulator/test/driver_SUITE.erl +++ b/erts/emulator/test/driver_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2013. All Rights Reserved. +%% Copyright Ericsson AB 1997-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -1945,6 +1945,14 @@ otp_9302(Config) when is_list(Config) -> end. thr_free_drv(Config) when is_list(Config) -> + case erlang:system_info(threads) of + false -> + {skipped, "No thread support"}; + true -> + thr_free_drv_do(Config) + end. + +thr_free_drv_do(Config) -> ?line Path = ?config(data_dir, Config), ?line erl_ddll:start(), ?line ok = load_driver(Path, thr_free_drv), diff --git a/erts/emulator/test/driver_SUITE_data/otp_9302_drv.c b/erts/emulator/test/driver_SUITE_data/otp_9302_drv.c index 7c144d20cf..ad29d17f06 100644 --- a/erts/emulator/test/driver_SUITE_data/otp_9302_drv.c +++ b/erts/emulator/test/driver_SUITE_data/otp_9302_drv.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2011-2013. All Rights Reserved. + * Copyright Ericsson AB 2011-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -94,7 +94,7 @@ DRIVER_INIT(otp_9302_drv) static void stop(ErlDrvData drv_data) { Otp9302Data *data = (Otp9302Data *) drv_data; - if (!data->smp) + if (data->msgq.mtx) erl_drv_mutex_destroy(data->msgq.mtx); driver_free(data); } @@ -114,13 +114,16 @@ static ErlDrvData start(ErlDrvPort port, driver_system_info(&sys_info, sizeof(ErlDrvSysInfo)); data->smp = sys_info.smp_support; + data->msgq.mtx = NULL; if (!data->smp) { data->msgq.start = NULL; data->msgq.end = NULL; - data->msgq.mtx = erl_drv_mutex_create(""); - if (!data->msgq.mtx) { - driver_free(data); - return ERL_DRV_ERROR_GENERAL; + if (sys_info.thread_support) { + data->msgq.mtx = erl_drv_mutex_create(""); + if (!data->msgq.mtx) { + driver_free(data); + return ERL_DRV_ERROR_GENERAL; + } } } @@ -143,19 +146,22 @@ static void enqueue_reply(Otp9302AsyncData *adata) Otp9302MsgQ *msgq = adata->msgq; adata->next = NULL; adata->refc++; - erl_drv_mutex_lock(msgq->mtx); + if (msgq->mtx) + erl_drv_mutex_lock(msgq->mtx); if (msgq->end) msgq->end->next = adata; else msgq->end = msgq->start = adata; msgq->end = adata; - erl_drv_mutex_unlock(msgq->mtx); + if (msgq->mtx) + erl_drv_mutex_unlock(msgq->mtx); } static void dequeue_replies(Otp9302AsyncData *adata) { Otp9302MsgQ *msgq = adata->msgq; - erl_drv_mutex_lock(msgq->mtx); + if (msgq->mtx) + erl_drv_mutex_lock(msgq->mtx); if (--adata->refc == 0) driver_free(adata); while (msgq->start) { @@ -166,7 +172,8 @@ static void dequeue_replies(Otp9302AsyncData *adata) driver_free(adata); } msgq->start = msgq->end = NULL; - erl_drv_mutex_unlock(msgq->mtx); + if (msgq->mtx) + erl_drv_mutex_unlock(msgq->mtx); } static void async_invoke(void *data) diff --git a/erts/emulator/test/map_SUITE.erl b/erts/emulator/test/map_SUITE.erl index 31c1486f1c..8cc5621181 100644 --- a/erts/emulator/test/map_SUITE.erl +++ b/erts/emulator/test/map_SUITE.erl @@ -254,7 +254,15 @@ t_update_exact(Config) when is_list(Config) -> M2 = M0#{3.0:=new}, #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2, M2 = M0#{3.0=>wrong,3.0:=new}, - M2 = M0#{3=>wrong,3.0:=new}, + true = M2 =/= M0#{3=>right,3.0:=new}, + #{ 3 := right, 3.0 := new } = M0#{3=>right,3.0:=new}, + + M3 = id(#{ 1 => val}), + #{1 := update2,1.0 := new_val4} = M3#{ + 1.0 => new_val1, 1 := update, 1=> update3, + 1 := update2, 1.0 := new_val2, 1.0 => new_val3, + 1.0 => new_val4 }, + %% Errors cases. {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index bcc1f9e5af..a854d3f05b 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -850,6 +850,138 @@ resource_takeover(Config) when is_list(Config) -> ?line ok = forget_resource(AN4), ?line [] = nif_mod_call_history(), + + %% + %% Test rollback after failed upgrade of same lib-version + %% + + {A5,BinA5} = make_resource(2, Holder, "A5"), + {NA5,BinNA5} = make_resource(0, Holder, "NA5"), + {AN5,_BinAN5} = make_resource(1, Holder, "AN5"), + + {A6,BinA6} = make_resource(2, Holder, "A6"), + {NA6,BinNA6} = make_resource(0, Holder, "NA6"), + {AN6,_BinAN6} = make_resource(1, Holder, "AN6"), + + {module,nif_mod} = erlang:load_module(nif_mod,ModBin), + undefined = nif_mod:lib_version(), + {error,{upgrade,_}} = + nif_mod:load_nif_lib(Config, 1, + [{resource_type, 4, ?RT_TAKEOVER, "resource_type_A",resource_dtor_B, + ?RT_TAKEOVER}, + {resource_type, 4, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",null, + ?RT_TAKEOVER}, + {resource_type, 4, ?RT_TAKEOVER, "resource_type_A_null",resource_dtor_A, + ?RT_TAKEOVER}, + {resource_type, 4, ?RT_CREATE, "Mr Pink", resource_dtor_A, + ?RT_CREATE}, + + {return, 1} % FAIL + ]), + + undefined = nif_mod:lib_version(), + [{upgrade,1,5,105}] = nif_mod_call_history(), + + %% Make sure dtor was not changed (from A to B) + ok = forget_resource(A5), + [{{resource_dtor_A_v1,BinA5},1,6,106}] = nif_mod_call_history(), + + %% Make sure dtor was not nullified (from A to null) + ok = forget_resource(NA5), + [{{resource_dtor_A_v1,BinNA5},1,7,107}] = nif_mod_call_history(), + + %% Make sure dtor was not added (from null to A) + ok = forget_resource(AN5), + [] = nif_mod_call_history(), + + %% + %% Test rollback after failed upgrade of other lib-version + %% + + {error,{upgrade,_}} = + nif_mod:load_nif_lib(Config, 2, + [{resource_type, 4, ?RT_TAKEOVER, "resource_type_A",resource_dtor_B, + ?RT_TAKEOVER}, + {resource_type, 4, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",null, + ?RT_TAKEOVER}, + {resource_type, 4, ?RT_TAKEOVER, "resource_type_A_null",resource_dtor_A, + ?RT_TAKEOVER}, + {resource_type, null, ?RT_TAKEOVER, "Mr Pink", resource_dtor_A, + ?RT_TAKEOVER}, + {resource_type, 4, ?RT_CREATE, "Mr Pink", resource_dtor_A, + ?RT_CREATE}, + + {return, 1} % FAIL + ]), + + undefined = nif_mod:lib_version(), + [{upgrade,2,_,_}] = nif_mod_call_history(), + + %% Make sure dtor was not changed (from A to B) + ok = forget_resource(A6), + [{{resource_dtor_A_v1,BinA6},1,_,_}] = nif_mod_call_history(), + + %% Make sure dtor was not nullified (from A to null) + ok = forget_resource(NA6), + [{{resource_dtor_A_v1,BinNA6},1,_,_}] = nif_mod_call_history(), + + %% Make sure dtor was not added (from null to A) + ok = forget_resource(AN6), + [] = nif_mod_call_history(), + + %% + %% Test rolback after failed initial load + %% + false = code:purge(nif_mod), + [{unload,1,_,_}] = nif_mod_call_history(), + true = code:delete(nif_mod), + false = code:purge(nif_mod), + [] = nif_mod_call_history(), + + + {module,nif_mod} = erlang:load_module(nif_mod,ModBin), + undefined = nif_mod:lib_version(), + {error,{load,_}} = + nif_mod:load_nif_lib(Config, 1, + [{resource_type, null, ?RT_TAKEOVER, "resource_type_A",resource_dtor_A, + ?RT_TAKEOVER}, + {resource_type, 4, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",null, + ?RT_CREATE}, + {resource_type, 4, ?RT_CREATE, "resource_type_A_null",resource_dtor_A, + ?RT_CREATE}, + {resource_type, 4, ?RT_CREATE, "Mr Pink", resource_dtor_A, + ?RT_CREATE}, + + {return, 1} % FAIL + ]), + + undefined = nif_mod:lib_version(), + ok = nif_mod:load_nif_lib(Config, 1, + [{resource_type, null, ?RT_TAKEOVER, "resource_type_A",resource_dtor_A, + ?RT_TAKEOVER}, + {resource_type, 0, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A", + resource_dtor_A, ?RT_CREATE}, + + {resource_type, 1, ?RT_CREATE, "resource_type_A_null", null, + ?RT_CREATE}, + {resource_type, null, ?RT_TAKEOVER, "Mr Pink", resource_dtor_A, + ?RT_TAKEOVER}, + + {return, 0} % SUCCESS + ]), + + ?line hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), + ?line [{load,1,1,101},{get_priv_data_ptr,1,2,102}] = nif_mod_call_history(), + + {NA7,BinNA7} = make_resource(0, Holder, "NA7"), + {AN7,BinAN7} = make_resource(1, Holder, "AN7"), + + ok = forget_resource(NA7), + [{{resource_dtor_A_v1,BinNA7},1,_,_}] = nif_mod_call_history(), + + ok = forget_resource(AN7), + [] = nif_mod_call_history(), + ?line true = lists:member(?MODULE, erlang:system_info(taints)), ?line true = lists:member(nif_mod, erlang:system_info(taints)), ?line verify_tmpmem(TmpMem), @@ -1406,7 +1538,7 @@ dirty_nif(Config) when is_list(Config) -> {skipped,"No dirty scheduler support"} end. -next_msg(Pid) -> +next_msg(_Pid) -> receive M -> M after 100 -> diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index b550d1f16d..160f4843ad 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -1477,7 +1477,6 @@ static ERL_NIF_TERM consume_timeslice_nif(ErlNifEnv* env, int argc, const ERL_NI { int percent; char atom[10]; - int do_repeat; if (!enif_get_int(env, argv[0], &percent) || !enif_get_atom(env, argv[1], atom, sizeof(atom), ERL_NIF_LATIN1)) { diff --git a/erts/emulator/test/nif_SUITE_data/nif_mod.c b/erts/emulator/test/nif_SUITE_data/nif_mod.c index e32d10057c..55a0d2ac4f 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_mod.c +++ b/erts/emulator/test/nif_SUITE_data/nif_mod.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2009-2010. All Rights Reserved. + * Copyright Ericsson AB 2009-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -41,6 +41,7 @@ static ERL_NIF_TERM am_null; static ERL_NIF_TERM am_resource_type; static ERL_NIF_TERM am_resource_dtor_A; static ERL_NIF_TERM am_resource_dtor_B; +static ERL_NIF_TERM am_return; static NifModPrivData* priv_data(ErlNifEnv* env) { @@ -54,6 +55,7 @@ static void init(ErlNifEnv* env) am_resource_type = enif_make_atom(env, "resource_type"); am_resource_dtor_A = enif_make_atom(env, "resource_dtor_A"); am_resource_dtor_B = enif_make_atom(env, "resource_dtor_B"); + am_return = enif_make_atom(env, "return"); } static void add_call_with_arg(ErlNifEnv* env, NifModPrivData* data, const char* func_name, @@ -105,19 +107,15 @@ static void resource_dtor_B(ErlNifEnv* env, void* a) } /* {resource_type, Ix|null, ErlNifResourceFlags in, "TypeName", dtor(A|B|null), ErlNifResourceFlags out}*/ -static void open_resource_type(ErlNifEnv* env, ERL_NIF_TERM op_tpl) +static void open_resource_type(ErlNifEnv* env, const ERL_NIF_TERM* arr) { NifModPrivData* data = priv_data(env); - const ERL_NIF_TERM* arr; - int arity; char rt_name[30]; union { ErlNifResourceFlags e; int i; } flags, exp_res, got_res; unsigned ix; ErlNifResourceDtor* dtor; ErlNifResourceType* got_ptr; - CHECK(enif_get_tuple(env, op_tpl, &arity, &arr)); - CHECK(arity == 6); CHECK(enif_is_identical(arr[0], am_resource_type)); CHECK(enif_get_int(env, arr[2], &flags.i)); CHECK(enif_get_string(env, arr[3], rt_name, sizeof(rt_name), ERL_NIF_LATIN1) > 0); @@ -147,18 +145,32 @@ static void open_resource_type(ErlNifEnv* env, ERL_NIF_TERM op_tpl) CHECK(got_res.e == exp_res.e); } -static void do_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info) +static void do_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info, int* retvalp) { NifModPrivData* data = priv_data(env); ERL_NIF_TERM head, tail; unsigned ix; + for (ix=0; ix<RT_MAX; ix++) { data->rt_arr[ix] = NULL; } for (head = load_info; enif_get_list_cell(env, head, &head, &tail); head = tail) { - - open_resource_type(env, head); + const ERL_NIF_TERM* arr; + int arity; + + CHECK(enif_get_tuple(env, head, &arity, &arr)); + switch (arity) { + case 6: + open_resource_type(env, arr); + break; + case 2: + CHECK(arr[0] == am_return); + CHECK(enif_get_int(env, arr[1], retvalp)); + break; + default: + CHECK(0); + } } CHECK(enif_is_empty_list(env, head)); } @@ -166,6 +178,7 @@ static void do_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info) static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) { NifModPrivData* data; + int retval = 0; init(env); data = (NifModPrivData*) enif_alloc(sizeof(NifModPrivData)); @@ -177,38 +190,41 @@ static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) add_call(env, data, "load"); - do_load_info(env, load_info); - data->calls = 0; - return 0; + do_load_info(env, load_info, &retval); + if (retval) + NifModPrivData_release(data); + return retval; } static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) { NifModPrivData* data = (NifModPrivData*) *priv; + int retval = 0; init(env); add_call(env, data, "reload"); - do_load_info(env, load_info); - return 0; + do_load_info(env, load_info, &retval); + return retval; } static int upgrade(ErlNifEnv* env, void** priv, void** old_priv_data, ERL_NIF_TERM load_info) { NifModPrivData* data = (NifModPrivData*) *old_priv_data; + int retval = 0; init(env); add_call(env, data, "upgrade"); data->ref_cnt++; *priv = *old_priv_data; - do_load_info(env, load_info); + do_load_info(env, load_info, &retval); - return 0; + return retval; } static void unload(ErlNifEnv* env, void* priv) { NifModPrivData* data = (NifModPrivData*) priv; - int is_last; + add_call(env, data, "unload"); NifModPrivData_release(data); } diff --git a/erts/emulator/test/nif_SUITE_data/nif_mod.h b/erts/emulator/test/nif_SUITE_data/nif_mod.h index cd0ecf4b54..fb14fee815 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_mod.h +++ b/erts/emulator/test/nif_SUITE_data/nif_mod.h @@ -14,7 +14,6 @@ typedef struct call_info_t typedef struct { ErlNifMutex* mtx; - int calls; int ref_cnt; CallInfo* call_history; ErlNifResourceType* rt_arr[RT_MAX]; @@ -28,6 +27,11 @@ typedef struct enif_mutex_unlock((NMPD)->mtx); \ if (is_last) { \ enif_mutex_destroy((NMPD)->mtx); \ + while ((NMPD)->call_history) { \ + CallInfo* next = (NMPD)->call_history->next; \ + enif_free((NMPD)->call_history); \ + (NMPD)->call_history = next; \ + } \ enif_free((NMPD)); \ } \ }while (0) diff --git a/lib/Makefile b/lib/Makefile index 5128241563..6301c882b2 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -25,12 +25,12 @@ ERTS_APPLICATIONS = stdlib sasl kernel compiler # Then these have to be build ERLANG_APPLICATIONS = tools test_server common_test runtime_tools \ - inets xmerl edoc erl_docgen + inets xmerl edoc erl_docgen parsetools # These are only build if -a is given to otp_build or make is used directly ALL_ERLANG_APPLICATIONS = snmp otp_mibs erl_interface asn1 jinterface \ wx debugger reltool gs \ - ic mnesia crypto orber os_mon parsetools syntax_tools \ + ic mnesia crypto orber os_mon syntax_tools \ public_key ssl observer odbc diameter \ cosTransactions cosEvent cosTime cosNotification \ cosProperty cosFileTransfer cosEventDomain et megaco webtool \ diff --git a/lib/asn1/src/asn1ct_check.erl b/lib/asn1/src/asn1ct_check.erl index 533b02409e..b9f2cb876a 100644 --- a/lib/asn1/src/asn1ct_check.erl +++ b/lib/asn1/src/asn1ct_check.erl @@ -2631,16 +2631,19 @@ normalize_objectdescriptor(Value) -> normalize_real(Value) -> Value. -normalize_enumerated(S, Id, {Base,Ext}) -> +normalize_enumerated(S, Id0, NNL) -> + {Id,_} = lookup_enum_value(S, Id0, NNL), + Id. + +lookup_enum_value(S, Id, {Base,Ext}) -> %% Extensible ENUMERATED. - normalize_enumerated(S, Id, Base++Ext); -normalize_enumerated(S, #'Externalvaluereference'{value=Id}, - NamedNumberList) -> - normalize_enumerated(S, Id, NamedNumberList); -normalize_enumerated(S, Id, NamedNumberList) when is_atom(Id) -> - case lists:keymember(Id, 1, NamedNumberList) of - true -> - Id; + lookup_enum_value(S, Id, Base++Ext); +lookup_enum_value(S, #'Externalvaluereference'{value=Id}, NNL) -> + lookup_enum_value(S, Id, NNL); +lookup_enum_value(S, Id, NNL) when is_atom(Id) -> + case lists:keyfind(Id, 1, NNL) of + {_,_}=Ret -> + Ret; false -> throw(asn1_error(S, S#state.value, {undefined,Id})) end. @@ -3774,8 +3777,9 @@ resolv_value(S,Val) -> resolv_value1(S,Id). resolv_value1(S, ERef = #'Externalvaluereference'{value=Name}) -> - case catch resolve_namednumber(S,S#state.type,Name) of - V when is_integer(V) -> V; + case catch resolve_namednumber(S, S#state.type, Name) of + V when is_integer(V) -> + V; _ -> case get_referenced_type(S,ERef) of {Err,_Reason} when Err == error; Err == 'EXIT' -> @@ -3828,21 +3832,20 @@ resolve_value_from_object(S,Object,FieldName) -> end. - resolve_namednumber(S,#typedef{typespec=Type},Name) -> case Type#type.def of {'ENUMERATED',NameList} -> - NamedNumberList=check_enumerated(S,NameList,Type#type.constraint), - N = normalize_enumerated(S,Name,NamedNumberList), - {value,{_,V}} = lists:keysearch(N,1,NamedNumberList), - V; + resolve_namednumber_1(S, Name, NameList, Type); {'INTEGER',NameList} -> - NamedNumberList = check_enumerated(S,NameList,Type#type.constraint), - {value,{_,V}} = lists:keysearch(Name,1,NamedNumberList), - V; + resolve_namednumber_1(S, Name, NameList, Type); _ -> not_enumerated end. + +resolve_namednumber_1(S, Name, NameList, Type) -> + NamedNumberList = check_enumerated(S, NameList, Type#type.constraint), + {_,N} = lookup_enum_value(S, Name, NamedNumberList), + N. check_constraints(S,[{'ContainedSubtype',Type} | Rest], Acc) -> {RefMod,CTDef} = get_referenced_type(S,Type#type.def), diff --git a/lib/asn1/test/asn1_SUITE_data/EnumExt.asn1 b/lib/asn1/test/asn1_SUITE_data/EnumExt.asn1 index 8dc5f3d7e1..74fa97e7aa 100644 --- a/lib/asn1/test/asn1_SUITE_data/EnumExt.asn1 +++ b/lib/asn1/test/asn1_SUITE_data/EnumExt.asn1 @@ -18,6 +18,8 @@ Ext1 ::= ENUMERATED { magenta(9) } +SubExt1 ::= Ext1 ( blue | orange | black ) + Noext ::= ENUMERATED { blue(0), red(1), diff --git a/lib/asn1/test/testEnumExt.erl b/lib/asn1/test/testEnumExt.erl index c66adaf949..878518be11 100644 --- a/lib/asn1/test/testEnumExt.erl +++ b/lib/asn1/test/testEnumExt.erl @@ -59,6 +59,10 @@ main(ber) -> common(ber). common(Erule) -> + roundtrip('SubExt1', blue), + roundtrip('SubExt1', orange), + roundtrip('SubExt1', black), + roundtrip('Seq', {'Seq',blue,42}), roundtrip('Seq', {'Seq',red,42}), roundtrip('Seq', {'Seq',green,42}), diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index 1d851f8d2f..ac9857487d 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -382,13 +382,17 @@ loop(Mode,TestData,StartDir) -> TestData1 = case lists:keysearch(Key,1,TestData) of {value,{Key,Val}} -> - case Fun(Val) of + try Fun(Val) of '$delete' -> return(From,deleted), lists:keydelete(Key,1,TestData); NewVal -> return(From,NewVal), [{Key,NewVal}|lists:keydelete(Key,1,TestData)] + catch + _:Error -> + return(From,{error,Error}), + TestData end; _ -> case lists:member(create,Opts) of diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index 28f0494d20..194e7d42ae 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -66,7 +66,7 @@ all() -> [cfg_error, lib_error, no_compile, timetrap_end_conf, timetrap_normal, timetrap_extended, timetrap_parallel, timetrap_fun, timetrap_fun_group, misc_errors, - config_restored]. + config_restored, config_func_errors]. groups() -> []. @@ -310,6 +310,25 @@ config_restored(Config) when is_list(Config) -> ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- +%%% +config_func_errors(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join(DataDir, "error/test/config_func_error_1_SUITE"), + {Opts,ERPid} = setup([{suite,Suite}], + Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(config_func_errors, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(config_func_errors), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + + +%%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- @@ -323,8 +342,6 @@ setup(Test, Config) -> reformat(Events, EH) -> ct_test_support:reformat(Events, EH). - %reformat(Events, _EH) -> - % Events. %%%----------------------------------------------------------------- %%% TEST EVENTS @@ -1498,4 +1515,42 @@ test_events(config_restored) -> {?eh,tc_done,{config_restored_SUITE,end_per_suite,ok}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} + ]; + +test_events(config_func_errors) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,4}}, + {?eh,tc_start,{config_func_error_1_SUITE,init_per_suite}}, + {?eh,tc_done,{config_func_error_1_SUITE,init_per_suite,ok}}, + + {?eh,tc_start,{config_func_error_1_SUITE,exit_in_iptc}}, + {?eh,tc_done,{config_func_error_1_SUITE,exit_in_iptc,'_'}}, + {?eh,test_stats,{0,1,{0,0}}}, + + {?eh,tc_start,{config_func_error_1_SUITE,exit_in_eptc}}, + {?eh,tc_done,{config_func_error_1_SUITE,exit_in_eptc,'_'}}, + {?eh,test_stats,{0,2,{0,0}}}, + + [{?eh,tc_start,{config_func_error_1_SUITE,{init_per_group,g1,[]}}}, + {?eh,tc_done,{config_func_error_1_SUITE,{init_per_group,g1,[]},ok}}, + {?eh,tc_start,{config_func_error_1_SUITE,exit_in_iptc}}, + {?eh,tc_done,{config_func_error_1_SUITE,exit_in_iptc,'_'}}, + {?eh,test_stats,{0,3,{0,0}}}, + {?eh,tc_start,{config_func_error_1_SUITE,{end_per_group,g1,[]}}}, + {?eh,tc_done,{config_func_error_1_SUITE,{end_per_group,g1,[]},ok}}], + + [{?eh,tc_start,{config_func_error_1_SUITE,{init_per_group,g2,[]}}}, + {?eh,tc_done,{config_func_error_1_SUITE,{init_per_group,g2,[]},ok}}, + {?eh,tc_start,{config_func_error_1_SUITE,exit_in_eptc}}, + {?eh,tc_done,{config_func_error_1_SUITE,exit_in_eptc,'_'}}, + {?eh,test_stats,{0,4,{0,0}}}, + {?eh,tc_start,{config_func_error_1_SUITE,{end_per_group,g2,[]}}}, + {?eh,tc_done,{config_func_error_1_SUITE,{end_per_group,g2,[]},ok}}], + + {?eh,tc_start,{config_func_error_1_SUITE,end_per_suite}}, + {?eh,tc_done,{config_func_error_1_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} ]. diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/config_func_error_1_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/config_func_error_1_SUITE.erl new file mode 100644 index 0000000000..f1025213dc --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/config_func_error_1_SUITE.erl @@ -0,0 +1,138 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(config_func_error_1_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% Info = [tuple()] +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,5}}]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +bad_proc(Config) -> + ct:pal("Bye bye from ~p", [self()]), + %% this call will either generate an exit immediately + %% or return a fun to be executed here + ErrorFun = ct_test_support:random_error(Config), + ct:log("Calling error fun now...", []), + ErrorFun(), + ct:sleep(10000), + ok. + +init_per_testcase(exit_in_iptc, Config) -> + spawn_link(?MODULE, bad_proc, [Config]), + ct:sleep(10000), + Config; +init_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_testcase(exit_in_eptc, Config) -> + spawn_link(?MODULE, bad_proc, [Config]), + ct:sleep(10000), + ok; +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%%-------------------------------------------------------------------- +groups() -> + [{g1, [], [exit_in_iptc]}, + {g2, [], [exit_in_eptc]}]. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%%-------------------------------------------------------------------- +all() -> + [exit_in_iptc, + exit_in_eptc, + {group, g1}, + {group, g2}]. + +exit_in_iptc(_) -> + ok. + +exit_in_eptc(_) -> + ok. + diff --git a/lib/common_test/test/ct_telnet_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE.erl index 25debf09d4..acce4eca14 100644 --- a/lib/common_test/test/ct_telnet_SUITE.erl +++ b/lib/common_test/test/ct_telnet_SUITE.erl @@ -215,23 +215,18 @@ all_cases(Suite,Config) -> fun({group,G}) -> {value,{G,Props,GTCs}} = lists:keysearch(G,1,Suite:groups()), - GTCs1 = case lists:member(parallel,Props) of - true -> - %%! TEMPORARY WORKAROUND FOR PROBLEM - %%! WITH ct_test_support NOT HANDLING - %%! VERIFICATION OF PARALLEL GROUPS - %%! CORRECTLY! - []; - false -> - [[{?eh,tc_start,{Suite,GTC}}, - {?eh,tc_done,{Suite,GTC,ok}}] || - GTC <- GTCs] - end, - [{?eh,tc_start,{Suite,{init_per_group,G,Props}}}, - {?eh,tc_done,{Suite,{init_per_group,G,Props},ok}} | - GTCs1] ++ - [{?eh,tc_start,{Suite,{end_per_group,G,Props}}}, - {?eh,tc_done,{Suite,{end_per_group,G,Props},ok}}]; + GTCs1 = [[{?eh,tc_start,{Suite,GTC}}, + {?eh,tc_done,{Suite,GTC,ok}}] || + GTC <- GTCs], + GEvs = [{?eh,tc_start,{Suite,{init_per_group,G,Props}}}, + {?eh,tc_done,{Suite,{init_per_group,G,Props},ok}} | + GTCs1] ++ + [{?eh,tc_start,{Suite,{end_per_group,G,Props}}}, + {?eh,tc_done,{Suite,{end_per_group,G,Props},ok}}], + case lists:member(parallel, Props) of + true -> [{parallel,GEvs}]; + false -> GEvs + end; (TC) -> [{?eh,tc_done,{Suite,TC,ok}}] end, GroupsAndTCs), diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 772274ce7e..2e2b45d59f 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -40,6 +40,8 @@ -export([ct_test_halt/1, ct_rpc/2]). +-export([random_error/1]). + -include_lib("kernel/include/file.hrl"). %%%----------------------------------------------------------------- @@ -110,7 +112,6 @@ start_slave(NodeName, Config, Level) -> _ -> ok end, - TraceFile = filename:join(DataDir, "ct.trace"), case file:read_file_info(TraceFile) of {ok,_} -> @@ -395,6 +396,55 @@ ct_rpc({M,F,A}, Config) -> %%%----------------------------------------------------------------- +%%% random_error/1 +random_error(Config) when is_list(Config) -> + random:seed(now()), + Gen = fun(0,_) -> ok; (N,Fun) -> Fun(N-1, Fun) end, + Gen(random:uniform(100), Gen), + + ErrorTypes = ['BADMATCH','BADARG','CASE_CLAUSE','FUNCTION_CLAUSE', + 'EXIT','THROW','UNDEF'], + Type = lists:nth(random:uniform(length(ErrorTypes)), ErrorTypes), + Where = case random:uniform(2) of + 1 -> + io:format("ct_test_support *returning* error of type ~w", + [Type]), + tc; + 2 -> + io:format("ct_test_support *generating* error of type ~w", + [Type]), + lib + end, + ErrorFun = + fun() -> + case Type of + 'BADMATCH' -> + ok = proplists:get_value(undefined, Config); + 'BADARG' -> + size(proplists:get_value(priv_dir, Config)); + 'FUNCTION_CLAUSE' -> + random_error(x); + 'EXIT' -> + spawn_link(fun() -> + undef_proc ! hello, + ok + end); + 'THROW' -> + PrivDir = proplists:get_value(priv_dir, Config), + if is_list(PrivDir) -> throw(generated_throw) end; + 'UNDEF' -> + apply(?MODULE, random_error, []) + end + end, + %% either call the fun here or return it to the caller (to be + %% executed in a test case instead) + case Where of + tc -> ErrorFun; + lib -> ErrorFun() + end. + + +%%%----------------------------------------------------------------- %%% EVENT HANDLING handle_event(EH, Event) -> diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index 1459f696a0..c66c8ea4bf 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -408,6 +408,11 @@ module.beam: module.erl \ <code>-compile({no_auto_import,[error/1]}).</code> </item> + <tag><c>no_auto_import</c></tag> + <item> + <p>Do not auto import any functions from the module <c>erlang</c>.</p> + </item> + <tag><c>no_line_info</c></tag> <item> diff --git a/lib/compiler/src/beam_bsm.erl b/lib/compiler/src/beam_bsm.erl index fdfcb08125..d54c2a9fde 100644 --- a/lib/compiler/src/beam_bsm.erl +++ b/lib/compiler/src/beam_bsm.erl @@ -209,6 +209,7 @@ btb_reaches_match_2([{call,Arity,{f,Lbl}}|Is], Regs, D) -> btb_reaches_match_2([{apply,Arity}|Is], Regs, D) -> btb_call(Arity+2, apply, Regs, Is, D); btb_reaches_match_2([{call_fun,Live}=I|Is], Regs, D) -> + btb_ensure_not_used([{x,Live}], I, Regs), btb_call(Live, I, Regs, Is, D); btb_reaches_match_2([{make_fun2,_,_,_,Live}|Is], Regs, D) -> btb_call(Live, make_fun2, Regs, Is, D); diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index e0d0d0fd1d..57fdf95677 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -1134,7 +1134,7 @@ resolve_inst({line,[Index]},_,_,_) -> {line,resolve_arg(Index)}; %% -%% R17A. +%% 17.0 %% resolve_inst({put_map_assoc,Args},_,_,_) -> [FLbl,Src,Dst,{u,N},{{z,1},{u,_Len},List0}] = Args, @@ -1150,6 +1150,10 @@ resolve_inst({is_map,Args0},_,_,_) -> [FLbl|Args] = resolve_args(Args0), {test, is_map, FLbl, Args}; +resolve_inst({has_map_field,Args0},_,_,_) -> + [FLbl|Args] = resolve_args(Args0), + {test,has_map_field,FLbl,Args}; + resolve_inst({get_map_element,Args},_,_,_) -> [FLbl,Src,Key,Dst] = resolve_args(Args), {get_map_element,FLbl,Src,Key,Dst}; diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 97f84da08f..2486d486ed 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -574,6 +574,7 @@ valfun_4({apply,Live}, Vst) -> valfun_4({apply_last,Live,_}, Vst) -> tail_call(apply, Live+2, Vst); valfun_4({call_fun,Live}, Vst) -> + validate_src([{x,Live}], Vst), call('fun', Live+1, Vst); valfun_4({call,Live,Func}, Vst) -> call(Func, Live, Vst); @@ -881,7 +882,7 @@ valfun_4(_, _) -> verify_put_map(Fail, Src, Dst, Live, List, Vst0) -> verify_live(Live, Vst0), verify_y_init(Vst0), - [assert_term(Term, Vst0) || Term <- List], + foreach(fun (Term) -> assert_term(Term, Vst0) end, List), assert_term(Src, Vst0), Vst1 = heap_alloc(0, Vst0), Vst2 = branch_state(Fail, Vst1), @@ -908,7 +909,7 @@ validate_bs_skip_utf(Fail, Ctx, Live, Vst0) -> branch_state(Fail, Vst). %% -%% Special state handling for setelement/3 and the set_tuple_element/3 instruction. +%% Special state handling for setelement/3 and set_tuple_element/3 instructions. %% A possibility for garbage collection must not occur between setelement/3 and %% set_tuple_element/3. %% diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 0bb4de6f17..e79fe41f9b 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -623,9 +623,11 @@ core_passes() -> [{core_old_inliner,fun test_old_inliner/1,fun core_old_inliner/1}, {iff,doldinline,{listing,"oldinline"}}, ?pass(core_fold_module), + {iff,dcorefold,{listing,"corefold"}}, {core_inline_module,fun test_core_inliner/1,fun core_inline_module/1}, {iff,dinline,{listing,"inline"}}, - {core_fold_after_inlining,fun test_core_inliner/1,fun core_fold_module_after_inlining/1}, + {core_fold_after_inlining,fun test_any_inliner/1, + fun core_fold_module_after_inlining/1}, ?pass(core_transforms)]}, {iff,dcopt,{listing,"copt"}}, {iff,'to_core',{done,"core"}}]} @@ -1171,6 +1173,9 @@ test_core_inliner(#compile{options=Opts}) -> end, Opts) end. +test_any_inliner(St) -> + test_old_inliner(St) orelse test_core_inliner(St). + core_old_inliner(#compile{code=Code0,options=Opts}=St) -> {ok,Code} = sys_core_inline:module(Code0, Opts), {ok,St#compile{code=Code}}. diff --git a/lib/compiler/src/core_lib.erl b/lib/compiler/src/core_lib.erl index f506901099..ed181e3baa 100644 --- a/lib/compiler/src/core_lib.erl +++ b/lib/compiler/src/core_lib.erl @@ -105,8 +105,8 @@ vu_expr(V, #c_cons{hd=H,tl=T}) -> vu_expr(V, H) orelse vu_expr(V, T); vu_expr(V, #c_tuple{es=Es}) -> vu_expr_list(V, Es); -vu_expr(V, #c_map{es=Es}) -> - vu_expr_list(V, Es); +vu_expr(V, #c_map{var=M,es=Es}) -> + vu_expr(V, M) orelse vu_expr_list(V, Es); vu_expr(V, #c_map_pair{key=Key,val=Val}) -> vu_expr_list(V, [Key,Val]); vu_expr(V, #c_binary{segments=Ss}) -> diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 1cdbac5693..e2b9213891 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -305,6 +305,10 @@ expr(#c_let{}=Let, Ctxt, Sub) -> %% Now recursively re-process the new expression. expr(Expr, Ctxt, sub_new_preserve_types(Sub)) end; +expr(#c_letrec{body=#c_var{}}=Letrec, effect, _Sub) -> + %% This is named fun in an 'effect' context. Warn and ignore. + add_warning(Letrec, useless_building), + void(); expr(#c_letrec{defs=Fs0,body=B0}=Letrec, Ctxt, Sub) -> Fs1 = map(fun ({Name,Fb}) -> {Name,expr(Fb, {letrec,Ctxt}, Sub)} @@ -349,7 +353,12 @@ expr(#c_case{}=Case0, Ctxt, Sub) -> Case = Case1#c_case{arg=Arg2,clauses=Cs2}, warn_no_clause_match(Case1, Case), Expr = eval_case(Case, Sub), - bsm_an(Expr); + case move_case_into_arg(Case, Sub) of + impossible -> + bsm_an(Expr); + Other -> + expr(Other, Ctxt, sub_new_preserve_types(Sub)) + end; Other -> expr(Other, Ctxt, Sub) end; @@ -598,6 +607,14 @@ eval_binary_1([#c_bitstr{val=#c_literal{val=Val},size=#c_literal{val=Sz}, error:_ -> throw(impossible) end; +eval_binary_1([#c_bitstr{val=#c_literal{},size=#c_literal{}, + unit=#c_literal{},type=#c_literal{}, + flags=#c_cons{}=Flags}=Bitstr|Ss], Acc0) -> + case cerl:fold_literal(Flags) of + #c_literal{} = Flags1 -> + eval_binary_1([Bitstr#c_bitstr{flags=Flags1}|Ss], Acc0); + _ -> throw(impossible) + end; eval_binary_1([], Acc) -> Acc; eval_binary_1(_, _) -> throw(impossible). @@ -1536,9 +1553,17 @@ map_pair_pattern_list(Ps0, Isub, Osub0) -> {Ps,{_,Osub}} = mapfoldl(fun map_pair_pattern/2, {Isub,Osub0}, Ps0), {Ps,Osub}. -map_pair_pattern(#c_map_pair{op=#c_literal{val=exact},key=K0,val=V0}=Pair, {Isub,Osub0}) -> - {K,Osub1} = pattern(K0, Isub, Osub0), - {V,Osub} = pattern(V0, Isub, Osub1), +map_pair_pattern(#c_map_pair{op=#c_literal{val=exact},key=K0,val=V0}=Pair,{Isub,Osub0}) -> + {K,Osub1} = case cerl:type(K0) of + binary -> + K1 = eval_binary(K0), + case cerl:type(K1) of + literal -> {K1,Osub0}; + _ -> pattern(K0,Isub,Osub0) + end; + _ -> pattern(K0,Isub,Osub0) + end, + {V,Osub} = pattern(V0,Isub,Osub1), {Pair#c_map_pair{key=K,val=V},{Isub,Osub}}. bin_pattern_list(Ps0, Isub, Osub0) -> @@ -1920,14 +1945,45 @@ opt_bool_case_guard(Arg, [#c_clause{pats=[#c_literal{val=false}]}=Fc,Tc]) -> %% last clause is guaranteed to match so if there is only one clause %% with a pattern containing only variables then rewrite to a let. -eval_case(#c_case{arg=E,clauses=[#c_clause{pats=Ps0,body=B}]}, Sub) -> +eval_case(#c_case{arg=E,clauses=[#c_clause{pats=Ps0, + guard=#c_literal{val=true}, + body=B}]}=Case, Sub) -> Es = case cerl:is_c_values(E) of true -> cerl:values_es(E); false -> [E] end, - {true,Bs} = cerl_clauses:match_list(Ps0, Es), - {Ps,As} = unzip(Bs), - expr(#c_let{vars=Ps,arg=core_lib:make_values(As),body=B}, sub_new(Sub)); + %% Consider: + %% + %% case SomeSideEffect() of + %% X=Y -> ... + %% end + %% + %% We must not rewrite it to: + %% + %% let <X,Y> = <SomeSideEffect(),SomeSideEffect()> in ... + %% + %% because SomeSideEffect() would be evaluated twice. + %% + %% Instead we must evaluate the case expression in an outer let + %% like this: + %% + %% let NewVar = SomeSideEffect() in + %% let <X,Y> = <NewVar,NewVar> in ... + %% + Vs = make_vars([], length(Es)), + case cerl_clauses:match_list(Ps0, Vs) of + {false,_} -> + %% This can only happen if the Core Erlang code is + %% handwritten or generated by another code generator + %% than v3_core. Assuming that the Core Erlang program + %% is correct, the clause will always match at run-time. + Case; + {true,Bs} -> + {Ps,As} = unzip(Bs), + InnerLet = cerl:c_let(Ps, core_lib:make_values(As), B), + Let = cerl:c_let(Vs, E, InnerLet), + expr(Let, sub_new(Sub)) + end; eval_case(Case, _) -> Case. %% case_opt(CaseArg, [Clause]) -> {CaseArg,[Clause]}. @@ -2555,6 +2611,77 @@ opt_simple_let_2(Let, Vs0, Arg0, Body, value, Sub) -> value, Sub) end. +move_case_into_arg(#c_case{arg=#c_let{vars=OuterVars0,arg=OuterArg, + body=InnerArg0}=Outer, + clauses=InnerClauses}=Inner, Sub) -> + %% + %% case let <OuterVars> = <OuterArg> in <InnerArg> of + %% <InnerClauses> + %% end + %% + %% ==> + %% + %% let <OuterVars> = <OuterArg> + %% in case <InnerArg> of <InnerClauses> end + %% + ScopeSub0 = sub_subst_scope(Sub#sub{t=[]}), + {OuterVars,ScopeSub} = pattern_list(OuterVars0, ScopeSub0), + InnerArg = body(InnerArg0, ScopeSub), + Outer#c_let{vars=OuterVars,arg=OuterArg, + body=Inner#c_case{arg=InnerArg,clauses=InnerClauses}}; +move_case_into_arg(#c_case{arg=#c_case{arg=OuterArg, + clauses=[OuterCa0,OuterCb]}=Outer, + clauses=InnerClauses}=Inner0, Sub) -> + case is_failing_clause(OuterCb) of + true -> + #c_clause{pats=OuterPats0,guard=OuterGuard0, + body=InnerArg0} = OuterCa0, + %% + %% case case <OuterArg> of + %% <OuterPats> when <OuterGuard> -> <InnerArg> + %% <OuterCb> + %% ... + %% end of + %% <InnerClauses> + %% end + %% + %% ==> + %% + %% case <OuterArg> of + %% <OuterPats> when <OuterGuard> -> + %% case <InnerArg> of <InnerClauses> end + %% <OuterCb> + %% end + %% + ScopeSub0 = sub_subst_scope(Sub#sub{t=[]}), + {OuterPats,ScopeSub} = pattern_list(OuterPats0, ScopeSub0), + OuterGuard = guard(OuterGuard0, ScopeSub), + InnerArg = body(InnerArg0, ScopeSub), + Inner = Inner0#c_case{arg=InnerArg,clauses=InnerClauses}, + OuterCa = OuterCa0#c_clause{pats=OuterPats,guard=OuterGuard, + body=Inner}, + Outer#c_case{arg=OuterArg, + clauses=[OuterCa,OuterCb]}; + false -> + impossible + end; +move_case_into_arg(#c_case{arg=#c_seq{arg=OuterArg,body=InnerArg}=Outer, + clauses=InnerClauses}=Inner, _Sub) -> + %% + %% case do <OuterArg> <InnerArg> of + %% <InnerClauses> + %% end + %% + %% ==> + %% + %% do <OuterArg> + %% case <InnerArg> of <InerClauses> end + %% + Outer#c_seq{arg=OuterArg, + body=Inner#c_case{arg=InnerArg,clauses=InnerClauses}}; +move_case_into_arg(_, _) -> + impossible. + %% In guards only, rewrite a case in a let argument like %% %% let <Var> = case <> of diff --git a/lib/compiler/src/v3_codegen.erl b/lib/compiler/src/v3_codegen.erl index c8735a76e8..4d155c0fd0 100644 --- a/lib/compiler/src/v3_codegen.erl +++ b/lib/compiler/src/v3_codegen.erl @@ -459,7 +459,7 @@ basic_block([Le|Les], Acc) -> %% sets that may garbage collect are not allowed in basic blocks. collect_block({set,_,{binary,_}}) -> no_block; -collect_block({set,_,{map,_,_}}) -> no_block; +collect_block({set,_,{map,_,_,_}}) -> no_block; collect_block({set,_,_}) -> include; collect_block({call,{var,_}=Var,As,_Rs}) -> {block_end,As++[Var]}; collect_block({call,Func,As,_Rs}) -> {block_end,As++func_vars(Func)}; @@ -928,7 +928,7 @@ select_extract_tuple(Src, Vs, I, Vdb, Bef, St) -> select_map(Scs, V, Tf, Vf, Bef, St0) -> Reg = fetch_var(V, Bef), {Is,Aft,St1} = - match_fmf(fun(#l{ke={val_clause,{map,Es},B},i=I,vdb=Vdb}, Fail, St1) -> + match_fmf(fun(#l{ke={val_clause,{map,_,Es},B},i=I,vdb=Vdb}, Fail, St1) -> select_map_val(V, Es, B, Fail, I, Vdb, Bef, St1) end, Vf, St0, Scs), {[{test,is_map,{f,Tf},[Reg]}|Is],Aft,St1}. @@ -1488,55 +1488,35 @@ set_cg([{var,R}], {binary,Segs}, Le, Vdb, Bef, %% Now generate the complete code for constructing the binary. Code = cg_binary(PutCode, Target, Temp, Fail, MaxRegs, Le#l.a), {Sis++Code,Aft,St}; -set_cg([{var,R}], {map,SrcMap,Es0}, Le, Vdb, Bef, +set_cg([{var,R}], {map,Op,Map,Es}, Le, Vdb, Bef, #cg{in_catch=InCatch,bfail=Bfail}=St) -> + Fail = {f,Bfail}, {Sis,Int0} = case InCatch of true -> adjust_stack(Bef, Le#l.i, Le#l.i+1, Vdb); false -> {[],Bef} end, + SrcReg = cg_reg_arg(Map,Int0), Line = line(Le#l.a), - SrcReg = case SrcMap of - {var,SrcVar} -> fetch_var(SrcVar, Int0); - _ -> SrcMap - end, - {Assoc,Exact} = - try - cg_map_pairs(Es0) - catch - throw:badarg -> - {[],[{{float,0.0},{atom,badarg}}, - {{integer,0},{atom,badarg}}]} - end, - F = fun ({K,{var,V}}) -> [K,fetch_var(V, Int0)]; - ({K,E}) -> [K,E] - end, - AssocList = flatmap(F, Assoc), - ExactList = flatmap(F, Exact), - Live0 = max_reg(Bef#sr.reg), - Int1 = clear_dead(Int0, Le#l.i, Vdb), - Aft = Bef#sr{reg=put_reg(R, Int1#sr.reg)}, - Target = fetch_reg(R, Aft#sr.reg), - Code = [Line] ++ - case {AssocList,ExactList} of - {[_|_],[]} -> - [{put_map_assoc,Fail,SrcReg,Target,Live0,{list,AssocList}}]; - {[_|_],[_|_]} -> - Live = case Target of - {x,TargetX} when TargetX =:= Live0 -> - Live0 + 1; - _ -> - Live0 - end, - [{put_map_assoc,Fail,SrcReg,Target,Live0,{list,AssocList}}, - {put_map_exact,Fail,Target,Target,Live,{list,ExactList}}]; - {[],[_|_]} -> - [{put_map_exact,Fail,SrcReg,Target,Live0,{list,ExactList}}]; - {[],[]} -> - [{put_map_assoc,Fail,SrcReg,Target,Live0,{list,[]}}] - end, - {Sis++Code,Aft,St}; + + %% The instruction needs to store keys in term sorted order + %% All keys has to be unique here + Pairs = map_pair_strip_and_termsort(Es), + + %% fetch registers for values to be put into the map + List = flatmap(fun({K,V}) -> [K,cg_reg_arg(V,Int0)] end, Pairs), + + Live = max_reg(Bef#sr.reg), + Int1 = Int0#sr{reg=put_reg(R, Int0#sr.reg)}, + Aft = clear_dead(Int1, Le#l.i, Vdb), + Target = fetch_reg(R, Int1#sr.reg), + + I = case Op of + assoc -> put_map_assoc; + exact -> put_map_exact + end, + {Sis++[Line]++[{I,Fail,SrcReg,Target,Live,{list,List}}],Aft,St}; set_cg([{var,R}], Con, Le, Vdb, Bef, St) -> %% Find a place for the return register first. Int = Bef#sr{reg=put_reg(R, Bef#sr.reg)}, @@ -1549,70 +1529,12 @@ set_cg([{var,R}], Con, Le, Vdb, Bef, St) -> end, {Ais,clear_dead(Int, Le#l.i, Vdb),St}. -%% cg_map_pairs(MapPairs) -> {Assoc,Exact} -%% Assoc = Exact = [{K,V}] -%% -%% Remove multiple assignments to the same key, and return -%% one list key-value list with all keys that may or may not exist -%% (Assoc), and one with keys that must exist (Exact). -%% - -cg_map_pairs(Es0) -> - Es = cg_map_pairs_1(Es0, 0), - R0 = sofs:relation(Es), - R1 = sofs:relation_to_family(R0), - R2 = sofs:to_external(R1), - - %% R2 is now [{KeyValue,[{Order,Op,OriginalKey,Value}]}] - R3 = [begin - %% The value for the last pair determines the value. - {_,_,_,V} = lists:last(Vs), - {Op,{_,SortOrder}=K} = map_pair_op_and_key(Vs), - {Op,{SortOrder,K,V}} - end || {_,Vs} <- R2], - - %% R3 is now [{Op,{Key,Value}}] - R = termsort(R3), - - %% R4 is now sorted with all alloc first in the list, followed by - %% all exact. - {Assoc,Exact} = lists:partition(fun({Op,_}) -> Op =:= assoc end, R), - {[{K,V} || {_,{_,K,V}} <- Assoc], - [{K,V} || {_,{_,K,V}} <- Exact]}. - -cg_map_pairs_1([{map_pair_assoc,{_,Kv}=K,V}|T], Order) -> - [{Kv,{Order,assoc,K,V}}|cg_map_pairs_1(T, Order+1)]; -cg_map_pairs_1([{map_pair_exact,{_,Kv}=K,V}|T], Order) -> - [{Kv,{Order,exact,K,V}}|cg_map_pairs_1(T, Order+1)]; -cg_map_pairs_1([], _) -> []. - -%% map_pair_op_and_key({_,Op,K,_}) -> {Operator,Key} -%% Determine the operator and key to use. Throw a 'badarg' -%% exception if there are contradictory exact updates. - -map_pair_op_and_key(L) -> - case [K || {_,exact,K,_} <- L] of - [K] -> - %% There is a single ':=' operator. Use that key. - {exact,K}; - [K|T] -> - %% There is more than one ':=' operator. All of them - %% must have the same key. - case lists:all(fun(E) -> E =:= K end, T) of - true -> - {exact,K}; - false -> - %% Some keys are different, e.g. 1 and 1.0. - throw(badarg) - end; - [] -> - %% Only '=>' operators. Use the first key in the list. - [{_,assoc,K,_}|_] = L, - {assoc,K} - end. - -termsort(Ls) -> - lists:sort(fun(A,B) -> erts_internal:cmp_term(A,B) < 0 end, Ls). +map_pair_strip_and_termsort(Es) -> + %% format in + %% [{map_pair,K,V}] + %% where K is for example {integer, 1} and we want to sort on 1. + Ls = [{K,V}||{_,K,V}<-Es], + lists:sort(fun({{_,A},_},{{_,B},_}) -> erts_internal:cmp_term(A,B) < 0 end, Ls). %%% %%% Code generation for constructing binaries. @@ -2085,7 +2007,7 @@ load_vars(Vs, Regs) -> foldl(fun ({var,V}, Rs) -> put_reg(V, Rs) end, Regs, Vs). %% put_reg(Val, Regs) -> Regs. -%% find_reg(Val, Regs) -> ok{r{R}} | error. +%% find_reg(Val, Regs) -> {ok,r{R}} | error. %% fetch_reg(Val, Regs) -> r{R}. %% Functions to interface the registers. diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index e30bfa729c..3435a46ca9 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -74,7 +74,7 @@ -export([module/2,format_error/1]). -import(lists, [reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3, - splitwith/2,keyfind/3,sort/1,foreach/2]). + splitwith/2,keyfind/3,sort/1,foreach/2,droplast/1,last/1]). -import(ordsets, [add_element/2,del_element/2,is_element/2, union/1,union/2,intersection/2,subtract/2]). -import(cerl, [ann_c_cons/3,ann_c_cons_skel/3,ann_c_tuple/2,c_tuple/1]). @@ -101,6 +101,8 @@ -record(ireceive2, {anno=#a{},clauses,timeout,action}). -record(iset, {anno=#a{},var,arg}). -record(itry, {anno=#a{},args,vars,body,evars,handler}). +-record(ifilter, {anno=#a{},arg}). +-record(igen, {anno=#a{},acc_pat,acc_guard,skip_pat,tail,tail_pat,arg}). -type iapply() :: #iapply{}. -type ibinary() :: #ibinary{}. @@ -117,10 +119,13 @@ -type ireceive2() :: #ireceive2{}. -type iset() :: #iset{}. -type itry() :: #itry{}. +-type ifilter() :: #ifilter{}. +-type igen() :: #igen{}. -type i() :: iapply() | ibinary() | icall() | icase() | icatch() | iclause() | ifun() | iletrec() | imatch() | iprimop() - | iprotect() | ireceive1() | ireceive2() | iset() | itry(). + | iprotect() | ireceive1() | ireceive2() | iset() | itry() + | ifilter() | igen(). -type warning() :: {file:filename(), [{integer(), module(), term()}]}. @@ -226,13 +231,13 @@ guard(Gs0, St0) -> Gt1 = guard_tests(Gt0), L = element(2, Gt1), {op,L,'or',Gt1,Rhs} - end, guard_tests(last(Gs0)), first(Gs0)), + end, guard_tests(last(Gs0)), droplast(Gs0)), {Gs,St} = gexpr_top(Gs1, St0#core{in_guard=true}), {Gs,St#core{in_guard=false}}. guard_tests(Gs) -> L = element(2, hd(Gs)), - {protect,L,foldr(fun (G, Rhs) -> {op,L,'and',G,Rhs} end, last(Gs), first(Gs))}. + {protect,L,foldr(fun (G, Rhs) -> {op,L,'and',G,Rhs} end, last(Gs), droplast(Gs))}. %% gexpr_top(Expr, State) -> {Cexpr,State}. %% Generate an internal core expression of a guard test. Explicitly @@ -479,8 +484,9 @@ expr({cons,L,H0,T0}, St0) -> {T1,Tps,St2} = safe(T0, St1), A = lineno_anno(L, St2), {ann_c_cons(A, H1, T1),Hps ++ Tps,St2}; -expr({lc,L,E,Qs}, St) -> - lc_tq(L, E, Qs, #c_literal{anno=lineno_anno(L, St),val=[]}, St); +expr({lc,L,E,Qs0}, St0) -> + {Qs1,St1} = preprocess_quals(L, Qs0, St0), + lc_tq(L, E, Qs1, #c_literal{anno=lineno_anno(L, St1),val=[]}, St1); expr({bc,L,E,Qs}, St) -> bc_tq(L, E, Qs, {nil,L}, St); expr({tuple,L,Es0}, St0) -> @@ -513,7 +519,7 @@ expr({bin,L,Es0}, St0) -> end; expr({block,_,Es0}, St0) -> %% Inline the block directly. - {Es1,St1} = exprs(first(Es0), St0), + {Es1,St1} = exprs(droplast(Es0), St0), {E1,Eps,St2} = expr(last(Es0), St1), {E1,Es1 ++ Eps,St2}; expr({'if',L,Cs0}, St0) -> @@ -647,7 +653,7 @@ expr({match,L,P0,E0}, St0) -> Other when not is_atom(Other) -> {#imatch{anno=#a{anno=Lanno},pat=P2,arg=E2,fc=Fc},Eps,St4} end; -expr({op,_,'++',{lc,Llc,E,Qs},More}, St0) -> +expr({op,_,'++',{lc,Llc,E,Qs0},More}, St0) -> %% Optimise '++' here because of the list comprehension algorithm. %% %% To avoid achieving quadratic complexity if there is a chain of @@ -655,7 +661,8 @@ expr({op,_,'++',{lc,Llc,E,Qs},More}, St0) -> %% evaluation of More now. Evaluating More here could also reduce the %% number variables in the environment for letrec. {Mc,Mps,St1} = safe(More, St0), - {Y,Yps,St} = lc_tq(Llc, E, Qs, Mc, St1), + {Qs,St2} = preprocess_quals(Llc, Qs0, St1), + {Y,Yps,St} = lc_tq(Llc, E, Qs, Mc, St2), {Y,Mps++Yps,St}; expr({op,L,'andalso',E1,E2}, St0) -> {#c_var{name=V0},St} = new_var(L, St0), @@ -889,133 +896,45 @@ fun_tq({_,_,Name}=Id, Cs0, L, St0, NameInfo) -> %% lc_tq(Line, Exp, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}. %% This TQ from Simon PJ pp 127-138. -%% This gets a bit messy as we must transform all directly here. We -%% recognise guard tests and try to fold them together and join to a -%% preceding generators, this should give us better and more compact -%% code. -lc_tq(Line, E, [{generate,Lg,P,G}|Qs0], Mc, St0) -> - {Gs,Qs1} = splitwith(fun is_guard_test/1, Qs0), +lc_tq(Line, E, [#igen{anno=GAnno,acc_pat=AccPat,acc_guard=AccGuard, + skip_pat=SkipPat,tail=Tail,tail_pat=TailPat, + arg={Pre,Arg}}|Qs], Mc, St0) -> {Name,St1} = new_fun_name("lc", St0), - {Head,St2} = new_var(St1), - {Tname,St3} = new_var_name(St2), - LA = lineno_anno(Line, St3), - LAnno = #a{anno=LA}, - Tail = #c_var{anno=LA,name=Tname}, - {Arg,St4} = new_var(St3), - {Nc,[],St5} = expr({call,Lg,{atom,Lg,Name},[{var,Lg,Tname}]}, St4), - {Guardc,St6} = lc_guard_tests(Gs, St5), %These are always flat! - {Lc,Lps,St7} = lc_tq(Line, E, Qs1, Nc, St6), - {Pc,St8} = list_gen_pattern(P, Line, St7), - {Gc,Gps,St9} = safe(G, St8), %Will be a function argument! - Fc = function_clause([Arg], LA, {Name,1}), - - %% Avoid constructing a default clause if the list comprehension - %% only has a variable as generator and there are no guard - %% tests. In other words, if the comprehension is equivalent to - %% lists:map/2. - Cs0 = case {Guardc, Pc} of - {[], #c_var{}} -> - [#iclause{anno=LAnno, - pats=[#c_literal{anno=LA,val=[]}],guard=[], - body=[Mc]}]; - _ -> - [#iclause{anno=#a{anno=[compiler_generated|LA]}, - pats=[ann_c_cons(LA, Head, Tail)], - guard=[], - body=[Nc]}, - #iclause{anno=LAnno, - pats=[#c_literal{anno=LA,val=[]}],guard=[], - body=[Mc]}] - end, - Cs = case Pc of - nomatch -> Cs0; - _ -> - [#iclause{anno=LAnno, - pats=[ann_c_cons(LA, Pc, Tail)], - guard=Guardc, - body=Lps ++ [Lc]}|Cs0] - end, - Fun = #ifun{anno=LAnno,id=[],vars=[Arg],clauses=Cs,fc=Fc}, - {#iletrec{anno=LAnno,defs=[{{Name,1},Fun}], - body=Gps ++ [#iapply{anno=LAnno, - op=#c_var{anno=LA,name={Name,1}}, - args=[Gc]}]}, - [],St9}; -lc_tq(Line, E, [{b_generate,Lg,P,G}|Qs0], Mc, St0) -> - {Gs,Qs1} = splitwith(fun is_guard_test/1, Qs0), - {Name,St1} = new_fun_name("blc", St0), LA = lineno_anno(Line, St1), LAnno = #a{anno=LA}, - HeadBinPattern = pattern(P, St1), - #c_binary{segments=Ps0} = HeadBinPattern, - {Ps,Tail,St2} = append_tail_segment(Ps0, St1), - {EPs,St3} = emasculate_segments(Ps, St2), - Pattern = HeadBinPattern#c_binary{segments=Ps}, - EPattern = HeadBinPattern#c_binary{segments=EPs}, - {Arg,St4} = new_var(St3), - {Guardc,St5} = lc_guard_tests(Gs, St4), %These are always flat! - Tname = Tail#c_var.name, - {Nc,[],St6} = expr({call,Lg,{atom,Lg,Name},[{var,Lg,Tname}]}, St5), - {Bc,Bps,St7} = lc_tq(Line, E, Qs1, Nc, St6), - {Gc,Gps,St10} = safe(G, St7), %Will be a function argument! - Fc = function_clause([Arg], LA, {Name,1}), - {TailSegList,_,St} = append_tail_segment([], St10), - Cs = [#iclause{anno=#a{anno=[compiler_generated|LA]}, - pats=[Pattern], - guard=Guardc, - body=Bps ++ [Bc]}, - #iclause{anno=#a{anno=[compiler_generated|LA]}, - pats=[EPattern], - guard=[], - body=[#iapply{anno=LAnno, - op=#c_var{anno=LA,name={Name,1}}, - args=[Tail]}]}, - #iclause{anno=LAnno, - pats=[#c_binary{anno=LA,segments=TailSegList}],guard=[], - body=[Mc]}], - Fun = #ifun{anno=LAnno,id=[],vars=[Arg],clauses=Cs,fc=Fc}, - {#iletrec{anno=LAnno,defs=[{{Name,1},Fun}], - body=Gps ++ [#iapply{anno=LAnno, - op=#c_var{anno=LA,name={Name,1}}, - args=[Gc]}]}, - [],St}; -lc_tq(Line, E, [Fil0|Qs0], Mc, St0) -> - %% Special case sequences guard tests. - LA = lineno_anno(element(2, Fil0), St0), - LAnno = #a{anno=LA}, - case is_guard_test(Fil0) of - true -> - {Gs0,Qs1} = splitwith(fun is_guard_test/1, Qs0), - {Lc,Lps,St1} = lc_tq(Line, E, Qs1, Mc, St0), - {Gs,St2} = lc_guard_tests([Fil0|Gs0], St1), %These are always flat! - {#icase{anno=LAnno, - args=[], - clauses=[#iclause{anno=LAnno,pats=[], - guard=Gs,body=Lps ++ [Lc]}], - fc=#iclause{anno=LAnno#a{anno=[compiler_generated|LA]}, - pats=[],guard=[],body=[Mc]}}, - [],St2}; - false -> - {Lc,Lps,St1} = lc_tq(Line, E, Qs0, Mc, St0), - {Fpat,St2} = new_var(St1), - Fc = fail_clause([Fpat], LA, - c_tuple([#c_literal{val=case_clause},Fpat])), - %% Do a novars little optimisation here. - {Filc,Fps,St3} = novars(Fil0, St2), - {#icase{anno=LAnno, - args=[Filc], - clauses=[#iclause{anno=LAnno, - pats=[#c_literal{anno=LA,val=true}], - guard=[], - body=Lps ++ [Lc]}, - #iclause{anno=LAnno#a{anno=[compiler_generated|LA]}, - pats=[#c_literal{anno=LA,val=false}], - guard=[], - body=[Mc]}], - fc=Fc}, - Fps,St3} - end; + F = #c_var{anno=LA,name={Name,1}}, + Nc = #iapply{anno=GAnno,op=F,args=[Tail]}, + {Var,St2} = new_var(St1), + Fc = function_clause([Var], LA, {Name,1}), + TailClause = #iclause{anno=LAnno,pats=[TailPat],guard=[],body=[Mc]}, + Cs0 = case {AccPat,AccGuard} of + {SkipPat,[]} -> + %% Skip and accumulator patterns are the same and there is + %% no guard, no need to generate a skip clause. + [TailClause]; + _ -> + [#iclause{anno=#a{anno=[compiler_generated|LA]}, + pats=[SkipPat],guard=[],body=[Nc]}, + TailClause] + end, + {Cs,St4} = case AccPat of + nomatch -> + %% The accumulator pattern never matches, no need + %% for an accumulator clause. + {Cs0,St2}; + _ -> + {Lc,Lps,St3} = lc_tq(Line, E, Qs, Nc, St2), + {[#iclause{anno=LAnno,pats=[AccPat],guard=AccGuard, + body=Lps ++ [Lc]}|Cs0], + St3} + end, + Fun = #ifun{anno=LAnno,id=[],vars=[Var],clauses=Cs,fc=Fc}, + {#iletrec{anno=LAnno#a{anno=[list_comprehension|LA]},defs=[{{Name,1},Fun}], + body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg]}]}, + [],St4}; +lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) -> + filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5); lc_tq(Line, E0, [], Mc0, St0) -> {H1,Hps,St1} = safe(E0, St0), {T1,Tps,St} = force_safe(Mc0, St1), @@ -1025,143 +944,60 @@ lc_tq(Line, E0, [], Mc0, St0) -> %% bc_tq(Line, Exp, [Qualifier], More, State) -> {LetRec,[PreExp],State}. %% This TQ from Gustafsson ERLANG'05. -%% This gets a bit messy as we must transform all directly here. We -%% recognise guard tests and try to fold them together and join to a -%% preceding generators, this should give us better and more compact -%% code. %% More could be transformed before calling bc_tq. -bc_tq(Line, Exp, Qualifiers, _, St0) -> +bc_tq(Line, Exp, Qs0, _, St0) -> {BinVar,St1} = new_var(St0), - {Sz,SzPre,St2} = bc_initial_size(Exp, Qualifiers, St1), - {E,BcPre,St} = bc_tq1(Line, Exp, Qualifiers, BinVar, St2), + {Sz,SzPre,St2} = bc_initial_size(Exp, Qs0, St1), + {Qs,St3} = preprocess_quals(Line, Qs0, St2), + {E,BcPre,St} = bc_tq1(Line, Exp, Qs, BinVar, St3), Pre = SzPre ++ [#iset{var=BinVar, arg=#iprimop{name=#c_literal{val=bs_init_writable}, args=[Sz]}}] ++ BcPre, {E,Pre,St}. -bc_tq1(Line, E, [{generate,Lg,P,G}|Qs0], AccExpr, St0) -> - {Gs,Qs1} = splitwith(fun is_guard_test/1, Qs0), - {Name,St1} = new_fun_name("lbc", St0), - LA = lineno_anno(Line, St1), - {[Head,Tail,AccVar],St2} = new_vars(LA, 3, St1), - LAnno = #a{anno=LA}, - {Arg,St3} = new_var(St2), - NewMore = {call,Lg,{atom,Lg,Name},[{var,Lg,Tail#c_var.name}, - {var,Lg,AccVar#c_var.name}]}, - {Guardc,St4} = lc_guard_tests(Gs, St3), %These are always flat! - {Lc,Lps,St5} = bc_tq1(Line, E, Qs1, AccVar, St4), - {Nc,Nps,St6} = expr(NewMore, St5), - {Pc,St7} = list_gen_pattern(P, Line, St6), - {Gc,Gps,St8} = safe(G, St7), %Will be a function argument! - Fc = function_clause([Arg,AccVar], LA, {Name,2}), - Cs0 = case {Guardc, Pc} of - {[], #c_var{}} -> - [#iclause{anno=LAnno, - pats=[#c_literal{anno=LA,val=[]},AccVar],guard=[], - body=[AccVar]}]; - _ -> - [#iclause{anno=#a{anno=[compiler_generated|LA]}, - pats=[ann_c_cons(LA, Head, Tail),AccVar], - guard=[], - body=Nps ++ [Nc]}, - #iclause{anno=LAnno, - pats=[#c_literal{anno=LA,val=[]},AccVar],guard=[], - body=[AccVar]}] - end, - Cs = case Pc of - nomatch -> Cs0; - _ -> - Body = Lps ++ Nps ++ [#iset{var=AccVar,arg=Lc},Nc], - [#iclause{anno=LAnno, - pats=[ann_c_cons(LA,Pc,Tail),AccVar], - guard=Guardc, - body=Body}|Cs0] - end, - Fun = #ifun{anno=LAnno,id=[],vars=[Arg,AccVar],clauses=Cs,fc=Fc}, - {#iletrec{anno=LAnno,defs=[{{Name,2},Fun}], - body=Gps ++ [#iapply{anno=LAnno, - op=#c_var{anno=LA,name={Name,2}}, - args=[Gc,AccExpr]}]}, - [],St8}; -bc_tq1(Line, E, [{b_generate,Lg,P,G}|Qs0], AccExpr, St0) -> - {Gs,Qs1} = splitwith(fun is_guard_test/1, Qs0), +bc_tq1(Line, E, [#igen{anno=GAnno,acc_pat=AccPat,acc_guard=AccGuard, + skip_pat=SkipPat,tail=Tail,tail_pat=TailPat, + arg={Pre,Arg}}|Qs], Mc, St0) -> {Name,St1} = new_fun_name("lbc", St0), LA = lineno_anno(Line, St1), - {AccVar,St2} = new_var(LA, St1), - LAnno = #a{anno=LA}, - HeadBinPattern = pattern(P, St2), - #c_binary{segments=Ps0} = HeadBinPattern, - {Ps,Tail,St3} = append_tail_segment(Ps0, St2), - {EPs,St4} = emasculate_segments(Ps, St3), - Pattern = HeadBinPattern#c_binary{segments=Ps}, - EPattern = HeadBinPattern#c_binary{segments=EPs}, - {Arg,St5} = new_var(St4), - NewMore = {call,Lg,{atom,Lg,Name},[{var,Lg,Tail#c_var.name}, - {var,Lg,AccVar#c_var.name}]}, - {Guardc,St6} = lc_guard_tests(Gs, St5), %These are always flat! - {Bc,Bps,St7} = bc_tq1(Line, E, Qs1, AccVar, St6), - {Nc,Nps,St8} = expr(NewMore, St7), - {Gc,Gps,St9} = safe(G, St8), %Will be a function argument! - Fc = function_clause([Arg,AccVar], LA, {Name,2}), - Body = Bps ++ Nps ++ [#iset{var=AccVar,arg=Bc},Nc], - {TailSegList,_,St} = append_tail_segment([], St9), - Cs = [#iclause{anno=LAnno, - pats=[Pattern,AccVar], - guard=Guardc, - body=Body}, - #iclause{anno=#a{anno=[compiler_generated|LA]}, - pats=[EPattern,AccVar], - guard=[], - body=Nps ++ [Nc]}, - #iclause{anno=LAnno, - pats=[#c_binary{anno=LA,segments=TailSegList},AccVar], - guard=[], - body=[AccVar]}], - Fun = #ifun{anno=LAnno,id=[],vars=[Arg,AccVar],clauses=Cs,fc=Fc}, - {#iletrec{anno=LAnno,defs=[{{Name,2},Fun}], - body=Gps ++ [#iapply{anno=LAnno, - op=#c_var{anno=LA,name={Name,2}}, - args=[Gc,AccExpr]}]}, - [],St}; -bc_tq1(Line, E, [Fil0|Qs0], AccVar, St0) -> - %% Special case sequences guard tests. - LA = lineno_anno(element(2, Fil0), St0), LAnno = #a{anno=LA}, - case is_guard_test(Fil0) of - true -> - {Gs0,Qs1} = splitwith(fun is_guard_test/1, Qs0), - {Bc,Bps,St1} = bc_tq1(Line, E, Qs1, AccVar, St0), - {Gs,St} = lc_guard_tests([Fil0|Gs0], St1), %These are always flat! - {#icase{anno=LAnno, - args=[], - clauses=[#iclause{anno=LAnno, - pats=[], - guard=Gs,body=Bps ++ [Bc]}], - fc=#iclause{anno=LAnno#a{anno=[compiler_generated|LA]}, - pats=[],guard=[],body=[AccVar]}}, - [],St}; - false -> - {Bc,Bps,St1} = bc_tq1(Line, E, Qs0, AccVar, St0), - {Fpat,St2} = new_var(St1), - Fc = fail_clause([Fpat], LA, - c_tuple([#c_literal{val=case_clause},Fpat])), - %% Do a novars little optimisation here. - {Filc,Fps,St} = novars(Fil0, St2), - {#icase{anno=LAnno, - args=[Filc], - clauses=[#iclause{anno=LAnno, - pats=[#c_literal{anno=LA,val=true}], - guard=[], - body=Bps ++ [Bc]}, - #iclause{anno=LAnno#a{anno=[compiler_generated|LA]}, - pats=[#c_literal{anno=LA,val=false}], - guard=[], - body=[AccVar]}], - fc=Fc}, - Fps,St} - end; + {Vars=[_,AccVar],St2} = new_vars(LA, 2, St1), + F = #c_var{anno=LA,name={Name,2}}, + Nc = #iapply{anno=GAnno,op=F,args=[Tail,AccVar]}, + Fc = function_clause(Vars, LA, {Name,2}), + TailClause = #iclause{anno=LAnno,pats=[TailPat,AccVar],guard=[], + body=[AccVar]}, + Cs0 = case {AccPat,AccGuard} of + {SkipPat,[]} -> + %% Skip and accumulator patterns are the same and there is + %% no guard, no need to generate a skip clause. + [TailClause]; + _ -> + [#iclause{anno=#a{anno=[compiler_generated|LA]}, + pats=[SkipPat,AccVar],guard=[],body=[Nc]}, + TailClause] + end, + {Cs,St4} = case AccPat of + nomatch -> + %% The accumulator pattern never matches, no need + %% for an accumulator clause. + {Cs0,St2}; + _ -> + {Bc,Bps,St3} = bc_tq1(Line, E, Qs, AccVar, St2), + Body = Bps ++ [#iset{var=AccVar,arg=Bc},Nc], + {[#iclause{anno=LAnno, + pats=[AccPat,AccVar],guard=AccGuard, + body=Body}|Cs0], + St3} + end, + Fun = #ifun{anno=LAnno,id=[],vars=Vars,clauses=Cs,fc=Fc}, + {#iletrec{anno=LAnno#a{anno=[list_comprehension|LA]},defs=[{{Name,2},Fun}], + body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg,Mc]}]}, + [],St4}; +bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) -> + filter_tq(Line, E, Filter, Mc, St, Qs, fun bc_tq1/5); bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) -> {E,Pre,St} = expr({bin,Bl,[{bin_element,Bl, {var,Bl,AccVar#c_var.name}, @@ -1169,16 +1005,154 @@ bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) -> [binary,{unit,1}]}|Elements]}, St0), #a{anno=A} = Anno0 = get_anno(E), Anno = Anno0#a{anno=[compiler_generated,single_use|A]}, - %%Anno = Anno0#a{anno=[compiler_generated|A]}, {set_anno(E, Anno),Pre,St}. +%% filter_tq(Line, Expr, Filter, Mc, State, [Qualifier], TqFun) -> +%% {Case,[PreExpr],State}. +%% Transform an intermediate comprehension filter to its intermediate case +%% representation. + +filter_tq(Line, E, #ifilter{anno=#a{anno=LA}=LAnno,arg={Pre,Arg}}, + Mc, St0, Qs, TqFun) -> + %% The filter is an expression, it is compiled to a case of degree 1 with + %% 3 clauses, one accumulating, one skipping and the final one throwing + %% {case_clause,Value} where Value is the result of the filter and is not a + %% boolean. + {Lc,Lps,St1} = TqFun(Line, E, Qs, Mc, St0), + {FailPat,St2} = new_var(St1), + Fc = fail_clause([FailPat], LA, + c_tuple([#c_literal{val=case_clause},FailPat])), + {#icase{anno=LAnno#a{anno=[list_comprehension|LA]},args=[Arg], + clauses=[#iclause{anno=LAnno, + pats=[#c_literal{val=true}],guard=[], + body=Lps ++ [Lc]}, + #iclause{anno=LAnno#a{anno=[compiler_generated|LA]}, + pats=[#c_literal{val=false}],guard=[], + body=[Mc]}], + fc=Fc}, + Pre,St2}; +filter_tq(Line, E, #ifilter{anno=#a{anno=LA}=LAnno,arg=Guard}, + Mc, St0, Qs, TqFun) when is_list(Guard) -> + %% Otherwise it is a guard, compiled to a case of degree 0 with 2 clauses, + %% the first matches if the guard succeeds and the comprehension continues + %% or the second one is selected and the current element is skipped. + {Lc,Lps,St1} = TqFun(Line, E, Qs, Mc, St0), + {#icase{anno=LAnno#a{anno=[list_comprehension|LA]},args=[], + clauses=[#iclause{anno=LAnno,pats=[],guard=Guard,body=Lps ++ [Lc]}], + fc=#iclause{anno=LAnno#a{anno=[compiler_generated|LA]}, + pats=[],guard=[],body=[Mc]}}, + [],St1}. + +%% preprocess_quals(Line, [Qualifier], State) -> {[Qualifier'],State}. +%% Preprocess a list of Erlang qualifiers into its intermediate representation, +%% represented as a list of #igen{} and #ifilter{} records. We recognise guard +%% tests and try to fold them together and join to a preceding generators, this +%% should give us better and more compact code. + +preprocess_quals(Line, Qs, St) -> + preprocess_quals(Line, Qs, St, []). + +preprocess_quals(Line, [Q|Qs0], St0, Acc) -> + case is_generator(Q) of + true -> + {Gs,Qs} = splitwith(fun is_guard_test/1, Qs0), + {Gen,St} = generator(Line, Q, Gs, St0), + preprocess_quals(Line, Qs, St, [Gen|Acc]); + false -> + LAnno = #a{anno=lineno_anno(get_anno(Q), St0)}, + case is_guard_test(Q) of + true -> + %% When a filter is a guard test, its argument in the + %% #ifilter{} record is a list as returned by + %% lc_guard_tests/2. + {Gs,Qs} = splitwith(fun is_guard_test/1, Qs0), + {Cg,St} = lc_guard_tests([Q|Gs], St0), + Filter = #ifilter{anno=LAnno,arg=Cg}, + preprocess_quals(Line, Qs, St, [Filter|Acc]); + false -> + %% Otherwise, it is a pair {Pre,Arg} as in a generator + %% input. + {Ce,Pre,St} = novars(Q, St0), + Filter = #ifilter{anno=LAnno,arg={Pre,Ce}}, + preprocess_quals(Line, Qs0, St, [Filter|Acc]) + end + end; +preprocess_quals(_, [], St, Acc) -> + {reverse(Acc),St}. + +is_generator({generate,_,_,_}) -> true; +is_generator({b_generate,_,_,_}) -> true; +is_generator(_) -> false. + +%% +%% Generators are abstracted as sextuplets: +%% - acc_pat is the accumulator pattern, e.g. [Pat|Tail] for Pat <- Expr. +%% - acc_guard is the list of guards immediately following the current +%% generator in the qualifier list input. +%% - skip_pat is the skip pattern, e.g. <<X,_:X,Tail/bitstring>> for +%% <<X,1:X>> <= Expr. +%% - tail is the variable used in AccPat and SkipPat bound to the rest of the +%% generator input. +%% - tail_pat is the tail pattern, respectively [] and <<_/bitstring>> for list +%% and bit string generators. +%% - arg is a pair {Pre,Arg} where Pre is the list of expressions to be +%% inserted before the comprehension function and Arg is the expression +%% that it should be passed. +%% + +%% generator(Line, Generator, Guard, State) -> {Generator',State}. +%% Transform a given generator into its #igen{} representation. + +generator(Line, {generate,Lg,P0,E}, Gs, St0) -> + LA = lineno_anno(Line, St0), + GA = lineno_anno(Lg, St0), + {Head,St1} = list_gen_pattern(P0, Line, St0), + {[Tail,Skip],St2} = new_vars(2, St1), + {Cg,St3} = lc_guard_tests(Gs, St2), + {AccPat,SkipPat} = case Head of + #c_var{} -> + %% If the generator pattern is a variable, the + %% pattern from the accumulator clause can be + %% reused in the skip one. lc_tq and bc_tq1 takes + %% care of dismissing the latter in that case. + Cons = ann_c_cons(LA, Head, Tail), + {Cons,Cons}; + nomatch -> + %% If it never matches, there is no need for + %% an accumulator clause. + {nomatch,ann_c_cons(LA, Skip, Tail)}; + _ -> + {ann_c_cons(LA, Head, Tail), + ann_c_cons(LA, Skip, Tail)} + end, + {Ce,Pre,St4} = safe(E, St3), + Gen = #igen{anno=#a{anno=GA},acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat, + tail=Tail,tail_pat=#c_literal{anno=LA,val=[]},arg={Pre,Ce}}, + {Gen,St4}; +generator(Line, {b_generate,Lg,P,E}, Gs, St0) -> + LA = lineno_anno(Line, St0), + GA = lineno_anno(Lg, St0), + Cp = #c_binary{segments=Segs} = pattern(P, St0), + %% The function append_tail_segment/2 keeps variable patterns as-is, making + %% it possible to have the same skip clause removal as with list generators. + {AccSegs,Tail,TailSeg,St1} = append_tail_segment(Segs, St0), + AccPat = Cp#c_binary{segments=AccSegs}, + {Cg,St2} = lc_guard_tests(Gs, St1), + {SkipSegs,St3} = emasculate_segments(AccSegs, St2), + SkipPat = Cp#c_binary{segments=SkipSegs}, + {Ce,Pre,St4} = safe(E, St3), + Gen = #igen{anno=#a{anno=GA},acc_pat=AccPat,acc_guard=Cg,skip_pat=SkipPat, + tail=Tail,tail_pat=#c_binary{anno=LA,segments=[TailSeg]}, + arg={Pre,Ce}}, + {Gen,St4}. + append_tail_segment(Segs, St0) -> {Var,St} = new_var(St0), Tail = #c_bitstr{val=Var,size=#c_literal{val=all}, unit=#c_literal{val=1}, type=#c_literal{val=binary}, flags=#c_literal{val=[unsigned,big]}}, - {Segs++[Tail],Var,St}. + {Segs++[Tail],Var,Tail,St}. emasculate_segments(Segs, St) -> emasculate_segments(Segs, St, []). @@ -1189,7 +1163,7 @@ emasculate_segments([B|Rest], St0, Acc) -> {Var,St1} = new_var(St0), emasculate_segments(Rest, St1, [B#c_bitstr{val=Var}|Acc]); emasculate_segments([], St, Acc) -> - {lists:reverse(Acc),St}. + {reverse(Acc),St}. lc_guard_tests([], St) -> {[],St}; lc_guard_tests(Gs0, St0) -> @@ -1588,15 +1562,6 @@ pat_alias_list(_, _) -> throw(nomatch). pattern_list(Ps, St) -> [pattern(P, St) || P <- Ps]. -%% first([A]) -> [A]. -%% last([A]) -> A. - -first([_]) -> []; -first([H|T]) -> [H|first(T)]. - -last([L]) -> L; -last([_|T]) -> last(T). - %% make_vars([Name]) -> [{Var,Name}]. make_vars(Vs) -> [ #c_var{name=V} || V <- Vs ]. @@ -1679,13 +1644,13 @@ uclause(#iclause{anno=Anno,pats=Ps0,guard=G0,body=B0}, Pks, Ks0, St0) -> uguard([], [], _, St) -> {[],St}; uguard(Pg, [], Ks, St) -> %% No guard, so fold together equality tests. - uguard(first(Pg), [last(Pg)], Ks, St); + uguard(droplast(Pg), [last(Pg)], Ks, St); uguard(Pg, Gs0, Ks, St0) -> %% Gs0 must contain at least one element here. {Gs3,St5} = foldr(fun (T, {Gs1,St1}) -> {L,St2} = new_var(St1), {R,St3} = new_var(St2), - {[#iset{var=L,arg=T}] ++ first(Gs1) ++ + {[#iset{var=L,arg=T}] ++ droplast(Gs1) ++ [#iset{var=R,arg=last(Gs1)}, #icall{anno=#a{}, %Must have an #a{} module=#c_literal{val=erlang}, @@ -2088,7 +2053,8 @@ cexpr(#ifun{anno=#a{us=Us0}=A0,name={named,Name},fc=#iclause{pats=Ps}}=Fun0, RecVar = #c_var{name={Name,length(Ps)}}, Let = #c_let{vars=[#c_var{name=Name}],arg=RecVar,body=Body}, CFun1 = CFun0#c_fun{body=Let}, - Letrec = #c_letrec{defs=[{RecVar,CFun1}], + Letrec = #c_letrec{anno=A0#a.anno, + defs=[{RecVar,CFun1}], body=RecVar}, {Letrec,[],Us1,St1} end; diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl index 9a2b1605ad..bc5ca0314a 100644 --- a/lib/compiler/src/v3_kernel.erl +++ b/lib/compiler/src/v3_kernel.erl @@ -81,7 +81,7 @@ -export([module/2,format_error/1]). -import(lists, [map/2,foldl/3,foldr/3,mapfoldl/3,splitwith/2,member/2, - keymember/3,keyfind/3,partition/2]). + keymember/3,keyfind/3,partition/2,droplast/1,last/1]). -import(ordsets, [add_element/2,del_element/2,union/2,union/1,subtract/2]). -import(cerl, [c_tuple/1]). @@ -274,8 +274,7 @@ expr(#c_tuple{anno=A,es=Ces}, Sub, St0) -> {#k_tuple{anno=A,es=Kes},Ep,St1}; expr(#c_map{anno=A,var=Var0,es=Ces}, Sub, St0) -> {Var,[],St1} = expr(Var0, Sub, St0), - {Kes,Ep,St2} = map_pairs(Ces, Sub, St1), - {#k_map{anno=A,var=Var,es=Kes},Ep,St2}; + map_split_pairs(A, Var, Ces, Sub, St1); expr(#c_binary{anno=A,segments=Cv}, Sub, St0) -> try atomic_bin(Cv, Sub, St0) of {Kv,Ep,St1} -> @@ -351,7 +350,7 @@ expr(#c_case{arg=Ca,clauses=Ccs}, Sub, St0) -> {Kvs,Pv,St2} = match_vars(Ka, St1), %Must have variables here! {Km,St3} = kmatch(Kvs, Ccs, Sub, St2), Match = flatten_seq(build_match(Kvs, Km)), - {last(Match),Pa ++ Pv ++ first(Match),St3}; + {last(Match),Pa ++ Pv ++ droplast(Match),St3}; expr(#c_receive{anno=A,clauses=Ccs0,timeout=Ce,action=Ca}, Sub, St0) -> {Ke,Pe,St1} = atomic(Ce, Sub, St0), %Force this to be atomic! {Rvar,St2} = new_var(St1), @@ -497,15 +496,71 @@ translate_match_fail_1(Anno, As, Sub, #kern{ff=FF}) -> translate_fc(Args) -> [#c_literal{val=function_clause},make_list(Args)]. -%% FIXME: Not completed -map_pairs(Es, Sub, St) -> - foldr(fun - (#c_map_pair{op=#c_literal{val=Op},key=K0,val=V0}, {Kes,Esp,St0}) when - Op =:= assoc; Op =:= exact -> %% assert Op - {K,[],St1} = expr(K0, Sub, St0), - {V,Ep,St2} = atomic(V0, Sub, St1), - {[#k_map_pair{op=Op,key=K,val=V}|Kes],Ep ++ Esp,St2} - end, {[],[],St}, Es). + %{Kes,Ep,St2} = map_pairs(Ces, Sub, St1), +map_split_pairs(A, Var, Ces, Sub, St0) -> + %% two steps + %% 1. force variables + %% 2. remove multiples + Pairs0 = [{Op,K,V} || #c_map_pair{op=#c_literal{val=Op},key=K,val=V} <- Ces], + {Pairs,Esp,St1} = foldr(fun + ({Op,K0,V0}, {Ops,Espi,Sti0}) when Op =:= assoc; Op =:= exact -> + {K,[],Sti1} = expr(K0, Sub, Sti0), + {V,Ep,Sti2} = atomic(V0, Sub, Sti1), + {[{Op,K,V}|Ops],Ep ++ Espi,Sti2} + end, {[],[],St0}, Pairs0), + + case map_group_pairs(Pairs) of + {Assoc,[]} -> + Kes = [#k_map_pair{key=K,val=V}||{_,{assoc,K,V}} <- Assoc], + {#k_map{anno=A,op=assoc,var=Var,es=Kes},Esp,St1}; + {[],Exact} -> + Kes = [#k_map_pair{key=K,val=V}||{_,{exact,K,V}} <- Exact], + {#k_map{anno=A,op=exact,var=Var,es=Kes},Esp,St1}; + {Assoc,Exact} -> + Kes1 = [#k_map_pair{key=K,val=V}||{_,{assoc,K,V}} <- Assoc], + {Mvar,Em,St2} = force_atomic(#k_map{anno=A,op=assoc,var=Var,es=Kes1},St1), + Kes2 = [#k_map_pair{key=K,val=V}||{_,{exact,K,V}} <- Exact], + {#k_map{anno=A,op=exact,var=Mvar,es=Kes2},Em ++ Esp,St2} + + end. + +%% Group map by Assoc operations and Exact operations + +map_group_pairs(Es) -> + Groups = dict:to_list(map_group_pairs(Es,dict:new())), + partition(fun({_,{Op,_,_}}) -> Op =:= assoc end, Groups). + +map_group_pairs([{assoc,K,V}|Es0],Used0) -> + Used1 = case map_key_is_used(K,Used0) of + {ok, {assoc,_,_}} -> map_key_set_used(K,{assoc,K,V},Used0); + {ok, {exact,_,_}} -> map_key_set_used(K,{exact,K,V},Used0); + _ -> map_key_set_used(K,{assoc,K,V},Used0) + end, + map_group_pairs(Es0,Used1); +map_group_pairs([{exact,K,V}|Es0],Used0) -> + Used1 = case map_key_is_used(K,Used0) of + {ok, {assoc,_,_}} -> map_key_set_used(K,{assoc,K,V},Used0); + {ok, {exact,_,_}} -> map_key_set_used(K,{exact,K,V},Used0); + _ -> map_key_set_used(K,{exact,K,V},Used0) + end, + map_group_pairs(Es0,Used1); +map_group_pairs([],Used) -> + Used. + +map_key_set_used(K,How,Used) -> + dict:store(map_key_clean(K),How,Used). + +map_key_is_used(K,Used) -> + dict:find(map_key_clean(K),Used). + +%% Be explicit instead of using set_kanno(K,[]) +map_key_clean(#k_literal{val=V}) -> {k_literal,V}; +map_key_clean(#k_int{val=V}) -> {k_int,V}; +map_key_clean(#k_float{val=V}) -> {k_float,V}; +map_key_clean(#k_atom{val=V}) -> {k_atom,V}; +map_key_clean(#k_nil{}) -> k_nil; +map_key_clean(#k_var{name=V}) -> {k_var,V}. + %% call_type(Module, Function, Arity) -> call | bif | apply | error. %% Classify the call. @@ -664,11 +719,11 @@ pattern(#c_tuple{anno=A,es=Ces}, Isub, Osub0, St0) -> {#k_tuple{anno=A,es=Kes},Osub1,St1}; pattern(#c_map{anno=A,es=Ces}, Isub, Osub0, St0) -> {Kes,Osub1,St1} = pattern_list(Ces, Isub, Osub0, St0), - {#k_map{anno=A,es=Kes},Osub1,St1}; + {#k_map{anno=A,op=exact,es=Kes},Osub1,St1}; pattern(#c_map_pair{op=#c_literal{val=exact},anno=A,key=Ck,val=Cv},Isub, Osub0, St0) -> {Kk,Osub1,St1} = pattern(Ck, Isub, Osub0, St0), {Kv,Osub2,St2} = pattern(Cv, Isub, Osub1, St1), - {#k_map_pair{anno=A,op=exact,key=Kk,val=Kv},Osub2,St2}; + {#k_map_pair{anno=A,key=Kk,val=Kv},Osub2,St2}; pattern(#c_binary{anno=A,segments=Cv}, Isub, Osub0, St0) -> {Kv,Osub1,St1} = pattern_bin(Cv, Isub, Osub0, St0), {#k_binary{anno=A,segs=Kv},Osub1,St1}; @@ -847,15 +902,6 @@ foldr2(Fun, Acc0, [E1|L1], [E2|L2]) -> foldr2(Fun, Acc1, L1, L2); foldr2(_, Acc, [], []) -> Acc. -%% first([A]) -> [A]. -%% last([A]) -> A. - -last([L]) -> L; -last([_|T]) -> last(T). - -first([_]) -> []; -first([H|T]) -> [H|first(T)]. - %% This code implements the algorithm for an optimizing compiler for %% pattern matching given "The Implementation of Functional %% Programming Languages" by Simon Peyton Jones. The code is much @@ -1365,9 +1411,8 @@ new_clauses(Cs0, U, St) -> [S,N|As]; #k_bin_int{next=N} -> [N|As]; - #k_map{es=Es} -> - Vals = [V || - #k_map_pair{op=exact,val=V} <- Es], + #k_map{op=exact,es=Es} -> + Vals = [V || #k_map_pair{val=V} <- Es], Vals ++ As; _Other -> As @@ -1467,9 +1512,9 @@ arg_val(Arg, C) -> _ -> {set_kanno(S, []),U,T,Fs} end; - #k_map{es=Es} -> + #k_map{op=exact,es=Es} -> Keys = [begin - #k_map_pair{op=exact,key=#k_literal{val=Key}} = Pair, + #k_map_pair{key=#k_literal{val=Key}} = Pair, Key end || Pair <- Es], %% multiple keys may have the same name @@ -1885,7 +1930,7 @@ pat_vars(#k_tuple{es=Es}) -> pat_list_vars(Es); pat_vars(#k_map{es=Es}) -> pat_list_vars(Es); -pat_vars(#k_map_pair{op=exact,val=V}) -> +pat_vars(#k_map_pair{val=V}) -> pat_vars(V). pat_list_vars(Ps) -> diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl index c7886a070d..ab66445f73 100644 --- a/lib/compiler/src/v3_kernel.hrl +++ b/lib/compiler/src/v3_kernel.hrl @@ -38,8 +38,8 @@ -record(k_nil, {anno=[]}). -record(k_tuple, {anno=[],es}). --record(k_map, {anno=[],var,es}). --record(k_map_pair, {anno=[],op,key,val}). +-record(k_map, {anno=[],var,op,es}). +-record(k_map_pair, {anno=[],key,val}). -record(k_cons, {anno=[],hd,tl}). -record(k_binary, {anno=[],segs}). -record(k_bin_seg, {anno=[],size,unit,type,flags,seg,next}). diff --git a/lib/compiler/src/v3_kernel_pp.erl b/lib/compiler/src/v3_kernel_pp.erl index edbd3f74f8..639c6737e2 100644 --- a/lib/compiler/src/v3_kernel_pp.erl +++ b/lib/compiler/src/v3_kernel_pp.erl @@ -110,15 +110,18 @@ format_1(#k_map{var=#k_var{}=Var,es=Es}, Ctxt) -> " | ",format_1(Var, Ctxt), $},$~ ]; -format_1(#k_map{es=Es}, Ctxt) -> - [$~,${, +format_1(#k_map{op=assoc,es=Es}, Ctxt) -> + ["~{", format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), - $},$~ + "}~" + ]; +format_1(#k_map{op=exact,es=Es}, Ctxt) -> + ["::{", + format_hseq(Es, ",", ctxt_bump_indent(Ctxt, 1), fun format/2), + "}::" ]; -format_1(#k_map_pair{op=assoc,key=K,val=V}, Ctxt) -> - ["~<",format(K, Ctxt),",",format(V, Ctxt),">"]; -format_1(#k_map_pair{op=exact,key=K,val=V}, Ctxt) -> - ["::<",format(K, Ctxt),",",format(V, Ctxt),">"]; +format_1(#k_map_pair{key=K,val=V}, Ctxt) -> + ["<",format(K, Ctxt),",",format(V, Ctxt),">"]; format_1(#k_binary{segs=S}, Ctxt) -> ["#<",format(S, ctxt_bump_indent(Ctxt, 2)),">#"]; format_1(#k_bin_seg{next=Next}=S, Ctxt) -> diff --git a/lib/compiler/src/v3_life.erl b/lib/compiler/src/v3_life.erl index ae928e955c..c4f54a7970 100644 --- a/lib/compiler/src/v3_life.erl +++ b/lib/compiler/src/v3_life.erl @@ -367,12 +367,10 @@ literal(#k_bin_end{}, Ctxt) -> {bin_end,Ctxt}; literal(#k_tuple{es=Es}, Ctxt) -> {tuple,literal_list(Es, Ctxt)}; -literal(#k_map{var=Var,es=Es}, Ctxt) -> - {map,literal(Var, Ctxt),literal_list(Es, Ctxt)}; -literal(#k_map_pair{op=assoc,key=K,val=V}, Ctxt) -> - {map_pair_assoc,literal(K, Ctxt),literal(V, Ctxt)}; -literal(#k_map_pair{op=exact,key=K,val=V}, Ctxt) -> - {map_pair_exact,literal(K, Ctxt),literal(V, Ctxt)}; +literal(#k_map{op=Op,var=Var,es=Es}, Ctxt) -> + {map,Op,literal(Var, Ctxt),literal_list(Es, Ctxt)}; +literal(#k_map_pair{key=K,val=V}, Ctxt) -> + {map_pair,literal(K, Ctxt),literal(V, Ctxt)}; literal(#k_literal{val=V}, _Ctxt) -> {literal,V}. @@ -402,8 +400,8 @@ literal2(#k_bin_end{}, Ctxt) -> {bin_end,Ctxt}; literal2(#k_tuple{es=Es}, Ctxt) -> {tuple,literal_list2(Es, Ctxt)}; -literal2(#k_map{es=Es}, Ctxt) -> - {map,literal_list2(Es, Ctxt)}; +literal2(#k_map{op=Op,es=Es}, Ctxt) -> + {map,Op,literal_list2(Es, Ctxt)}; literal2(#k_map_pair{key=K,val=V}, Ctxt) -> {map_pair,literal2(K, Ctxt),literal2(V, Ctxt)}. diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index 51b3064589..39c9fea5d9 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -26,6 +26,7 @@ MODULES= \ guard_SUITE \ inline_SUITE \ lc_SUITE \ + map_SUITE \ match_SUITE \ misc_SUITE \ num_bif_SUITE \ @@ -47,6 +48,7 @@ NO_OPT= \ fun \ guard \ lc \ + map \ match \ misc \ num_bif \ diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index 9f15845d33..149b9bbb8f 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -34,7 +34,7 @@ otp_7188/1,otp_7233/1,otp_7240/1,otp_7498/1, match_string/1,zero_width/1,bad_size/1,haystack/1, cover_beam_bool/1,matched_out_size/1,follow_fail_branch/1, - no_partition/1]). + no_partition/1,calling_a_binary/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -59,7 +59,7 @@ groups() -> matching_and_andalso,otp_7188,otp_7233,otp_7240, otp_7498,match_string,zero_width,bad_size,haystack, cover_beam_bool,matched_out_size,follow_fail_branch, - no_partition]}]. + no_partition,calling_a_binary]}]. init_per_suite(Config) -> @@ -1178,6 +1178,17 @@ no_partition_2([], a5) -> no_partition_2(42.0, a6) -> six. +calling_a_binary(Config) when is_list(Config) -> + [] = call_binary(<<>>, []), + {'EXIT',{badarg,_}} = (catch call_binary(<<1>>, [])), + {'EXIT',{badarg,_}} = (catch call_binary(<<1,2,3>>, [])), + ok. + +call_binary(<<>>, Acc) -> + Acc; +call_binary(<<H,T/bits>>, Acc) -> + T(<<Acc/binary,H>>). + check(F, R) -> R = F(). diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index de35ebc7bd..34c4b1e264 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -286,57 +286,67 @@ cond_and_ifdef(Config) when is_list(Config) -> ok. listings(Config) when is_list(Config) -> - ?line Dog = test_server:timetrap(test_server:minutes(8)), - ?line DataDir = ?config(data_dir, Config), - ?line PrivDir = ?config(priv_dir, Config), - ?line Simple = filename:join(DataDir, simple), - ?line TargetDir = filename:join(PrivDir, listings), - ?line ok = file:make_dir(TargetDir), + Dog = test_server:timetrap(test_server:minutes(8)), + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ok = do_file_listings(DataDir, PrivDir, [ + "simple", + "small", + "small_maps" + ]), + test_server:timetrap_cancel(Dog), + ok. + +do_file_listings(_, _, []) -> ok; +do_file_listings(DataDir, PrivDir, [File|Files]) -> + Simple = filename:join(DataDir, File), + TargetDir = filename:join(PrivDir, listings), + ok = file:make_dir(TargetDir), %% Test all dedicated listing options. - ?line do_listing(Simple, TargetDir, 'S'), - ?line do_listing(Simple, TargetDir, 'E'), - ?line do_listing(Simple, TargetDir, 'P'), - ?line do_listing(Simple, TargetDir, dpp, ".pp"), - ?line do_listing(Simple, TargetDir, dabstr, ".abstr"), - ?line do_listing(Simple, TargetDir, dexp, ".expand"), - ?line do_listing(Simple, TargetDir, dcore, ".core"), - ?line do_listing(Simple, TargetDir, doldinline, ".oldinline"), - ?line do_listing(Simple, TargetDir, dinline, ".inline"), - ?line do_listing(Simple, TargetDir, dcore, ".core"), - ?line do_listing(Simple, TargetDir, dcopt, ".copt"), - ?line do_listing(Simple, TargetDir, dsetel, ".dsetel"), - ?line do_listing(Simple, TargetDir, dkern, ".kernel"), - ?line do_listing(Simple, TargetDir, dlife, ".life"), - ?line do_listing(Simple, TargetDir, dcg, ".codegen"), - ?line do_listing(Simple, TargetDir, dblk, ".block"), - ?line do_listing(Simple, TargetDir, dbool, ".bool"), - ?line do_listing(Simple, TargetDir, dtype, ".type"), - ?line do_listing(Simple, TargetDir, ddead, ".dead"), - ?line do_listing(Simple, TargetDir, djmp, ".jump"), - ?line do_listing(Simple, TargetDir, dclean, ".clean"), - ?line do_listing(Simple, TargetDir, dpeep, ".peep"), - ?line do_listing(Simple, TargetDir, dopt, ".optimize"), + do_listing(Simple, TargetDir, 'S'), + do_listing(Simple, TargetDir, 'E'), + do_listing(Simple, TargetDir, 'P'), + do_listing(Simple, TargetDir, dpp, ".pp"), + do_listing(Simple, TargetDir, dabstr, ".abstr"), + do_listing(Simple, TargetDir, dexp, ".expand"), + do_listing(Simple, TargetDir, dcore, ".core"), + do_listing(Simple, TargetDir, doldinline, ".oldinline"), + do_listing(Simple, TargetDir, dinline, ".inline"), + do_listing(Simple, TargetDir, dcore, ".core"), + do_listing(Simple, TargetDir, dcopt, ".copt"), + do_listing(Simple, TargetDir, dsetel, ".dsetel"), + do_listing(Simple, TargetDir, dkern, ".kernel"), + do_listing(Simple, TargetDir, dlife, ".life"), + do_listing(Simple, TargetDir, dcg, ".codegen"), + do_listing(Simple, TargetDir, dblk, ".block"), + do_listing(Simple, TargetDir, dbool, ".bool"), + do_listing(Simple, TargetDir, dtype, ".type"), + do_listing(Simple, TargetDir, ddead, ".dead"), + do_listing(Simple, TargetDir, djmp, ".jump"), + do_listing(Simple, TargetDir, dclean, ".clean"), + do_listing(Simple, TargetDir, dpeep, ".peep"), + do_listing(Simple, TargetDir, dopt, ".optimize"), %% First clean up. - ?line Listings = filename:join(PrivDir, listings), - ?line lists:foreach(fun(F) -> ok = file:delete(F) end, - filelib:wildcard(filename:join(Listings, "*"))), + Listings = filename:join(PrivDir, listings), + lists:foreach(fun(F) -> ok = file:delete(F) end, + filelib:wildcard(filename:join(Listings, "*"))), %% Test options that produce a listing file if 'binary' is not given. - ?line do_listing(Simple, TargetDir, to_pp, ".P"), - ?line do_listing(Simple, TargetDir, to_exp, ".E"), - ?line do_listing(Simple, TargetDir, to_core0, ".core"), - ?line ok = file:delete(filename:join(Listings, "simple.core")), - ?line do_listing(Simple, TargetDir, to_core, ".core"), - ?line do_listing(Simple, TargetDir, to_kernel, ".kernel"), + do_listing(Simple, TargetDir, to_pp, ".P"), + do_listing(Simple, TargetDir, to_exp, ".E"), + do_listing(Simple, TargetDir, to_core0, ".core"), + ok = file:delete(filename:join(Listings, File ++ ".core")), + do_listing(Simple, TargetDir, to_core, ".core"), + do_listing(Simple, TargetDir, to_kernel, ".kernel"), %% Final clean up. - ?line lists:foreach(fun(F) -> ok = file:delete(F) end, - filelib:wildcard(filename:join(Listings, "*"))), - ?line ok = file:del_dir(Listings), - ?line test_server:timetrap_cancel(Dog), - ok. + lists:foreach(fun(F) -> ok = file:delete(F) end, + filelib:wildcard(filename:join(Listings, "*"))), + ok = file:del_dir(Listings), + + do_file_listings(DataDir,PrivDir,Files). listings_big(Config) when is_list(Config) -> ?line Dog = test_server:timetrap(test_server:minutes(10)), diff --git a/lib/compiler/test/compile_SUITE_data/small.erl b/lib/compiler/test/compile_SUITE_data/small.erl new file mode 100644 index 0000000000..37cd270e50 --- /dev/null +++ b/lib/compiler/test/compile_SUITE_data/small.erl @@ -0,0 +1,48 @@ +-module(small). + +-export([go/0,go/2]). + + +-small_attribute({value,3}). + +go() -> go(3, 3.0). +go(A,B) -> + V1 = A + B, + V2 = A * B, + V3 = V1 / V2, + V4 = V3 / 0.3, + V5 = V1 + V2 + V3 + V4, + try + R = call(<<"wazzup">>, A), + {A,B,V5,R,t(),recv()} + catch + C:E -> + {error, C, E} + end. + +-spec call(binary(), term()) -> binary(). + +call(<<"wa", B/binary>>,V) when is_integer(V) -> B; +call(B,_) -> B. + +t() -> + <<23:32, V:14, _:2, B/binary>> = id(<<"wazzup world">>), + {V,B}. + +recv() -> + F = fun() -> + receive + 1 -> ok; + 2 -> ok; + 3 -> ok; + a -> ok; + _ -> none + after 0 -> tmo + end + end, + tmo = F(), + ok. + + +id(I) -> I. + diff --git a/lib/compiler/test/compile_SUITE_data/small_maps.erl b/lib/compiler/test/compile_SUITE_data/small_maps.erl new file mode 100644 index 0000000000..a17a136a7d --- /dev/null +++ b/lib/compiler/test/compile_SUITE_data/small_maps.erl @@ -0,0 +1,16 @@ +-module(small_maps). + +-export([go/0,go/1]). + +go() -> + go(1337). + +go(V0) -> + M0 = #{ a => 1, val => V0}, + V1 = get_val(M0), + M1 = M0#{ val := [V0,V1] }, + {some_val,[1337,{some_val,1337}]} = get_val(M1), + ok. + +get_val(#{ "wazzup" := _, val := V}) -> V; +get_val(#{ val := V }) -> {some_val, V}. diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl index 1a521c3591..aa222c48de 100644 --- a/lib/compiler/test/core_SUITE.erl +++ b/lib/compiler/test/core_SUITE.erl @@ -24,7 +24,7 @@ dehydrated_itracer/1,nested_tries/1, seq_in_guard/1,make_effect_seq/1,eval_is_boolean/1, unsafe_case/1,nomatch_shadow/1,reversed_annos/1, - map_core_test/1]). + map_core_test/1,eval_case/1]). -include_lib("test_server/include/test_server.hrl"). @@ -50,7 +50,7 @@ groups() -> [{p,test_lib:parallel(), [dehydrated_itracer,nested_tries,seq_in_guard,make_effect_seq, eval_is_boolean,unsafe_case,nomatch_shadow,reversed_annos, - map_core_test + map_core_test,eval_case ]}]. @@ -76,6 +76,7 @@ end_per_group(_GroupName, Config) -> ?comp(nomatch_shadow). ?comp(reversed_annos). ?comp(map_core_test). +?comp(eval_case). try_it(Mod, Conf) -> Src = filename:join(?config(data_dir, Conf), atom_to_list(Mod)), diff --git a/lib/compiler/test/core_SUITE_data/eval_case.core b/lib/compiler/test/core_SUITE_data/eval_case.core new file mode 100644 index 0000000000..f2776e2b1f --- /dev/null +++ b/lib/compiler/test/core_SUITE_data/eval_case.core @@ -0,0 +1,34 @@ +module 'eval_case' ['eval_case'/0] + attributes [] +'eval_case'/0 = + fun () -> + case <> of + <> when 'true' -> + case apply 'do_case'/0() of + <'ok'> when 'true' -> + 'ok' + ( <_cor0> when 'true' -> + primop 'match_fail' + ({'badmatch',_cor0}) + -| ['compiler_generated'] ) + end + ( <> when 'true' -> + ( primop 'match_fail' + ({'function_clause'}) + -| [{'function_name',{'eval_case',0}}] ) + -| ['compiler_generated'] ) + end +'do_case'/0 = + fun () -> + case let <_cor0> = + apply 'id'/1(42) + in let <_cor1> = + call 'erlang':'+' + (_cor0, 7) + in {'x',_cor1} of + <{'x',49}> when 'true' -> + 'ok' + end +'id'/1 = + fun (_cor0) -> _cor0 +end diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index 69f61a046f..8151dc1b16 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -22,7 +22,8 @@ init_per_group/2,end_per_group/2, t_element/1,setelement/1,t_length/1,append/1,t_apply/1,bifs/1, eq/1,nested_call_in_case/1,guard_try_catch/1,coverage/1, - unused_multiple_values_error/1,unused_multiple_values/1]). + unused_multiple_values_error/1,unused_multiple_values/1, + multiple_aliases/1]). -export([foo/0,foo/1,foo/2,foo/3]). @@ -38,7 +39,8 @@ groups() -> [{p,test_lib:parallel(), [t_element,setelement,t_length,append,t_apply,bifs, eq,nested_call_in_case,guard_try_catch,coverage, - unused_multiple_values_error,unused_multiple_values]}]. + unused_multiple_values_error,unused_multiple_values, + multiple_aliases]}]. init_per_suite(Config) -> @@ -299,8 +301,6 @@ cover_is_safe_bool_expr(X) -> bsm_an_inlined(<<_:8>>, _) -> ok; bsm_an_inlined(_, _) -> error. -id(I) -> I. - unused_multiple_values_error(Config) when is_list(Config) -> PrivDir = ?config(priv_dir, Config), Dir = filename:dirname(code:which(?MODULE)), @@ -338,3 +338,31 @@ do_something(I) -> put(unused_multiple_values, [I|get(unused_multiple_values)]), I. + + +%% Make sure that multiple aliases does not cause +%% the case expression to be evaluated twice. +multiple_aliases(Config) when is_list(Config) -> + do_ma(fun() -> + X = Y = run_once(), + {X,Y} + end, {ok,ok}), + do_ma(fun() -> + case {true,run_once()} of + {true=A=B,ok=X=Y} -> + {A,B,X,Y} + end + end, {true,true,ok,ok}), + ok. + +do_ma(Fun, Expected) when is_function(Fun, 0) -> + Expected = Fun(), + ran_once = erase(run_once), + ok. + +run_once() -> + undefined = put(run_once, ran_once), + ok. + + +id(I) -> I. diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl new file mode 100644 index 0000000000..86f65c3ed6 --- /dev/null +++ b/lib/compiler/test/map_SUITE.erl @@ -0,0 +1,507 @@ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(map_SUITE). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2 + ]). + +-export([ + t_build_and_match_literals/1, + t_update_literals/1,t_match_and_update_literals/1, + t_update_map_expressions/1, + t_update_assoc/1,t_update_exact/1, + t_guard_bifs/1, t_guard_sequence/1, t_guard_update/1, + t_guard_receive/1, t_guard_fun/1, + t_list_comprehension/1, + t_map_sort_literals/1, + t_map_size/1, + + %% warnings + t_warn_useless_build/1, + t_warn_pair_key_overloaded/1, + + %% not covered in 17.0-rc1 + t_build_and_match_over_alloc/1, + t_build_and_match_empty_val/1, + t_build_and_match_val/1, + + %% errors in 17.0-rc1 + t_update_values/1 + ]). + +suite() -> []. + +all() -> [ + t_build_and_match_literals, + t_update_literals, t_match_and_update_literals, + t_update_map_expressions, + t_update_assoc,t_update_exact, + t_guard_bifs, t_guard_sequence, t_guard_update, + t_guard_receive,t_guard_fun, t_list_comprehension, + t_map_sort_literals, + + %% warnings + t_warn_useless_build, + t_warn_pair_key_overloaded, + + %% not covered in 17.0-rc1 + t_build_and_match_over_alloc, + t_build_and_match_empty_val, + t_build_and_match_val, + + %% errors in 17.0-rc1 + t_update_values + ]. + +groups() -> []. + +init_per_suite(Config) -> Config. +end_per_suite(_Config) -> ok. + +init_per_group(_GroupName, Config) -> Config. +end_per_group(_GroupName, Config) -> Config. + +%% tests + +t_build_and_match_literals(Config) when is_list(Config) -> + #{} = id(#{}), + #{1:=a} = id(#{1=>a}), + #{1:=a,2:=b} = id(#{1=>a,2=>b}), + #{1:=a,2:=b,3:="c"} = id(#{1=>a,2=>b,3=>"c"}), + #{1:=a,2:=b,3:="c","4":="d"} = id(#{1=>a,2=>b,3=>"c","4"=>"d"}), + #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>} = + id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>}), + #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f"} = + id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f"}), + #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f",8:=g} = + id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f",8=>g}), + + #{<<"hi all">> := 1} = id(#{<<"hi",32,"all">> => 1}), + + #{a:=X,a:=X=3,b:=4} = id(#{a=>3,b=>4}), % weird but ok =) + + #{ a:=#{ b:=#{c := third, b:=second}}, b:=first} = + id(#{ b=>first, a=>#{ b=>#{c => third, b=> second}}}), + + M = #{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first}, + M = #{ map_1:=#{ map_2:=#{value_3 := third}, value_2:= second}, value_1:=first} = + id(#{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first}), + + %% error case + %V = 32, + %{'EXIT',{{badmatch,_},_}} = (catch (#{<<"hi all">> => 1} = id(#{<<"hi",V,"all">> => 1}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3,x:=2} = id(#{x=>3}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=2} = id(#{x=>3}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id({a,b,c}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id(#{y=>3}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id(#{x=>"three"}))), + ok. + + +t_map_size(Config) when is_list(Config) -> + 0 = map_size(id(#{})), + 1 = map_size(id(#{a=>1})), + 1 = map_size(id(#{a=>"wat"})), + 2 = map_size(id(#{a=>1, b=>2})), + 3 = map_size(id(#{a=>1, b=>2, b=>"3","33"=><<"n">>})), + + true = map_is_size(#{a=>1}, 1), + true = map_is_size(#{a=>1, a=>2}, 1), + M = #{ "a" => 1, "b" => 2}, + true = map_is_size(M, 2), + false = map_is_size(M, 3), + true = map_is_size(M#{ "a" => 2}, 2), + false = map_is_size(M#{ "c" => 2}, 2), + + %% Error cases. + {'EXIT',{badarg,_}} = (catch map_size([])), + {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)), + {'EXIT',{badarg,_}} = (catch map_size(1)), + ok. + +map_is_size(M,N) when map_size(M) =:= N -> true; +map_is_size(_,_) -> false. + +% test map updates without matching +t_update_literals(Config) when is_list(Config) -> + Map = #{x=>1,y=>2,z=>3,q=>4}, + #{x:="d",q:="4"} = loop_update_literals_x_q(Map, [ + {"a","1"},{"b","2"},{"c","3"},{"d","4"} + ]), + ok. + +loop_update_literals_x_q(Map, []) -> Map; +loop_update_literals_x_q(Map, [{X,Q}|Vs]) -> + loop_update_literals_x_q(Map#{q=>Q,x=>X},Vs). + +% test map updates with matching +t_match_and_update_literals(Config) when is_list(Config) -> + Map = #{x=>0,y=>"untouched",z=>"also untouched",q=>1}, + #{x:=16,q:=21,y:="untouched",z:="also untouched"} = loop_match_and_update_literals_x_q(Map, [ + {1,2},{3,4},{5,6},{7,8} + ]), + M0 = id(#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}), + M1 = id(#{}), + M2 = M1#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}, + M0 = M2, + + #{ 4 := another_number, int := 3 } = M2#{ 4 => another_number }, + ok. + +loop_match_and_update_literals_x_q(Map, []) -> Map; +loop_match_and_update_literals_x_q(#{q:=Q0,x:=X0} = Map, [{X,Q}|Vs]) -> + loop_match_and_update_literals_x_q(Map#{q=>Q0+Q,x=>X0+X},Vs). + + +t_update_map_expressions(Config) when is_list(Config) -> + M = maps:new(), + #{ a := 1 } = M#{a => 1}, + + #{ b := 2 } = (maps:new())#{ b => 2 }, + + #{ a :=42, b:=42, c:=42 } = (maps:from_list([{a,1},{b,2},{c,3}]))#{ a := 42, b := 42, c := 42 }, + #{ "a" :=1, "b":=42, "c":=42 } = (maps:from_list([{"a",1},{"b",2}]))#{ "b" := 42, "c" => 42 }, + + %% Test need to be in a fun. + %% This tests that let expr optimisation in sys_core_fold + %% covers maps correctly. + F = fun() -> + M0 = id(#{ "a" => [1,2,3] }), + #{ "a" := _ } = M0, + M0#{ "a" := b } + end, + + #{ "a" := b } = F(), + + %% Error cases, FIXME: should be 'badmap'? + {'EXIT',{badarg,_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), + {'EXIT',{badarg,_}} = (catch (id([]))#{ a := 42, b => 2 }), + ok. + + +t_update_assoc(Config) when is_list(Config) -> + M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e}), + + M1 = M0#{1=>42,2=>100,4=>[a,b,c]}, + #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1, + #{1:=42,2:=b,4:=d,5:=e,2.0:=100,3.0:=c,4.0:=[a,b,c]} = M0#{1.0=>float,1:=42,2.0=>wrong,2.0=>100,4.0=>[a,b,c]}, + + M2 = M0#{3.0=>new}, + #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2, + M2 = M0#{3.0:=wrong,3.0=>new}, + + %% Errors cases. + BadMap = id(badmap), + {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}), + + ok. + +t_update_exact(Config) when is_list(Config) -> + M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e}), + + M1 = M0#{1:=42,2:=100,4:=[a,b,c]}, + #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1, + M1 = M0#{1:=wrong,1=>42,2=>wrong,2:=100,4:=[a,b,c]}, + + M2 = M0#{3.0:=new}, + #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2, + M2 = M0#{3.0=>wrong,3.0:=new}, + true = M2 =/= M0#{3=>right,3.0:=new}, + #{ 3 := right, 3.0 := new } = M0#{3=>right,3.0:=new}, + + M3 = id(#{ 1 => val}), + #{1 := update2,1.0 := new_val4} = M3#{ + 1.0 => new_val1, 1 := update, 1=> update3, + 1 := update2, 1.0 := new_val2, 1.0 => new_val3, + 1.0 => new_val4 }, + + %% Errors cases. + {'EXIT',{badarg,_}} = (catch ((id(nil))#{ a := b })), + {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + ok. + +t_update_values(Config) when is_list(Config) -> + V0 = id(1337), + M0 = #{ a => 1, val => V0}, + V1 = get_val(M0), + M1 = M0#{ val := [V0,V1], "wazzup" => 42 }, + [1337, {some_val, 1337}] = get_val(M1), + + N = 110, + List = [{[I,1,2,3,I],{1,2,3,"wat",I}}|| I <- lists:seq(1,N)], + + {_,_,#{val2 := {1,2,3,"wat",N}, val1 := [N,1,2,3,N]}} = lists:foldl(fun + ({V2,V3},{Old2,Old3,Mi}) -> + ok = check_val(Mi,Old2,Old3), + #{ val1 := Old2, val2 := Old3 } = Mi, + {V2,V3, Mi#{ val1 := id(V2), val2 := V1, val2 => id(V3)}} + end, {none, none, #{val1=>none,val2=>none}},List), + ok. + +check_val(#{val1:=V1, val2:=V2},V1,V2) -> ok. + +get_val(#{ "wazzup" := _, val := V}) -> V; +get_val(#{ val := V }) -> {some_val, V}. + +t_guard_bifs(Config) when is_list(Config) -> + true = map_guard_head(#{a=>1}), + false = map_guard_head([]), + true = map_guard_body(#{a=>1}), + false = map_guard_body({}), + true = map_guard_pattern(#{a=>1, <<"hi">> => "hi" }), + false = map_guard_pattern("list"), + ok. + +map_guard_head(M) when is_map(M) -> true; +map_guard_head(_) -> false. + +map_guard_body(M) -> is_map(M). + +map_guard_pattern(#{}) -> true; +map_guard_pattern(_) -> false. + +t_guard_sequence(Config) when is_list(Config) -> + {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}), + {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}), + {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}), + {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}), + {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}), + + {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})), + {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})), + {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})), + {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})), + {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})), + + %% error case + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})), + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})), + ok. + +map_guard_sequence_1(#{seq:=1=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=2=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=3=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=4=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=5=Seq, val:=Val}) -> {Seq,Val}. + +map_guard_sequence_2(#{ a:=3 }=M) -> {1, M}; +map_guard_sequence_2(#{ a:=4 }=M) -> {2, M}; +map_guard_sequence_2(#{ a:=X, a:=X, b:=4 }=M) -> {3,X,M}; +map_guard_sequence_2(#{ a:=X, a:=Y, b:=3 }=M) when X =:= Y -> {4,X,Y,M}; +map_guard_sequence_2(#{ a:=X, a:=Y }=M) when X =:= Y -> {5,X,Y,M}. + + +t_guard_update(Config) when is_list(Config) -> + error = map_guard_update(#{},#{}), + first = map_guard_update(#{}, #{x=>first}), + second = map_guard_update(#{y=>old}, #{x=>second,y=>old}), + third = map_guard_update(#{x=>old,y=>old}, #{x=>third,y=>old}), + ok. + +map_guard_update(M1, M2) when M1#{x=>first} =:= M2 -> first; +map_guard_update(M1, M2) when M1#{x=>second} =:= M2 -> second; +map_guard_update(M1, M2) when M1#{x:=third} =:= M2 -> third; +map_guard_update(_, _) -> error. + +t_guard_receive(Config) when is_list(Config) -> + M0 = #{ id => 0 }, + Pid = spawn_link(fun() -> guard_receive_loop() end), + Big = 36893488147419103229, + B1 = <<"some text">>, + B2 = <<"was appended">>, + B3 = <<B1/binary, B2/binary>>, + + #{id:=1, res:=Big} = M1 = call(Pid, M0#{op=>sub,in=>{1 bsl 65, 3}}), + #{id:=2, res:=26} = M2 = call(Pid, M1#{op=>idiv,in=>{53,2}}), + #{id:=3, res:=832} = M3 = call(Pid, M2#{op=>imul,in=>{26,32}}), + #{id:=4, res:=4} = M4 = call(Pid, M3#{op=>add,in=>{1,3}}), + #{id:=5, res:=Big} = M5 = call(Pid, M4#{op=>sub,in=>{1 bsl 65, 3}}), + #{id:=6, res:=B3} = M6 = call(Pid, M5#{op=>"append",in=>{B1,B2}}), + #{id:=7, res:=4} = _ = call(Pid, M6#{op=>add,in=>{1,3}}), + + + %% update old maps and check id update + #{id:=2, res:=B3} = call(Pid, M1#{op=>"append",in=>{B1,B2}}), + #{id:=5, res:=99} = call(Pid, M4#{op=>add,in=>{33, 66}}), + + %% cleanup + done = call(Pid, done), + ok. + +call(Pid, M) -> + Pid ! {self(), M}, receive {Pid, Res} -> Res end. + +guard_receive_loop() -> + receive + {Pid, #{ id:=Id, op:="append", in:={X,Y}}=M} when is_binary(X), is_binary(Y) -> + Pid ! {self(), M#{ id=>Id+1, res=><<X/binary,Y/binary>>}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=add, in:={X,Y}}} -> + Pid ! {self(), #{ id=>Id+1, res=>X+Y}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=sub, in:={X,Y}}=M} -> + Pid ! {self(), M#{ id=>Id+1, res=>X-Y}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=idiv, in:={X,Y}}=M} -> + Pid ! {self(), M#{ id=>Id+1, res=>X div Y}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=imul, in:={X,Y}}=M} -> + Pid ! {self(), M#{ id=>Id+1, res=>X * Y}}, + guard_receive_loop(); + {Pid, done} -> + Pid ! {self(), done}; + {Pid, Other} -> + Pid ! {error, Other}, + guard_receive_loop() + end. + + +t_list_comprehension(Config) when is_list(Config) -> + [#{k:=1},#{k:=2},#{k:=3}] = [#{k=>I} || I <- [1,2,3]], + ok. + +t_guard_fun(Config) when is_list(Config) -> + F1 = fun + (#{s:=v,v:=V}) -> {v,V}; + (#{s:=t,v:={V,V}}) -> {t,V}; + (#{s:=l,v:=[V,V]}) -> {l,V} + end, + + F2 = fun + (#{s:=T,v:={V,V}}) -> {T,V}; + (#{s:=T,v:=[V,V]}) -> {T,V}; + (#{s:=T,v:=V}) -> {T,V} + end, + V = <<"hi">>, + + {v,V} = F1(#{s=>v,v=>V}), + {t,V} = F1(#{s=>t,v=>{V,V}}), + {l,V} = F1(#{s=>l,v=>[V,V]}), + + {v,V} = F2(#{s=>v,v=>V}), + {t,V} = F2(#{s=>t,v=>{V,V}}), + {l,V} = F2(#{s=>l,v=>[V,V]}), + + %% error case + {'EXIT', {function_clause,[{?MODULE,_,[#{s:=none,v:=none}],_}|_]}} = (catch F1(#{s=>none,v=>none})), + ok. + + +t_map_sort_literals(Config) when is_list(Config) -> + % test relation + + %% size order + true = #{ a => 1, b => 2} < id(#{ a => 1, b => 1, c => 1}), + true = #{ b => 1, a => 1} < id(#{ c => 1, a => 1, b => 1}), + false = #{ c => 1, b => 1, a => 1} < id(#{ c => 1, a => 1}), + + %% key order + true = id(#{ a => 1 }) < id(#{ b => 1}), + false = id(#{ b => 1 }) < id(#{ a => 1}), + true = id(#{ a => 1, b => 1, c => 1 }) < id(#{ b => 1, c => 1, d => 1}), + true = id(#{ b => 1, c => 1, d => 1 }) > id(#{ a => 1, b => 1, c => 1}), + true = id(#{ c => 1, b => 1, a => 1 }) < id(#{ b => 1, c => 1, d => 1}), + true = id(#{ "a" => 1 }) < id(#{ <<"a">> => 1}), + false = id(#{ <<"a">> => 1 }) < id(#{ "a" => 1}), + false = id(#{ 1 => 1 }) < id(#{ 1.0 => 1}), + false = id(#{ 1.0 => 1 }) < id(#{ 1 => 1}), + + %% value order + true = id(#{ a => 1 }) < id(#{ a => 2}), + false = id(#{ a => 2 }) < id(#{ a => 1}), + false = id(#{ a => 2, b => 1 }) < id(#{ a => 1, b => 3}), + true = id(#{ a => 1, b => 1 }) < id(#{ a => 1, b => 3}), + + true = id(#{ "a" => "hi", b => 134 }) == id(#{ b => 134,"a" => "hi"}), + + %% lists:sort + + SortVs = [#{"a"=>1},#{a=>2},#{1=>3},#{<<"a">>=>4}], + [#{1:=ok},#{a:=ok},#{"a":=ok},#{<<"a">>:=ok}] = lists:sort([#{"a"=>ok},#{a=>ok},#{1=>ok},#{<<"a">>=>ok}]), + [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(SortVs), + [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(lists:reverse(SortVs)), + + ok. + +t_warn_pair_key_overloaded(Config) when is_list(Config) -> + #{ "hi1" := 42 } = id(#{ "hi1" => 1, "hi1" => 42 }), + + #{ "hi1" := 1337, "hi2" := [2], "hi3" := 3 } = id(#{ + "hi1" => erlang:atom_to_binary(?MODULE,utf8), + "hi1" => erlang:binary_to_atom(<<"wazzup">>,utf8), + "hi1" => erlang:binary_to_float(<<"3.1416">>), + "hi1" => erlang:float_to_binary(3.1416), + "hi2" => erlang:pid_to_list(self()), + "hi3" => erlang:float_to_binary(3.1416), + "hi2" => lists:subtract([1,2],[1]), + "hi3" => +3, + "hi1" => erlang:min(1,2), + "hi1" => erlang:hash({1,2},35), + "hi1" => erlang:phash({1,2},33), + "hi1" => erlang:phash2({1,2},34), + "hi1" => erlang:integer_to_binary(1337), + "hi1" => erlang:binary_to_integer(<<"1337">>), + "hi4" => erlang:float_to_binary(3.1416) + }), + ok. + +t_warn_useless_build(Config) when is_list(Config) -> + [#{ a => id(I)} || I <- [1,2,3]], + ok. + +t_build_and_match_over_alloc(Config) when is_list(Config) -> + Ls = id([1,2,3]), + V0 = [a|Ls], + M0 = id(#{ "a" => V0 }), + #{ "a" := V1 } = M0, + V2 = id([c|Ls]), + M2 = id(#{ "a" => V2 }), + #{ "a" := V3 } = M2, + {[a,1,2,3],[c,1,2,3]} = id({V1,V3}), + ok. + +t_build_and_match_empty_val(Config) when is_list(Config) -> + F = fun(#{ "hi":=_,{1,2}:=_,1337:=_}) -> ok end, + ok = F(id(#{"hi"=>ok,{1,2}=>ok,1337=>ok})), + + %% error case + {'EXIT',{function_clause,_}} = (catch (F(id(#{"hi"=>ok})))), + ok. + +t_build_and_match_val(Config) when is_list(Config) -> + F = fun + (#{ "hi" := first, v := V}) -> {1,V}; + (#{ "hi" := second, v := V}) -> {2,V} + end, + + + {1,"hello"} = F(id(#{"hi"=>first,v=>"hello"})), + {2,"second"} = F(id(#{"hi"=>second,v=>"second"})), + + %% error case + {'EXIT',{function_clause,_}} = (catch (F(id(#{"hi"=>ok})))), + ok. + + +%% Use this function to avoid compile-time evaluation of an expression. +id(I) -> I. diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index 7186956603..16d15a59e5 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -390,6 +390,10 @@ effect(Config) when is_list(Config) -> <<X:8>>; unused_fun -> fun() -> {ok,X} end; + unused_named_fun -> + fun F(0) -> 1; + F(N) -> N*F(N-1) + end; unused_atom -> ignore; %no warning unused_nil -> @@ -484,8 +488,9 @@ effect(Config) when is_list(Config) -> {22,sys_core_fold,{no_effect,{erlang,is_integer,1}}}, {24,sys_core_fold,useless_building}, {26,sys_core_fold,useless_building}, - {32,sys_core_fold,{no_effect,{erlang,'=:=',2}}}, - {34,sys_core_fold,{no_effect,{erlang,get_cookie,0}}}]}}], + {28,sys_core_fold,useless_building}, + {36,sys_core_fold,{no_effect,{erlang,'=:=',2}}}, + {38,sys_core_fold,{no_effect,{erlang,get_cookie,0}}}]}}], ?line [] = run(Config, Ts), ok. diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 925ad0c091..2e1a21178f 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2010-2013. All Rights Reserved. + * Copyright Ericsson AB 2010-2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -444,6 +444,15 @@ static ERL_NIF_TERM atom_ppbasis; static ERL_NIF_TERM atom_onbasis; #endif +static ErlNifResourceType* hmac_context_rtype; +struct hmac_context +{ + ErlNifMutex* mtx; + int alive; + HMAC_CTX ctx; +}; +static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context*); + /* #define PRINTF_ERR0(FMT) enif_fprintf(stderr, FMT "\n") #define PRINTF_ERR1(FMT, A1) enif_fprintf(stderr, FMT "\n", A1) @@ -498,6 +507,15 @@ static int init(ErlNifEnv* env, ERL_NIF_TERM load_info) return 0; } + hmac_context_rtype = enif_open_resource_type(env, NULL, "hmac_context", + (ErlNifResourceDtor*) hmac_context_dtor, + ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, + NULL); + if (!hmac_context_rtype) { + PRINTF_ERR0("CRYPTO: Could not open resource type 'hmac_context'"); + return 0; + } + if (library_refc > 0) { /* Repeated loading of this library (module upgrade). * Atoms and callbacks are already set, we are done. @@ -1280,11 +1298,19 @@ static ERL_NIF_TERM sha512_mac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM #endif } +static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj) +{ + if (obj->alive) { + HMAC_CTX_cleanup(&obj->ctx); + obj->alive = 0; + } + enif_mutex_destroy(obj->mtx); +} + static ERL_NIF_TERM hmac_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {/* (Type, Key) */ ErlNifBinary key; - ERL_NIF_TERM ret; - unsigned char * ctx_buf; + struct hmac_context* obj; const EVP_MD *md; if (argv[0] == atom_sha) md = EVP_sha1(); @@ -1309,57 +1335,60 @@ static ERL_NIF_TERM hmac_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[ return enif_make_badarg(env); } - ctx_buf = enif_make_new_binary(env, sizeof(HMAC_CTX), &ret); - HMAC_CTX_init((HMAC_CTX *) ctx_buf); - HMAC_Init((HMAC_CTX *) ctx_buf, key.data, key.size, md); + obj = enif_alloc_resource(hmac_context_rtype, sizeof(struct hmac_context)); + obj->mtx = enif_mutex_create("crypto.hmac"); + obj->alive = 1; + HMAC_CTX_init(&obj->ctx); + HMAC_Init(&obj->ctx, key.data, key.size, md); - return ret; + return enif_make_resource(env, obj); } static ERL_NIF_TERM hmac_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {/* (Context, Data) */ - ErlNifBinary context, data; - ERL_NIF_TERM ret; - unsigned char * ctx_buf; + ErlNifBinary data; + struct hmac_context* obj; - if (!enif_inspect_binary(env, argv[0], &context) - || !enif_inspect_iolist_as_binary(env, argv[1], &data) - || context.size != sizeof(HMAC_CTX)) { + if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj) + || !enif_inspect_iolist_as_binary(env, argv[1], &data)) { + return enif_make_badarg(env); + } + enif_mutex_lock(obj->mtx); + if (!obj->alive) { + enif_mutex_unlock(obj->mtx); return enif_make_badarg(env); } + HMAC_Update(&obj->ctx, data.data, data.size); + enif_mutex_unlock(obj->mtx); - ctx_buf = enif_make_new_binary(env, sizeof(HMAC_CTX), &ret); - memcpy(ctx_buf, context.data, context.size); - HMAC_Update((HMAC_CTX *)ctx_buf, data.data, data.size); CONSUME_REDS(env,data); - - return ret; + return argv[0]; } static ERL_NIF_TERM hmac_final(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {/* (Context) or (Context, HashLen) */ - ErlNifBinary context; ERL_NIF_TERM ret; - HMAC_CTX ctx; + struct hmac_context* obj; unsigned char mac_buf[EVP_MAX_MD_SIZE]; unsigned char * mac_bin; unsigned int req_len = 0; unsigned int mac_len; - if (!enif_inspect_binary(env, argv[0], &context)) { - return enif_make_badarg(env); - } - if (argc == 2 && !enif_get_uint(env, argv[1], &req_len)) { + if (!enif_get_resource(env,argv[0],hmac_context_rtype, (void**)&obj) + || (argc == 2 && !enif_get_uint(env, argv[1], &req_len))) { return enif_make_badarg(env); } - if (context.size != sizeof(ctx)) { - return enif_make_badarg(env); + enif_mutex_lock(obj->mtx); + if (!obj->alive) { + enif_mutex_unlock(obj->mtx); + return enif_make_badarg(env); } - memcpy(&ctx, context.data, context.size); - HMAC_Final(&ctx, mac_buf, &mac_len); - HMAC_CTX_cleanup(&ctx); + HMAC_Final(&obj->ctx, mac_buf, &mac_len); + HMAC_CTX_cleanup(&obj->ctx); + obj->alive = 0; + enif_mutex_unlock(obj->mtx); if (argc == 2 && req_len < mac_len) { /* Only truncate to req_len bytes if asked. */ diff --git a/lib/crypto/c_src/crypto_callback.c b/lib/crypto/c_src/crypto_callback.c index 81106b4cc2..a08dcec463 100644 --- a/lib/crypto/c_src/crypto_callback.c +++ b/lib/crypto/c_src/crypto_callback.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2012. All Rights Reserved. + * Copyright Ericsson AB 2014. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -17,6 +17,7 @@ * %CopyrightEnd% */ +#include <stdio.h> #include <string.h> #include <openssl/opensslconf.h> @@ -51,13 +52,28 @@ DLLEXPORT struct crypto_callbacks* get_crypto_callbacks(int nlocks); static ErlNifRWLock** lock_vec = NULL; /* Static locks used by openssl */ +static void nomem(size_t size, const char* op) +{ + fprintf(stderr, "Out of memory abort. Crypto failed to %s %zu bytes.\r\n", + op, size); + abort(); +} + static void* crypto_alloc(size_t size) { - return enif_alloc(size); + void *ret = enif_alloc(size); + + if (!ret && size) + nomem(size, "allocate"); + return ret; } static void* crypto_realloc(void* ptr, size_t size) { - return enif_realloc(ptr, size); + void* ret = enif_realloc(ptr, size); + + if (!ret && size) + nomem(size, "reallocate"); + return ret; } static void crypto_free(void* ptr) { diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 40f829e704..c95827c371 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1999</year><year>2013</year> + <year>1999</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -366,7 +366,11 @@ or to one of the functions <seealso marker="#hmac_final-1">hmac_final</seealso> and <seealso marker="#hmac_final_n-2">hmac_final_n</seealso> </p> - + <warning><p>Do not use a <c>Context</c> as argument in more than one + call to hmac_update or hmac_final. The semantics of reusing old contexts + in any way is undefined and could even crash the VM in earlier releases. + The reason for this limitation is a lack of support in the underlying + OpenSSL API.</p></warning> </desc> </func> diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl index 14a17fe304..1d36aae8ee 100644 --- a/lib/debugger/src/dbg_ieval.erl +++ b/lib/debugger/src/dbg_ieval.erl @@ -505,12 +505,12 @@ lambda(Fun, As) when is_function(Fun) -> %% ... and the fun was defined in interpreted code {module, ?MODULE} -> - {Mod,Name,Bs} = + {Mod,Name,Bs, Cs} = case erlang:fun_info(Fun, env) of - {env,[{{M,F},Bs0,Cs}]} -> - {M,F,Bs0}; - {env,[{{M,F},Bs0,Cs,FName}]} -> - {M,F,add_binding(FName, Fun, Bs0)} + {env,[{{M,F},Bs0,Cs0}]} -> + {M,F,Bs0, Cs0}; + {env,[{{M,F},Bs0,Cs0,FName}]} -> + {M,F,add_binding(FName, Fun, Bs0), Cs0} end, {arity, Arity} = erlang:fun_info(Fun, arity), if @@ -654,6 +654,21 @@ expr({tuple,Line,Es0}, Bs0, Ieval) -> {Vs,Bs} = eval_list(Es0, Bs0, Ieval#ieval{line=Line}), {value,list_to_tuple(Vs),Bs}; +%% Map +expr({map,Line,Fs0}, Bs0, Ieval) -> + {Fs,Bs} = eval_map_fields(Fs0, Bs0, Ieval#ieval{line=Line,top=false}), + Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi) end, + #{}, Fs), + {value,Value,Bs}; +expr({map,Line,E0,Fs0}, Bs0, Ieval0) -> + Ieval = Ieval0#ieval{line=Line,top=false}, + {value,E,Bs1} = expr(E0, Bs0, Ieval), + {Fs,Bs2} = eval_map_fields(Fs0, Bs1, Ieval), + Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi); + ({map_exact,K,V}, Mi) -> maps:update(K,V,Mi) end, + E, Fs), + {value,Value,Bs2}; + %% A block of statements expr({block,Line,Es},Bs,Ieval) -> seq(Es, Bs, Ieval#ieval{line=Line}); @@ -1127,7 +1142,7 @@ eval_generate([V|Rest], P, Bs0, CompFun, Ieval) -> case catch match1(P, V, erl_eval:new_bindings(), Bs0) of {match,Bsn} -> Bs2 = add_bindings(Bsn, Bs0), - CompFun(Bs2) ++ eval_generate(Rest, P, Bs2, CompFun, Ieval); + CompFun(Bs2) ++ eval_generate(Rest, P, Bs0, CompFun, Ieval); nomatch -> eval_generate(Rest, P, Bs0, CompFun, Ieval) end; @@ -1468,6 +1483,19 @@ guard_expr({cons,_,H0,T0}, Bs) -> guard_expr({tuple,_,Es0}, Bs) -> {values,Es} = guard_exprs(Es0, Bs), {value,list_to_tuple(Es)}; +guard_expr({map,_,Fs0}, Bs) -> + Fs = eval_map_fields_guard(Fs0, Bs), + Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi) end, + #{}, Fs), + {value,Value}; +guard_expr({map,_,E0,Fs0}, Bs) -> + {value,E} = guard_expr(E0, Bs), + Fs = eval_map_fields_guard(Fs0, Bs), + Value = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi); + ({map_exact,K,V}, Mi) -> maps:update(K,V,Mi) end, + E, Fs), + io:format("~p~n", [{E,Value}]), + {value,Value}; guard_expr({bin,_,Flds}, Bs) -> {value,V,_Bs} = eval_bits:expr_grp(Flds, Bs, @@ -1477,6 +1505,37 @@ guard_expr({bin,_,Flds}, Bs) -> end, [], false), {value,V}. + +%% eval_map_fields([Field], Bindings, IEvalState) -> +%% {[{map_assoc | map_exact,Key,Value}],Bindings} + +eval_map_fields(Fs, Bs, Ieval) -> + eval_map_fields(Fs, Bs, Ieval, fun expr/3). + +eval_map_fields_guard(Fs0, Bs) -> + {Fs,_} = eval_map_fields(Fs0, Bs, #ieval{}, + fun (G0, Bs0, _) -> + {value,G} = guard_expr(G0, Bs0), + {value,G,Bs0} + end), + Fs. + +eval_map_fields(Fs, Bs, Ieval, F) -> + eval_map_fields(Fs, Bs, Ieval, F, []). + +eval_map_fields([{map_field_assoc,Line,K0,V0}|Fs], Bs0, Ieval0, F, Acc) -> + Ieval = Ieval0#ieval{line=Line}, + {value,K,Bs1} = F(K0, Bs0, Ieval), + {value,V,Bs2} = F(V0, Bs1, Ieval), + eval_map_fields(Fs, Bs2, Ieval0, F, [{map_assoc,K,V}|Acc]); +eval_map_fields([{map_field_exact,Line,K0,V0}|Fs], Bs0, Ieval0, F, Acc) -> + Ieval = Ieval0#ieval{line=Line}, + {value,K,Bs1} = F(K0, Bs0, Ieval), + {value,V,Bs2} = F(V0, Bs1, Ieval), + eval_map_fields(Fs, Bs2, Ieval0, F, [{map_exact,K,V}|Acc]); +eval_map_fields([], Bs, _Ieval, _F, Acc) -> + {lists:reverse(Acc),Bs}. + %% match(Pattern,Term,Bs) -> {match,Bs} | nomatch match(Pat, Term, Bs) -> try match1(Pat, Term, Bs, Bs) @@ -1506,6 +1565,8 @@ match1({cons,_,H,T}, [H1|T1], Bs0, BBs) -> match1({tuple,_,Elts}, Tuple, Bs, BBs) when length(Elts) =:= tuple_size(Tuple) -> match_tuple(Elts, Tuple, 1, Bs, BBs); +match1({map,_,Fields}, Map, Bs, BBs) when is_map(Map) -> + match_map(Fields, Map, Bs, BBs); match1({bin,_,Fs}, B, Bs0, BBs) when is_bitstring(B) -> try eval_bits:match_bits(Fs, B, Bs0, BBs, match_fun(BBs), @@ -1529,6 +1590,17 @@ match_tuple([E|Es], Tuple, I, Bs0, BBs) -> match_tuple([], _, _, Bs, _BBs) -> {match,Bs}. +match_map([{map_field_exact,_,K0,Pat}|Fs], Map, Bs0, BBs) -> + {value,K,BBs} = expr(K0, BBs, #ieval{}), + case maps:find(K, Map) of + {ok,Value} -> + {match,Bs} = match1(Pat, Value, Bs0, BBs), + match_map(Fs, Map, Bs, BBs); + error -> throw(nomatch) + end; +match_map([], _, Bs, _BBs) -> + {match,Bs}. + head_match([Par|Pars], [Arg|Args], Bs0, BBs) -> try match1(Par, Arg, Bs0, BBs) of {match,Bs} -> head_match(Pars, Args, Bs, BBs) diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl index 9806692afc..266cf239dd 100644 --- a/lib/debugger/src/dbg_iload.erl +++ b/lib/debugger/src/dbg_iload.erl @@ -194,6 +194,11 @@ pattern({cons,Line,H0,T0}) -> pattern({tuple,Line,Ps0}) -> Ps1 = pattern_list(Ps0), {tuple,Line,Ps1}; +pattern({map,Line,Fs0}) -> + Fs1 = lists:map(fun ({map_field_exact,L,K,V}) -> + {map_field_exact,L,expr(K, false),pattern(V)} + end, Fs0), + {map,Line,Fs1}; pattern({op,_,'-',{integer,Line,I}}) -> {value,Line,-I}; pattern({op,_,'+',{integer,Line,I}}) -> @@ -262,6 +267,8 @@ guard_test({string,Line,_}) -> {value,Line,false}; guard_test({nil,Line}) -> {value,Line,false}; guard_test({cons,Line,_,_}) -> {value,Line,false}; guard_test({tuple,Line,_}) -> {value,Line,false}; +guard_test({map,Line,_}) -> {value,Line,false}; +guard_test({map,Line,_,_}) -> {value,Line,false}; guard_test({bin,Line,_}) -> {value,Line,false}. gexpr({var,Line,V}) -> {var,Line,V}; @@ -279,6 +286,13 @@ gexpr({cons,Line,H0,T0}) -> gexpr({tuple,Line,Es0}) -> Es1 = gexpr_list(Es0), {tuple,Line,Es1}; +gexpr({map,Line,Fs0}) -> + Fs1 = map_fields(Fs0, fun gexpr/1), + {map,Line,Fs1}; +gexpr({map,Line,E0,Fs0}) -> + E1 = gexpr(E0), + Fs1 = map_fields(Fs0, fun gexpr/1), + {map,Line,E1,Fs1}; gexpr({bin,Line,Flds0}) -> Flds = gexpr_list(Flds0), {bin,Line,Flds}; @@ -341,6 +355,13 @@ expr({cons,Line,H0,T0}, _Lc) -> expr({tuple,Line,Es0}, _Lc) -> Es1 = expr_list(Es0), {tuple,Line,Es1}; +expr({map,Line,Fs0}, _Lc) -> + Fs1 = map_fields(Fs0), + {map,Line,Fs1}; +expr({map,Line,E0,Fs0}, _Lc) -> + E1 = expr(E0, false), + Fs1 = map_fields(Fs0), + {map,Line,E1,Fs1}; expr({block,Line,Es0}, Lc) -> %% Unfold block into a sequence. Es1 = exprs(Es0, Lc), @@ -436,7 +457,7 @@ expr({lc,Line,E0,Gs0}, _Lc) -> %R8. ({b_generate,L,P0,Qs}) -> %R12. {b_generate,L,expr(P0, false),expr(Qs, false)}; (Expr) -> - case is_guard(Expr) of + case erl_lint:is_guard_test(Expr) of true -> {guard,guard([[Expr]])}; false -> expr(Expr, false) end @@ -448,7 +469,7 @@ expr({bc,Line,E0,Gs0}, _Lc) -> %R12. ({b_generate,L,P0,Qs}) -> %R12. {b_generate,L,expr(P0, false),expr(Qs, false)}; (Expr) -> - case is_guard(Expr) of + case erl_lint:is_guard_test(Expr) of true -> {guard,guard([[Expr]])}; false -> expr(Expr, false) end @@ -491,42 +512,6 @@ expr({bin_element,Line,Expr,Size,Type}, _Lc) -> expr(Other, _Lc) -> exit({?MODULE,{unknown_expr,Other}}). -%% is_guard(Expression) -> true | false. -%% Test if a general expression is a guard test or guard BIF. -%% Cannot use erl_lint here as sys_pre_expand has transformed source. - -is_guard({op,_,Op,L,R}) -> - erl_internal:comp_op(Op, 2) andalso is_gexpr_list([L,R]); -is_guard({call,_,{remote,_,{atom,_,erlang},{atom,_,Test}},As}) -> - Arity = length(As), - (erl_internal:guard_bif(Test, Arity) orelse - erl_internal:old_type_test(Test, Arity)) andalso is_gexpr_list(As); -is_guard({atom,_,true}) -> true; -is_guard(_) -> false. - -is_gexpr({var,_,_}) -> true; -is_gexpr({atom,_,_}) -> true; -is_gexpr({integer,_,_}) -> true; -is_gexpr({char,_,_}) -> true; -is_gexpr({float,_,_}) -> true; -is_gexpr({string,_,_}) -> true; -is_gexpr({nil,_}) -> true; -is_gexpr({cons,_,H,T}) -> is_gexpr_list([H,T]); -is_gexpr({tuple,_,Es}) -> is_gexpr_list(Es); -is_gexpr({call,_,{remote,_,{atom,_,erlang},{atom,_,F}},As}) -> - Ar = length(As), - case erl_internal:guard_bif(F, Ar) of - true -> is_gexpr_list(As); - false -> erl_internal:arith_op(F, Ar) andalso is_gexpr_list(As) - end; -is_gexpr({op,_,Op,A}) -> - erl_internal:arith_op(Op, 1) andalso is_gexpr(A); -is_gexpr({op,_,Op,A1,A2}) -> - erl_internal:arith_op(Op, 2) andalso is_gexpr_list([A1,A2]); -is_gexpr(_) -> false. - -is_gexpr_list(Es) -> lists:all(fun (E) -> is_gexpr(E) end, Es). - consify([A|As]) -> {cons,0,A,consify(As)}; consify([]) -> {value,0,[]}. @@ -550,6 +535,15 @@ fun_clauses([{clause,L,H,G,B}|Cs]) -> [{clause,L,head(H),guard(G),exprs(B, true)}|fun_clauses(Cs)]; fun_clauses([]) -> []. +map_fields(Fs) -> + map_fields(Fs, fun (E) -> expr(E, false) end). + +map_fields([{map_field_assoc,L,N,V}|Fs], F) -> + [{map_field_assoc,L,F(N),F(V)}|map_fields(Fs)]; +map_fields([{map_field_exact,L,N,V}|Fs], F) -> + [{map_field_exact,L,F(N),F(V)}|map_fields(Fs)]; +map_fields([], _) -> []. + %% new_var_name() -> VarName. new_var_name() -> diff --git a/lib/debugger/src/dbg_wx_filedialog_win.erl b/lib/debugger/src/dbg_wx_filedialog_win.erl index c8ecb7b5d4..ea34295067 100644 --- a/lib/debugger/src/dbg_wx_filedialog_win.erl +++ b/lib/debugger/src/dbg_wx_filedialog_win.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2012. All Rights Reserved. +%% Copyright Ericsson AB 2009-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -242,16 +242,13 @@ handle_event(#wx{event=#wxList{itemIndex=Index}}, {noreply, State0} end; -handle_event(#wx{event=#wxCommand{type=command_text_updated, cmdString=Wanted}}, +handle_event(#wx{event=#wxCommand{type=command_text_updated, cmdString=Wanted}}, State = #state{ptext=Previous, completion=Comp}) -> case Previous =:= undefined orelse lists:prefix(Wanted, Previous) of - true -> - case Comp of - {Temp,_} -> wxWindow:destroy(Temp); - undefined -> ok - end, + true -> + destroy_completion(Comp), {noreply, State#state{ptext=Wanted,completion=undefined}}; - false -> + false -> {noreply, show_completion(Wanted, State)} end; @@ -310,8 +307,7 @@ handle_event(#wx{event=#wxSize{size={Width,_}}}, State = #state{list=LC}) -> end), {noreply, State}; -handle_event(Event,State) -> - io:format("~p Got ~p ~n",[self(), Event]), +handle_event(_Event,State) -> {noreply, State}. handle_info(_Msg, State) -> @@ -419,8 +415,9 @@ show_completion(Wanted, State = #state{text=TC, win=Win, list=LC, completion=Com end. destroy_completion(undefined) -> ok; -destroy_completion({Window, _}) -> +destroy_completion({Window, _LB}) -> Parent = wxWindow:getParent(Window), + wxWindow:hide(Window), wxWindow:destroy(Window), wxWindow:refresh(Parent). diff --git a/lib/debugger/src/dbg_wx_mon.erl b/lib/debugger/src/dbg_wx_mon.erl index 4a01492cf5..4ab03985d3 100644 --- a/lib/debugger/src/dbg_wx_mon.erl +++ b/lib/debugger/src/dbg_wx_mon.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -148,7 +148,7 @@ init2(CallingPid, Mode, SFile, GS) -> win = Win, focus = undefined, - coords = {20,20}, + coords = {-1,-1}, intdir = element(2, file:get_cwd()), pinfos = [], diff --git a/lib/debugger/src/dbg_wx_mon_win.erl b/lib/debugger/src/dbg_wx_mon_win.erl index 0071b27027..d94eb14937 100644 --- a/lib/debugger/src/dbg_wx_mon_win.erl +++ b/lib/debugger/src/dbg_wx_mon_win.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -617,8 +617,6 @@ handle_event(#wx{userData=Data, _WinInfo) -> Data; handle_event(_Event, _WinInfo) -> -%% FIXME - io:format("Ev: ~p~n",[_Event]), ignore. %%==================================================================== diff --git a/lib/debugger/src/dbg_wx_trace.erl b/lib/debugger/src/dbg_wx_trace.erl index 1ac796bb4c..4438466bb0 100644 --- a/lib/debugger/src/dbg_wx_trace.erl +++ b/lib/debugger/src/dbg_wx_trace.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -125,7 +125,7 @@ init(Pid, Parent, Meta, TraceWin, BackTrace, Strings) -> dbg_wx_winman:insert(Title, Window), %% Initial process state - State1 = #state{win=Win, coords={0,0}, pid=Pid, meta=Meta, + State1 = #state{win=Win, coords={-1,-1}, pid=Pid, meta=Meta, status={idle,null,null}, stack={1,1}, strings=[str_on]}, diff --git a/lib/debugger/src/dbg_wx_view.erl b/lib/debugger/src/dbg_wx_view.erl index 6242b9d0e0..fc7ffc0d56 100644 --- a/lib/debugger/src/dbg_wx_view.erl +++ b/lib/debugger/src/dbg_wx_view.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2011. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -72,7 +72,7 @@ init(GS, Env, Mod, Title) -> Win2, int:all_breaks(Mod)), - try loop(#state{gs=GS, win=Win3, coords={0,0}, mod=Mod}) + try loop(#state{gs=GS, win=Win3, coords={-1,-1}, mod=Mod}) catch _E:normal -> exit(normal); _E:_R -> diff --git a/lib/debugger/src/int.erl b/lib/debugger/src/int.erl index bdd671cff1..2755db64b8 100644 --- a/lib/debugger/src/int.erl +++ b/lib/debugger/src/int.erl @@ -630,14 +630,13 @@ find_beam(Mod, Src) -> File = filename:join(SrcDir, BeamFile), case is_file(File) of true -> File; - false -> find_beam_1(Mod, BeamFile, SrcDir) + false -> find_beam_1(BeamFile, SrcDir) end. -find_beam_1(Mod, BeamFile, SrcDir) -> +find_beam_1(BeamFile, SrcDir) -> RootDir = filename:dirname(SrcDir), EbinDir = filename:join(RootDir, "ebin"), CodePath = [EbinDir | code:get_path()], - BeamFile = atom_to_list(Mod) ++ code:objfile_extension(), lists:foldl(fun(_, Beam) when is_list(Beam) -> Beam; (Dir, error) -> File = filename:join(Dir, BeamFile), diff --git a/lib/debugger/test/Makefile b/lib/debugger/test/Makefile index 5aa290c61b..9a0ce14ecd 100644 --- a/lib/debugger/test/Makefile +++ b/lib/debugger/test/Makefile @@ -44,6 +44,7 @@ MODULES= \ fun_SUITE \ lc_SUITE \ line_number_SUITE \ + map_SUITE \ record_SUITE \ trycatch_SUITE \ test_lib \ diff --git a/lib/debugger/test/erl_eval_SUITE.erl b/lib/debugger/test/erl_eval_SUITE.erl index be9312b68f..ba60ed6fb3 100644 --- a/lib/debugger/test/erl_eval_SUITE.erl +++ b/lib/debugger/test/erl_eval_SUITE.erl @@ -64,6 +64,7 @@ config(priv_dir,_) -> % Default timetrap timeout (set in init_per_testcase). -define(default_timeout, ?t:minutes(1)). init_per_testcase(_Case, Config) -> + test_lib:interpret(?MODULE), ?line Dog = ?t:timetrap(?default_timeout), [{watchdog, Dog} | Config]. end_per_testcase(_Case, Config) -> diff --git a/lib/debugger/test/map_SUITE.erl b/lib/debugger/test/map_SUITE.erl new file mode 100644 index 0000000000..e9f4ea1fad --- /dev/null +++ b/lib/debugger/test/map_SUITE.erl @@ -0,0 +1,1002 @@ +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(map_SUITE). + +%% Copied from map_SUITE in erts. + +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2 + ]). + +-export([ + t_build_and_match_literals/1, + t_update_literals/1,t_match_and_update_literals/1, + t_update_map_expressions/1, + t_update_assoc/1,t_update_exact/1, + t_guard_bifs/1, t_guard_sequence/1, t_guard_update/1, + t_guard_receive/1, t_guard_fun/1, + t_list_comprehension/1, + t_map_sort_literals/1, + %t_size/1, + t_map_size/1, + + %% Specific Map BIFs + t_bif_map_get/1, + t_bif_map_find/1, + t_bif_map_is_key/1, + t_bif_map_keys/1, + t_bif_map_merge/1, + t_bif_map_new/1, + t_bif_map_put/1, + t_bif_map_remove/1, + t_bif_map_update/1, + t_bif_map_values/1, + t_bif_map_to_list/1, + t_bif_map_from_list/1, + + %% erlang + t_erlang_hash/1, + t_map_encode_decode/1, + + %% maps module not bifs + t_maps_fold/1, + t_maps_map/1, + t_maps_size/1, + t_maps_without/1, + + %% misc + t_pdict/1, + t_ets/1, + t_dets/1 + ]). + +-include_lib("stdlib/include/ms_transform.hrl"). + +suite() -> []. + +all() -> [ + t_build_and_match_literals, + t_update_literals, t_match_and_update_literals, + t_update_map_expressions, + t_update_assoc,t_update_exact, + t_guard_bifs, t_guard_sequence, t_guard_update, + t_guard_receive,t_guard_fun, t_list_comprehension, + t_map_sort_literals, + + %% Specific Map BIFs + t_bif_map_get,t_bif_map_find,t_bif_map_is_key, + t_bif_map_keys, t_bif_map_merge, t_bif_map_new, + t_bif_map_put, + t_bif_map_remove, t_bif_map_update, + t_bif_map_values, + t_bif_map_to_list, t_bif_map_from_list, + + %% erlang + t_erlang_hash, t_map_encode_decode, + t_map_size, + + %% maps module + t_maps_fold, t_maps_map, + t_maps_size, t_maps_without, + + + %% Other functions + t_pdict, + t_ets + ]. + +groups() -> []. + +init_per_suite(Config) -> + test_lib:interpret(?MODULE), + Config. + +end_per_suite(_Config) -> ok. + +init_per_group(_GroupName, Config) -> Config. +end_per_group(_GroupName, Config) -> Config. + +%% tests + +t_build_and_match_literals(Config) when is_list(Config) -> + #{} = id(#{}), + #{1:=a} = id(#{1=>a}), + #{1:=a,2:=b} = id(#{1=>a,2=>b}), + #{1:=a,2:=b,3:="c"} = id(#{1=>a,2=>b,3=>"c"}), + #{1:=a,2:=b,3:="c","4":="d"} = id(#{1=>a,2=>b,3=>"c","4"=>"d"}), + #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>} = + id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>}), + #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f"} = + id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f"}), + #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f",8:=g} = + id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f",8=>g}), + + #{<<"hi all">> := 1} = id(#{<<"hi",32,"all">> => 1}), + + #{a:=X,a:=X=3,b:=4} = id(#{a=>3,b=>4}), % weird but ok =) + + #{ a:=#{ b:=#{c := third, b:=second}}, b:=first} = + id(#{ b=>first, a=>#{ b=>#{c => third, b=> second}}}), + + M = #{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first}, + M = #{ map_1:=#{ map_2:=#{value_3 := third}, value_2:= second}, value_1:=first} = + id(#{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first}), + + %% error case + %V = 32, + %{'EXIT',{{badmatch,_},_}} = (catch (#{<<"hi all">> => 1} = id(#{<<"hi",V,"all">> => 1}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3,x:=2} = id(#{x=>3}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=2} = id(#{x=>3}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id({a,b,c}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id(#{y=>3}))), + {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = id(#{x=>"three"}))), + ok. + + +%% Tests size(Map). +%% not implemented, perhaps it shouldn't be either + +%t_size(Config) when is_list(Config) -> +% 0 = size(#{}), +% 1 = size(#{a=>1}), +% 1 = size(#{a=>#{a=>1}}), +% 2 = size(#{a=>1, b=>2}), +% 3 = size(#{a=>1, b=>2, b=>"3"}), +% ok. + +t_map_size(Config) when is_list(Config) -> + 0 = map_size(id(#{})), + 1 = map_size(id(#{a=>1})), + 1 = map_size(id(#{a=>"wat"})), + 2 = map_size(id(#{a=>1, b=>2})), + 3 = map_size(id(#{a=>1, b=>2, b=>"3","33"=><<"n">>})), + + true = map_is_size(#{a=>1}, 1), + true = map_is_size(#{a=>1, a=>2}, 1), + M = #{ "a" => 1, "b" => 2}, + true = map_is_size(M, 2), + false = map_is_size(M, 3), + true = map_is_size(M#{ "a" => 2}, 2), + false = map_is_size(M#{ "c" => 2}, 2), + + %% Error cases. + {'EXIT',{badarg,_}} = (catch map_size([])), + {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)), + {'EXIT',{badarg,_}} = (catch map_size(1)), + ok. + +map_is_size(M,N) when map_size(M) =:= N -> true; +map_is_size(_,_) -> false. + +% test map updates without matching +t_update_literals(Config) when is_list(Config) -> + Map = #{x=>1,y=>2,z=>3,q=>4}, + #{x:="d",q:="4"} = loop_update_literals_x_q(Map, [ + {"a","1"},{"b","2"},{"c","3"},{"d","4"} + ]), + ok. + +loop_update_literals_x_q(Map, []) -> Map; +loop_update_literals_x_q(Map, [{X,Q}|Vs]) -> + loop_update_literals_x_q(Map#{q=>Q,x=>X},Vs). + +% test map updates with matching +t_match_and_update_literals(Config) when is_list(Config) -> + Map = #{x=>0,y=>"untouched",z=>"also untouched",q=>1}, + #{x:=16,q:=21,y:="untouched",z:="also untouched"} = loop_match_and_update_literals_x_q(Map, [ + {1,2},{3,4},{5,6},{7,8} + ]), + M0 = id(#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}), + M1 = id(#{}), + M2 = M1#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}, + M0 = M2, + + #{ 4 := another_number, int := 3 } = M2#{ 4 => another_number }, + ok. + +loop_match_and_update_literals_x_q(Map, []) -> Map; +loop_match_and_update_literals_x_q(#{q:=Q0,x:=X0} = Map, [{X,Q}|Vs]) -> + loop_match_and_update_literals_x_q(Map#{q=>Q0+Q,x=>X0+X},Vs). + + +t_update_map_expressions(Config) when is_list(Config) -> + M = maps:new(), + #{ a := 1 } = M#{a => 1}, + + #{ b := 2 } = (maps:new())#{ b => 2 }, + + #{ a :=42, b:=42, c:=42 } = (maps:from_list([{a,1},{b,2},{c,3}]))#{ a := 42, b := 42, c := 42 }, + #{ "a" :=1, "b":=42, "c":=42 } = (maps:from_list([{"a",1},{"b",2}]))#{ "b" := 42, "c" => 42 }, + + %% Error cases, FIXME: should be 'badmap'? + {'EXIT',{badarg,_}} = (catch (id(<<>>))#{ a := 42, b => 2 }), + {'EXIT',{badarg,_}} = (catch (id([]))#{ a := 42, b => 2 }), + ok. + + +t_update_assoc(Config) when is_list(Config) -> + M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e}), + + M1 = M0#{1=>42,2=>100,4=>[a,b,c]}, + #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1, + #{1:=42,2:=b,4:=d,5:=e,2.0:=100,3.0:=c,4.0:=[a,b,c]} = M0#{1.0=>float,1:=42,2.0=>wrong,2.0=>100,4.0=>[a,b,c]}, + + M2 = M0#{3.0=>new}, + #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2, + M2 = M0#{3.0:=wrong,3.0=>new}, + + %% Errors cases. + BadMap = id(badmap), + {'EXIT',{badarg,_}} = (catch BadMap#{nonexisting=>val}), + + ok. + +t_update_exact(Config) when is_list(Config) -> + M0 = id(#{1=>a,2=>b,3.0=>c,4=>d,5=>e}), + + M1 = M0#{1:=42,2:=100,4:=[a,b,c]}, + #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1, + M1 = M0#{1:=wrong,1=>42,2=>wrong,2:=100,4:=[a,b,c]}, + + M2 = M0#{3.0:=new}, + #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2, + M2 = M0#{3.0=>wrong,3.0:=new}, + %% M2 = M0#{3=>wrong,3.0:=new}, %% FIXME + + %% Errors cases. + {'EXIT',{badarg,_}} = (catch M0#{nonexisting:=val}), + {'EXIT',{badarg,_}} = (catch M0#{1.0:=v,1.0=>v2}), + {'EXIT',{badarg,_}} = (catch M0#{42.0:=v,42:=v2}), + {'EXIT',{badarg,_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}), + + ok. + +t_guard_bifs(Config) when is_list(Config) -> + true = map_guard_head(#{a=>1}), + false = map_guard_head([]), + true = map_guard_body(#{a=>1}), + false = map_guard_body({}), + true = map_guard_pattern(#{a=>1, <<"hi">> => "hi" }), + false = map_guard_pattern("list"), + ok. + +map_guard_head(M) when is_map(M) -> true; +map_guard_head(_) -> false. + +map_guard_body(M) -> is_map(M). + +map_guard_pattern(#{}) -> true; +map_guard_pattern(_) -> false. + +t_guard_sequence(Config) when is_list(Config) -> + {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}), + {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}), + {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}), + {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}), + {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}), + + {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})), + {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})), + {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})), + {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})), + {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})), + + %% error case + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})), + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})), + ok. + +map_guard_sequence_1(#{seq:=1=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=2=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=3=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=4=Seq, val:=Val}) -> {Seq,Val}; +map_guard_sequence_1(#{seq:=5=Seq, val:=Val}) -> {Seq,Val}. + +map_guard_sequence_2(#{ a:=3 }=M) -> {1, M}; +map_guard_sequence_2(#{ a:=4 }=M) -> {2, M}; +map_guard_sequence_2(#{ a:=X, a:=X, b:=4 }=M) -> {3,X,M}; +map_guard_sequence_2(#{ a:=X, a:=Y, b:=3 }=M) when X =:= Y -> {4,X,Y,M}; +map_guard_sequence_2(#{ a:=X, a:=Y }=M) when X =:= Y -> {5,X,Y,M}. + + +t_guard_update(Config) when is_list(Config) -> + error = map_guard_update(#{},#{}), + first = map_guard_update(#{}, #{x=>first}), + second = map_guard_update(#{y=>old}, #{x=>second,y=>old}), + ok. + +map_guard_update(M1, M2) when M1#{x=>first} =:= M2 -> first; +map_guard_update(M1, M2) when M1#{x=>second} =:= M2 -> second; +map_guard_update(_, _) -> error. + +t_guard_receive(Config) when is_list(Config) -> + M0 = #{ id => 0 }, + Pid = spawn_link(fun() -> guard_receive_loop() end), + Big = 36893488147419103229, + B1 = <<"some text">>, + B2 = <<"was appended">>, + B3 = <<B1/binary, B2/binary>>, + + #{id:=1, res:=Big} = M1 = call(Pid, M0#{op=>sub,in=>{1 bsl 65, 3}}), + #{id:=2, res:=26} = M2 = call(Pid, M1#{op=>idiv,in=>{53,2}}), + #{id:=3, res:=832} = M3 = call(Pid, M2#{op=>imul,in=>{26,32}}), + #{id:=4, res:=4} = M4 = call(Pid, M3#{op=>add,in=>{1,3}}), + #{id:=5, res:=Big} = M5 = call(Pid, M4#{op=>sub,in=>{1 bsl 65, 3}}), + #{id:=6, res:=B3} = M6 = call(Pid, M5#{op=>"append",in=>{B1,B2}}), + #{id:=7, res:=4} = _ = call(Pid, M6#{op=>add,in=>{1,3}}), + + + %% update old maps and check id update + #{id:=2, res:=B3} = call(Pid, M1#{op=>"append",in=>{B1,B2}}), + #{id:=5, res:=99} = call(Pid, M4#{op=>add,in=>{33, 66}}), + + %% cleanup + done = call(Pid, done), + ok. + +call(Pid, M) -> + Pid ! {self(), M}, receive {Pid, Res} -> Res end. + +guard_receive_loop() -> + receive + {Pid, #{ id:=Id, op:="append", in:={X,Y}}=M} when is_binary(X), is_binary(Y) -> + Pid ! {self(), M#{ id=>Id+1, res=><<X/binary,Y/binary>>}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=add, in:={X,Y}}} -> + Pid ! {self(), #{ id=>Id+1, res=>X+Y}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=sub, in:={X,Y}}=M} -> + Pid ! {self(), M#{ id=>Id+1, res=>X-Y}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=idiv, in:={X,Y}}=M} -> + Pid ! {self(), M#{ id=>Id+1, res=>X div Y}}, + guard_receive_loop(); + {Pid, #{ id:=Id, op:=imul, in:={X,Y}}=M} -> + Pid ! {self(), M#{ id=>Id+1, res=>X * Y}}, + guard_receive_loop(); + {Pid, done} -> + Pid ! {self(), done}; + {Pid, Other} -> + Pid ! {error, Other}, + guard_receive_loop() + end. + + +t_list_comprehension(Config) when is_list(Config) -> + [#{k:=1},#{k:=2},#{k:=3}] = [#{k=>I} || I <- [1,2,3]], + ok. + +t_guard_fun(Config) when is_list(Config) -> + F1 = fun + (#{s:=v,v:=V}) -> {v,V}; + (#{s:=t,v:={V,V}}) -> {t,V}; + (#{s:=l,v:=[V,V]}) -> {l,V} + end, + + F2 = fun + (#{s:=T,v:={V,V}}) -> {T,V}; + (#{s:=T,v:=[V,V]}) -> {T,V}; + (#{s:=T,v:=V}) -> {T,V} + end, + V = <<"hi">>, + + {v,V} = F1(#{s=>v,v=>V}), + {t,V} = F1(#{s=>t,v=>{V,V}}), + {l,V} = F1(#{s=>l,v=>[V,V]}), + + {v,V} = F2(#{s=>v,v=>V}), + {t,V} = F2(#{s=>t,v=>{V,V}}), + {l,V} = F2(#{s=>l,v=>[V,V]}), + + %% error case + {'EXIT', {function_clause,[{?MODULE,_,[#{s:=none,v:=none}],_}|_]}} = (catch F1(#{s=>none,v=>none})), + ok. + + +t_map_sort_literals(Config) when is_list(Config) -> + % test relation + + %% size order + true = #{ a => 1, b => 2} < id(#{ a => 1, b => 1, c => 1}), + true = #{ b => 1, a => 1} < id(#{ c => 1, a => 1, b => 1}), + false = #{ c => 1, b => 1, a => 1} < id(#{ c => 1, a => 1}), + + %% key order + true = #{ a => 1 } < id(#{ b => 1}), + false = #{ b => 1 } < id(#{ a => 1}), + true = #{ a => 1, b => 1, c => 1 } < id(#{ b => 1, c => 1, d => 1}), + true = #{ b => 1, c => 1, d => 1 } > id(#{ a => 1, b => 1, c => 1}), + true = #{ c => 1, b => 1, a => 1 } < id(#{ b => 1, c => 1, d => 1}), + true = #{ "a" => 1 } < id(#{ <<"a">> => 1}), + false = #{ <<"a">> => 1 } < id(#{ "a" => 1}), + false = #{ 1 => 1 } < id(#{ 1.0 => 1}), + false = #{ 1.0 => 1 } < id(#{ 1 => 1}), + + %% value order + true = #{ a => 1 } < id(#{ a => 2}), + false = #{ a => 2 } < id(#{ a => 1}), + false = #{ a => 2, b => 1 } < id(#{ a => 1, b => 3}), + true = #{ a => 1, b => 1 } < id(#{ a => 1, b => 3}), + + true = #{ "a" => "hi", b => 134 } == id(#{ b => 134,"a" => "hi"}), + + %% lists:sort + + SortVs = [#{"a"=>1},#{a=>2},#{1=>3},#{<<"a">>=>4}], + [#{1:=ok},#{a:=ok},#{"a":=ok},#{<<"a">>:=ok}] = lists:sort([#{"a"=>ok},#{a=>ok},#{1=>ok},#{<<"a">>=>ok}]), + [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(SortVs), + [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(lists:reverse(SortVs)), + + ok. + +%% BIFs +t_bif_map_get(Config) when is_list(Config) -> + + 1 = maps:get(a, #{ a=> 1}), + 2 = maps:get(b, #{ a=> 1, b => 2}), + "hi" = maps:get("hello", #{ a=>1, "hello" => "hi"}), + "tuple hi" = maps:get({1,1.0}, #{ a=>a, {1,1.0} => "tuple hi"}), + + M = id(#{ k1=>"v1", <<"k2">> => <<"v3">> }), + "v4" = maps:get(<<"k2">>, M#{ <<"k2">> => "v4" }), + + %% error case + {'EXIT',{badarg, [{maps,get,_,_}|_]}} = (catch maps:get(a,[])), + {'EXIT',{badarg, [{maps,get,_,_}|_]}} = (catch maps:get(a,<<>>)), + {'EXIT',{bad_key,[{maps,get,_,_}|_]}} = (catch maps:get({1,1}, #{{1,1.0} => "tuple"})), + {'EXIT',{bad_key,[{maps,get,_,_}|_]}} = (catch maps:get(a,#{})), + {'EXIT',{bad_key,[{maps,get,_,_}|_]}} = (catch maps:get(a,#{ b=>1, c=>2})), + ok. + +t_bif_map_find(Config) when is_list(Config) -> + + {ok, 1} = maps:find(a, #{ a=> 1}), + {ok, 2} = maps:find(b, #{ a=> 1, b => 2}), + {ok, "int"} = maps:find(1, #{ 1 => "int"}), + {ok, "float"} = maps:find(1.0, #{ 1.0=> "float"}), + + {ok, "hi"} = maps:find("hello", #{ a=>1, "hello" => "hi"}), + {ok, "tuple hi"} = maps:find({1,1.0}, #{ a=>a, {1,1.0} => "tuple hi"}), + + M = id(#{ k1=>"v1", <<"k2">> => <<"v3">> }), + {ok, "v4"} = maps:find(<<"k2">>, M#{ <<"k2">> => "v4" }), + + %% error case + error = maps:find(a,#{}), + error = maps:find(a,#{b=>1, c=>2}), + error = maps:find(1.0, #{ 1 => "int"}), + error = maps:find(1, #{ 1.0 => "float"}), + error = maps:find({1.0,1}, #{ a=>a, {1,1.0} => "tuple hi"}), % reverse types in tuple key + + + {'EXIT',{badarg,[{maps,find,_,_}|_]}} = (catch maps:find(a,id([]))), + {'EXIT',{badarg,[{maps,find,_,_}|_]}} = (catch maps:find(a,id(<<>>))), + ok. + + +t_bif_map_is_key(Config) when is_list(Config) -> + M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number}, + + true = maps:is_key("hi", M1), + true = maps:is_key(int, M1), + true = maps:is_key(<<"key">>, M1), + true = maps:is_key(4, M1), + + false = maps:is_key(5, M1), + false = maps:is_key(<<"key2">>, M1), + false = maps:is_key("h", M1), + false = maps:is_key("hello", M1), + false = maps:is_key(atom, M1), + false = maps:is_key(any, id(#{})), + + false = maps:is_key("hi", maps:remove("hi", M1)), + true = maps:is_key("hi", M1), + true = maps:is_key(1, maps:put(1, "number", M1)), + false = maps:is_key(1.0, maps:put(1, "number", M1)), + + %% error case + {'EXIT',{badarg,[{maps,is_key,_,_}|_]}} = (catch maps:is_key(a,id([]))), + {'EXIT',{badarg,[{maps,is_key,_,_}|_]}} = (catch maps:is_key(a,id(<<>>))), + ok. + +t_bif_map_keys(Config) when is_list(Config) -> + [] = maps:keys(#{}), + + [1,2,3,4,5] = maps:keys(#{ 1 => a, 2 => b, 3 => c, 4 => d, 5 => e}), + [1,2,3,4,5] = maps:keys(#{ 4 => d, 5 => e, 1 => a, 2 => b, 3 => c}), + + % values in key order: [4,int,"hi",<<"key">>] + M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number}, + [4,int,"hi",<<"key">>] = maps:keys(M1), + + %% error case + {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(1 bsl 65 + 3)), + {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(154)), + {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(atom)), + {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys([])), + {'EXIT',{badarg,[{maps,keys,_,_}|_]}} = (catch maps:keys(<<>>)), + ok. + +t_bif_map_new(Config) when is_list(Config) -> + #{} = maps:new(), + 0 = erlang:map_size(maps:new()), + ok. + +t_bif_map_merge(Config) when is_list(Config) -> + 0 = erlang:map_size(maps:merge(#{},#{})), + + M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}, + + #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>, + 4 := number, 18446744073709551629 := wat} = maps:merge(#{}, M0), + + #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>, + 4 := number, 18446744073709551629 := wat} = maps:merge(M0, #{}), + + M1 = #{ "hi" => "hello again", float => 3.3, {1,2} => "tuple", 4 => integer }, + + #{4 := number, 18446744073709551629 := wat, float := 3.3, int := 3, + {1,2} := "tuple", "hi" := "hello", <<"key">> := <<"value">>} = maps:merge(M1,M0), + + #{4 := integer, 18446744073709551629 := wat, float := 3.3, int := 3, + {1,2} := "tuple", "hi" := "hello again", <<"key">> := <<"value">>} = maps:merge(M0,M1), + + %% error case + {'EXIT',{badarg,[{maps,merge,_,_}|_]}} = (catch maps:merge((1 bsl 65 + 3), <<>>)), + {'EXIT',{badarg,[{maps,merge,_,_}|_]}} = (catch maps:merge(<<>>, id(#{ a => 1}))), + {'EXIT',{badarg,[{maps,merge,_,_}|_]}} = (catch maps:merge(id(#{ a => 2}), <<>> )), + + ok. + + +t_bif_map_put(Config) when is_list(Config) -> + M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}, + + M1 = #{ "hi" := "hello"} = maps:put("hi", "hello", #{}), + + ["hi"] = maps:keys(M1), + ["hello"] = maps:values(M1), + + M2 = #{ int := 3 } = maps:put(int, 3, M1), + + [int,"hi"] = maps:keys(M2), + [3,"hello"] = maps:values(M2), + + M3 = #{ <<"key">> := <<"value">> } = maps:put(<<"key">>, <<"value">>, M2), + + [int,"hi",<<"key">>] = maps:keys(M3), + [3,"hello",<<"value">>] = maps:values(M3), + + M4 = #{ 18446744073709551629 := wat } = maps:put(18446744073709551629, wat, M3), + + [18446744073709551629,int,"hi",<<"key">>] = maps:keys(M4), + [wat,3,"hello",<<"value">>] = maps:values(M4), + + M0 = #{ 4 := number } = M5 = maps:put(4, number, M4), + + [4,18446744073709551629,int,"hi",<<"key">>] = maps:keys(M5), + [number,wat,3,"hello",<<"value">>] = maps:values(M5), + + M6 = #{ <<"key">> := <<"other value">> } = maps:put(<<"key">>, <<"other value">>, M5), + + [4,18446744073709551629,int,"hi",<<"key">>] = maps:keys(M6), + [number,wat,3,"hello",<<"other value">>] = maps:values(M6), + + %% error case + {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,1 bsl 65 + 3)), + {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,154)), + {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,atom)), + {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,[])), + {'EXIT',{badarg,[{maps,put,_,_}|_]}} = (catch maps:put(1,a,<<>>)), + ok. + +t_bif_map_remove(Config) when is_list(Config) -> + 0 = erlang:map_size(maps:remove(some_key, #{})), + + M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}, + + M1 = maps:remove("hi", M0), + [4,18446744073709551629,int,<<"key">>] = maps:keys(M1), + [number,wat,3,<<"value">>] = maps:values(M1), + + M2 = maps:remove(int, M1), + [4,18446744073709551629,<<"key">>] = maps:keys(M2), + [number,wat,<<"value">>] = maps:values(M2), + + M3 = maps:remove(<<"key">>, M2), + [4,18446744073709551629] = maps:keys(M3), + [number,wat] = maps:values(M3), + + M4 = maps:remove(18446744073709551629, M3), + [4] = maps:keys(M4), + [number] = maps:values(M4), + + M5 = maps:remove(4, M4), + [] = maps:keys(M5), + [] = maps:values(M5), + + M0 = maps:remove(5,M0), + M0 = maps:remove("hi there",M0), + + #{ "hi" := "hello", int := 3, 4 := number} = maps:remove(18446744073709551629,maps:remove(<<"key">>,M0)), + + %% error case + {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(a,1 bsl 65 + 3)), + {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(1,154)), + {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(a,atom)), + {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(1,[])), + {'EXIT',{badarg,[{maps,remove,_,_}|_]}} = (catch maps:remove(a,<<>>)), + ok. + +t_bif_map_update(Config) when is_list(Config) -> + M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, + 4 => number, 18446744073709551629 => wat}, + + #{ "hi" := "hello again", int := 3, <<"key">> := <<"value">>, + 4 := number, 18446744073709551629 := wat} = maps:update("hi", "hello again", M0), + + #{ "hi" := "hello", int := 1337, <<"key">> := <<"value">>, + 4 := number, 18446744073709551629 := wat} = maps:update(int, 1337, M0), + + #{ "hi" := "hello", int := 3, <<"key">> := <<"new value">>, + 4 := number, 18446744073709551629 := wat} = maps:update(<<"key">>, <<"new value">>, M0), + + #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>, + 4 := integer, 18446744073709551629 := wat} = maps:update(4, integer, M0), + + #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>, + 4 := number, 18446744073709551629 := wazzup} = maps:update(18446744073709551629, wazzup, M0), + + %% error case + {'EXIT',{badarg,[{maps,update,_,_}|_]}} = (catch maps:update(1,none,{})), + {'EXIT',{badarg,[{maps,update,_,_}|_]}} = (catch maps:update(1,none,<<"value">>)), + {'EXIT',{badarg,[{maps,update,_,_}|_]}} = (catch maps:update(5,none,M0)), + + ok. + + + +t_bif_map_values(Config) when is_list(Config) -> + + [] = maps:values(#{}), + + [a,b,c,d,e] = maps:values(#{ 1 => a, 2 => b, 3 => c, 4 => d, 5 => e}), + [a,b,c,d,e] = maps:values(#{ 4 => d, 5 => e, 1 => a, 2 => b, 3 => c}), + + % values in key order: [4,int,"hi",<<"key">>] + M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number}, + M2 = M1#{ "hi" => "hello2", <<"key">> => <<"value2">> }, + [number,3,"hello2",<<"value2">>] = maps:values(M2), + [number,3,"hello",<<"value">>] = maps:values(M1), + + %% error case + {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values(1 bsl 65 + 3)), + {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values(atom)), + {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values([])), + {'EXIT',{badarg,[{maps,values,_,_}|_]}} = (catch maps:values(<<>>)), + ok. + +t_erlang_hash(Config) when is_list(Config) -> + + ok = t_bif_erlang_phash2(), + ok = t_bif_erlang_phash(), + ok = t_bif_erlang_hash(), + + ok. + +t_bif_erlang_phash2() -> + + 39679005 = erlang:phash2(#{}), + 78942764 = erlang:phash2(#{ a => 1, "a" => 2, <<"a">> => 3, {a,b} => 4 }), + 37338230 = erlang:phash2(#{ 1 => a, 2 => "a", 3 => <<"a">>, 4 => {a,b} }), + 14363616 = erlang:phash2(#{ 1 => a }), + 51612236 = erlang:phash2(#{ a => 1 }), + + 37468437 = erlang:phash2(#{{} => <<>>}), + 44049159 = erlang:phash2(#{<<>> => {}}), + + M0 = #{ a => 1, "key" => <<"value">> }, + M1 = maps:remove("key",M0), + M2 = M1#{ "key" => <<"value">> }, + + 118679416 = erlang:phash2(M0), + 51612236 = erlang:phash2(M1), + 118679416 = erlang:phash2(M2), + ok. + +t_bif_erlang_phash() -> + Sz = 1 bsl 32, + 268440612 = erlang:phash(#{},Sz), + 1196461908 = erlang:phash(#{ a => 1, "a" => 2, <<"a">> => 3, {a,b} => 4 },Sz), + 3944426064 = erlang:phash(#{ 1 => a, 2 => "a", 3 => <<"a">>, 4 => {a,b} },Sz), + 1394238263 = erlang:phash(#{ 1 => a },Sz), + 4066388227 = erlang:phash(#{ a => 1 },Sz), + + 1578050717 = erlang:phash(#{{} => <<>>},Sz), + 1578050717 = erlang:phash(#{<<>> => {}},Sz), % yep, broken + + M0 = #{ a => 1, "key" => <<"value">> }, + M1 = maps:remove("key",M0), + M2 = M1#{ "key" => <<"value">> }, + + 3590546636 = erlang:phash(M0,Sz), + 4066388227 = erlang:phash(M1,Sz), + 3590546636 = erlang:phash(M2,Sz), + ok. + +t_bif_erlang_hash() -> + Sz = 1 bsl 27 - 1, + 5158 = erlang:hash(#{},Sz), + 71555838 = erlang:hash(#{ a => 1, "a" => 2, <<"a">> => 3, {a,b} => 4 },Sz), + 5497225 = erlang:hash(#{ 1 => a, 2 => "a", 3 => <<"a">>, 4 => {a,b} },Sz), + 126071654 = erlang:hash(#{ 1 => a },Sz), + 126426236 = erlang:hash(#{ a => 1 },Sz), + + 101655720 = erlang:hash(#{{} => <<>>},Sz), + 101655720 = erlang:hash(#{<<>> => {}},Sz), % yep, broken + + M0 = #{ a => 1, "key" => <<"value">> }, + M1 = maps:remove("key",M0), + M2 = M1#{ "key" => <<"value">> }, + + 38260486 = erlang:hash(M0,Sz), + 126426236 = erlang:hash(M1,Sz), + 38260486 = erlang:hash(M2,Sz), + ok. + + +t_map_encode_decode(Config) when is_list(Config) -> + <<131,116,0,0,0,0>> = erlang:term_to_binary(#{}), + Pairs = [ + {a,b},{"key","values"},{<<"key">>,<<"value">>}, + {1,b},{[atom,1],{<<"wat">>,1,2,3}}, + {aa,"values"}, + {1 bsl 64 + (1 bsl 50 - 1), sc1}, + {99, sc2}, + {1 bsl 65 + (1 bsl 51 - 1), sc3}, + {88, sc4}, + {1 bsl 66 + (1 bsl 52 - 1), sc5}, + {77, sc6}, + {1 bsl 67 + (1 bsl 53 - 1), sc3}, + {75, sc6}, {-10,sc8}, + {<<>>, sc9}, {3.14158, sc10}, + {[3.14158], sc11}, {more_atoms, sc12}, + {{more_tuples}, sc13}, {self(), sc14}, + {{},{}},{[],[]} + ], + ok = map_encode_decode_and_match(Pairs,[],#{}), + + %% check sorting + + %% literally #{ b=>2, a=>1 } in the internal order + #{ a:=1, b:=2 } = + erlang:binary_to_term(<<131,116,0,0,0,2,100,0,1,98,100,0,1,97,97,2,97,1>>), + + + %% literally #{ "hi" => "value", a=>33, b=>55 } in the internal order + #{ a:=33, b:=55, "hi" := "value"} = erlang:binary_to_term(<<131,116,0,0,0,3, + 107,0,2,104,105, % "hi" :: list() + 100,0,1,97, % a :: atom() + 100,0,1,98, % b :: atom() + 107,0,5,118,97,108,117,101, % "value" :: list() + 97,33, % 33 :: integer() + 97,55 % 55 :: integer() + >>), + + + %% error cases + %% template: <<131,116,0,0,0,2,100,0,1,97,100,0,1,98,97,1,97,1>> + %% which is: #{ a=>1, b=>1 } + + %% uniqueness violation + %% literally #{ a=>1, "hi"=>"value", a=>2 } + {'EXIT',{badarg,[{_,_,_,_}|_]}} = (catch + erlang:binary_to_term(<<131,116,0,0,0,3,100,0,1,97,107,0,2,104,105,100,0,1,97,97,1,107,0,5,118,97,108,117,101,97,2>>)), + + %% bad size (too large) + {'EXIT',{badarg,[{_,_,_,_}|_]}} = (catch + erlang:binary_to_term(<<131,116,0,0,0,12,100,0,1,97,100,0,1,98,97,1,97,1>>)), + + %% bad size (too small) .. should fail just truncate it .. weird. + %% possibly change external format so truncated will be #{a:=1} + #{ a:=b } = + erlang:binary_to_term(<<131,116,0,0,0,1,100,0,1,97,100,0,1,98,97,1,97,1>>), + + ok. + +map_encode_decode_and_match([{K,V}|Pairs], EncodedPairs, M0) -> + M1 = maps:put(K,V,M0), + B0 = erlang:term_to_binary(M1), + Ls = lists:sort(fun(A,B) -> erts_internal:cmp_term(A,B) < 0 end, [{K, erlang:term_to_binary(K), erlang:term_to_binary(V)}|EncodedPairs]), + %% sort Ks and Vs according to term spec, then match it + ok = match_encoded_map(B0, length(Ls), [Kbin||{_,Kbin,_}<-Ls] ++ [Vbin||{_,_,Vbin}<-Ls]), + %% decode and match it + M1 = erlang:binary_to_term(B0), + map_encode_decode_and_match(Pairs,Ls,M1); +map_encode_decode_and_match([],_,_) -> ok. + +match_encoded_map(<<131,116,Size:32,Encoded/binary>>,Size,Items) -> + match_encoded_map(Encoded,Items); +match_encoded_map(_,_,_) -> no_match_size. + +match_encoded_map(<<>>,[]) -> ok; +match_encoded_map(Bin,[<<131,Item/binary>>|Items]) -> + Size = erlang:byte_size(Item), + <<EncodedTerm:Size/binary, Bin1/binary>> = Bin, + EncodedTerm = Item, %% Asssert + match_encoded_map(Bin1,Items). + + +t_bif_map_to_list(Config) when is_list(Config) -> + [] = maps:to_list(#{}), + [{a,1},{b,2}] = maps:to_list(#{a=>1,b=>2}), + [{a,1},{b,2},{c,3}] = maps:to_list(#{c=>3,a=>1,b=>2}), + [{a,1},{b,2},{g,3}] = maps:to_list(#{g=>3,a=>1,b=>2}), + [{a,1},{b,2},{g,3},{"c",4}] = maps:to_list(#{g=>3,a=>1,b=>2,"c"=>4}), + [{3,v2},{hi,v4},{{hi,3},v5},{"hi",v3},{<<"hi">>,v1}] = maps:to_list(#{ + <<"hi">>=>v1,3=>v2,"hi"=>v3,hi=>v4,{hi,3}=>v5}), + + [{3,v7},{hi,v9},{{hi,3},v10},{"hi",v8},{<<"hi">>,v6}] = maps:to_list(#{ + <<"hi">>=>v1,3=>v2,"hi"=>v3,hi=>v4,{hi,3}=>v5, + <<"hi">>=>v6,3=>v7,"hi"=>v8,hi=>v9,{hi,3}=>v10}), + + %% error cases + {'EXIT', {badarg,_}} = (catch maps:to_list(id(a))), + {'EXIT', {badarg,_}} = (catch maps:to_list(id(42))), + ok. + + +t_bif_map_from_list(Config) when is_list(Config) -> + #{} = maps:from_list([]), + A = maps:from_list([]), + 0 = erlang:map_size(A), + + #{a:=1,b:=2} = maps:from_list([{a,1},{b,2}]), + #{c:=3,a:=1,b:=2} = maps:from_list([{a,1},{b,2},{c,3}]), + #{g:=3,a:=1,b:=2} = maps:from_list([{a,1},{b,2},{g,3}]), + + #{a:=2} = maps:from_list([{a,1},{a,3},{a,2}]), + + #{ <<"hi">>:=v1,3:=v3,"hi":=v6,hi:=v4,{hi,3}:=v5} = + maps:from_list([{3,v3},{"hi",v6},{hi,v4},{{hi,3},v5},{<<"hi">>,v1}]), + + #{<<"hi">>:=v6,3:=v8,"hi":=v11,hi:=v9,{hi,3}:=v10} = + maps:from_list([ {{hi,3},v3}, {"hi",v0},{3,v1}, {<<"hi">>,v4}, {hi,v2}, + {<<"hi">>,v6}, {{hi,3},v10},{"hi",v11}, {hi,v9}, {3,v8}]), + + %% error cases + {'EXIT', {badarg,_}} = (catch maps:from_list(id([{a,b},b]))), + {'EXIT', {badarg,_}} = (catch maps:from_list(id([{a,b},{b,b,3}]))), + {'EXIT', {badarg,_}} = (catch maps:from_list(id([{a,b},<<>>]))), + {'EXIT', {badarg,_}} = (catch maps:from_list(id([{a,b}|{b,a}]))), + {'EXIT', {badarg,_}} = (catch maps:from_list(id(a))), + {'EXIT', {badarg,_}} = (catch maps:from_list(id(42))), + ok. + +%% Maps module, not BIFs +t_maps_fold(_Config) -> + Vs = lists:seq(1,100), + M = maps:from_list([{{k,I},{v,I}}||I<-Vs]), + + %% fold + 5050 = maps:fold(fun({k,_},{v,V},A) -> V + A end, 0, M), + + ok. + +t_maps_map(_Config) -> + Vs = lists:seq(1,100), + M1 = maps:from_list([{I,I}||I<-Vs]), + M2 = maps:from_list([{I,{token,I}}||I<-Vs]), + + M2 = maps:map(fun(_K,V) -> {token,V} end, M1), + ok. + +t_maps_size(_Config) -> + Vs = lists:seq(1,100), + lists:foldl(fun(I,M) -> + M1 = maps:put(I,I,M), + I = maps:size(M1), + M1 + end, #{}, Vs), + ok. + + +t_maps_without(_Config) -> + Ki = [11,22,33,44,55,66,77,88,99], + M0 = maps:from_list([{{k,I},{v,I}}||I<-lists:seq(1,100)]), + M1 = maps:from_list([{{k,I},{v,I}}||I<-lists:seq(1,100) -- Ki]), + M1 = maps:without([{k,I}||I <- Ki],M0), + ok. + + +%% MISC +t_pdict(_Config) -> + + put(#{ a => b, b => a},#{ c => d}), + put(get(#{ a => b, b => a}),1), + 1 = get(#{ c => d}), + #{ c := d } = get(#{ a => b, b => a}). + +t_ets(_Config) -> + + Tid = ets:new(map_table,[]), + + [ets:insert(Tid,{maps:from_list([{I,-I}]),I}) || I <- lists:seq(1,100)], + + + [{#{ 2 := -2},2}] = ets:lookup(Tid,#{ 2 => -2 }), + + %% Test equal + [3,4] = lists:sort( + ets:select(Tid,[{{'$1','$2'}, + [{'or',{'==','$1',#{ 3 => -3 }}, + {'==','$1',#{ 4 => -4 }}}], + ['$2']}])), + %% Test match + [30,50] = lists:sort( + ets:select(Tid, + [{{#{ 30 => -30}, '$1'},[],['$1']}, + {{#{ 50 => -50}, '$1'},[],['$1']}] + )), + + ets:insert(Tid,{#{ a => b, b => c, c => a},transitivity}), + + %% Test equal with map of different size + [] = ets:select(Tid,[{{'$1','_'},[{'==','$1',#{ b => c }}],['$_']}]), + + %% Test match with map of different size + %[{#{ a := b },_}] = ets:select(Tid,[{{#{ b => c },'_'},[],['$_']}]), + + %%% Test match with don't care value + %[{#{ a := b },_}] = ets:select(Tid,[{{#{ b => '_' },'_'},[],['$_']}]), + + %% Test is_map bif + 101 = length(ets:select(Tid,[{'$1',[{is_map,{element,1,'$1'}}],['$1']}])), + ets:insert(Tid,{not_a_map,2}), + 101 = length(ets:select(Tid,[{'$1',[{is_map,{element,1,'$1'}}],['$1']}])), + ets:insert(Tid,{{nope,a,tuple},2}), + 101 = length(ets:select(Tid,[{'$1',[{is_map,{element,1,'$1'}}],['$1']}])), + + %% Test map_size bif + [3] = ets:select(Tid,[{{'$1','_'},[{'==',{map_size,'$1'},3}], + [{map_size,'$1'}]}]), + + true = ets:delete(Tid,#{50 => -50}), + [] = ets:lookup(Tid,#{50 => -50}), + + ets:delete(Tid), + ok. + +t_dets(_Config) -> + ok. + +getmsg(_Tracer) -> + receive V -> V after 100 -> timeout end. + +trace_collector(Msg,Parent) -> + io:format("~p~n",[Msg]), + Parent ! Msg, + Parent. + +%% Use this function to avoid compile-time evaluation of an expression. +id(I) -> I. diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml index 2a631c3010..a92b890a80 100644 --- a/lib/dialyzer/doc/src/dialyzer.xml +++ b/lib/dialyzer/doc/src/dialyzer.xml @@ -229,7 +229,7 @@ match.</item> <tag><c><![CDATA[-Wno_opaque]]></c></tag> <item>Suppress warnings for violations of opaqueness of data types.</item> - <tag><c><![CDATA[-Wno_behaviours]]></c>***</tag> + <tag><c><![CDATA[-Wno_behaviours]]></c></tag> <item>Suppress warnings about behaviour callbacks which drift from the published recommended interfaces.</item> <tag><c><![CDATA[-Wunmatched_returns]]></c>***</tag> diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src index 9222a28a77..0d048b607e 100644 --- a/lib/dialyzer/src/dialyzer.app.src +++ b/lib/dialyzer/src/dialyzer.app.src @@ -2,7 +2,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% Copyright Ericsson AB 2006-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -29,17 +29,19 @@ dialyzer_cl_parse, dialyzer_codeserver, dialyzer_contracts, + dialyzer_coordinator, dialyzer_dataflow, dialyzer_dep, dialyzer_explanation, - dialyzer_gui, dialyzer_gui_wx, dialyzer_options, dialyzer_plt, dialyzer_races, dialyzer_succ_typings, dialyzer_typesig, - dialyzer_utils]}, + dialyzer_utils, + dialyzer_timing, + dialyzer_worker]}, {registered, []}, {applications, [compiler, gs, hipe, kernel, stdlib, wx]}, {env, []}]}. diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 33fa107019..b00e0465e0 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -58,10 +58,12 @@ t_fun_range/2, t_integer/0, t_integers/1, t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_any_atom/3, t_is_boolean/2, - t_is_integer/2, t_is_nil/2, t_is_none/1, t_is_none_or_unit/1, + t_is_integer/2, t_is_list/1, + t_is_nil/2, t_is_none/1, t_is_none_or_unit/1, t_is_number/2, t_is_reference/2, t_is_pid/2, t_is_port/2, t_is_unit/1, - t_limit/2, t_list/0, t_maybe_improper_list/0, t_module/0, + t_limit/2, t_list/0, t_list_elements/2, + t_maybe_improper_list/0, t_module/0, t_none/0, t_non_neg_integer/0, t_number/0, t_number_vals/2, t_pid/0, t_port/0, t_product/1, t_reference/0, t_to_string/2, t_to_tlist/1, @@ -293,6 +295,7 @@ traverse(Tree, Map, State) -> t_is_any(ArgType) orelse t_is_simple(ArgType, State) orelse is_call_to_send(Arg) + orelse is_lc_simple_list(Arg, ArgType, State) of true -> % do not warn in these cases State1; @@ -2302,6 +2305,9 @@ bind_guard_list([], Map, _Env, _Eval, _State, Acc) -> signal_guard_fail(Eval, Guard, ArgTypes, State) -> signal_guard_failure(Eval, Guard, ArgTypes, fail, State). +-spec signal_guard_fatal_fail(eval(), cerl:c_call(), [erl_types:erl_type()], + state()) -> no_return(). + signal_guard_fatal_fail(Eval, Guard, ArgTypes, State) -> signal_guard_failure(Eval, Guard, ArgTypes, fatal_fail, State). @@ -2710,6 +2716,13 @@ is_call_to_send(Tree) -> andalso (Arity =:= 2) end. +is_lc_simple_list(Tree, TreeType, State) -> + Opaques = State#state.opaques, + Ann = cerl:get_ann(Tree), + lists:member(list_comprehension, Ann) + andalso t_is_list(TreeType) + andalso t_is_simple(t_list_elements(TreeType, Opaques), State). + filter_match_fail([Clause] = Cls) -> Body = cerl:clause_body(Clause), case cerl:type(Body) of diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index b4b3d5a092..70e14781f7 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -2535,8 +2535,10 @@ enter_type(Key, Val, Map) when is_integer(Key) -> erase_type(Key, Map); false -> LimitedVal = t_limit(Val, ?INTERNAL_TYPE_LIMIT), - [?debug("LimitedVal ~s\n", [format_type(LimitedVal)]) || - not is_equal(LimitedVal, Val)], + case is_equal(LimitedVal, Val) of + true -> ok; + false -> ?debug("LimitedVal ~s\n", [format_type(LimitedVal)]) + end, case dict:find(Key, Map) of {ok, Value} -> case is_equal(Value, LimitedVal) of diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile index 27cabc8ef8..f43e04dd59 100644 --- a/lib/dialyzer/test/Makefile +++ b/lib/dialyzer/test/Makefile @@ -11,6 +11,7 @@ AUXILIARY_FILES=\ dialyzer_test_constants.hrl\ dialyzer_common.erl\ file_utils.erl\ + dialyzer_SUITE.erl\ plt_SUITE.erl # ---------------------------------------------------- diff --git a/lib/dialyzer/test/dialyzer_SUITE.erl b/lib/dialyzer/test/dialyzer_SUITE.erl new file mode 100644 index 0000000000..63214c915d --- /dev/null +++ b/lib/dialyzer/test/dialyzer_SUITE.erl @@ -0,0 +1,73 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(dialyzer_SUITE). + +-include_lib("test_server/include/test_server.hrl"). + +%% Default timetrap timeout (set in init_per_testcase). +-define(default_timeout, ?t:minutes(1)). +-define(application, dialyzer). + +%% Test server specific exports +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, + init_per_group/2,end_per_group/2]). +-export([init_per_testcase/2, end_per_testcase/2]). + +%% Test cases must be exported. +-export([app_test/1]). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [app_test]. + +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + +init_per_testcase(_Case, Config) -> + ?line Dog=test_server:timetrap(?default_timeout), + [{watchdog, Dog}|Config]. +end_per_testcase(_Case, Config) -> + Dog=?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +%%% +%%% Test cases starts here. +%%% + +app_test(doc) -> + ["Test that the .app file does not contain any `basic' errors"]; +app_test(suite) -> + []; +app_test(Config) when is_list(Config) -> + ?line ?t:app_test(dialyzer). diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options b/lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options new file mode 100644 index 0000000000..49ac917f61 --- /dev/null +++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options @@ -0,0 +1 @@ +{dialyzer_options, [{warnings, [unmatched_returns]}]}. diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/results/lc_warnings b/lib/dialyzer/test/unmatched_returns_SUITE_data/results/lc_warnings new file mode 100644 index 0000000000..2784f2119e --- /dev/null +++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/results/lc_warnings @@ -0,0 +1,5 @@ + +lc_warnings.erl:32: Expression produces a value of type [opaque_atom_adt:opaque_atom()], but this value is unmatched +lc_warnings.erl:43: Expression produces a value of type [array()], but this value is unmatched +lc_warnings.erl:65: Expression produces a value of type [lc_warnings:opaque_tuple()], but this value is unmatched +lc_warnings.erl:7: Expression produces a value of type ['ok' | {'error',atom()}], but this value is unmatched diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/src/lc_warnings/lc_warnings.erl b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/lc_warnings/lc_warnings.erl new file mode 100644 index 0000000000..cb01a8fde3 --- /dev/null +++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/lc_warnings/lc_warnings.erl @@ -0,0 +1,95 @@ +-module(lc_warnings). +-compile([export_all]). + +close(Fs) -> + %% There should be a warning since we ignore a potential + %% {error,Error} return from file:close/1. + [file:close(F) || F <- Fs], + + %% No warning because the type of unmatched return will be ['ok'] + %% (which is a list of a simple type). + [ok = file:close(F) || F <- Fs], + + %% Suppressed. + _ = [file:close(F) || F <- Fs], + ok. + +format(X) -> + %% No warning since the result of the list comprehension is + %% a list of simple. + [io:format("~p\n", [E]) || E <- X], + + %% Warning explicitly suppressed. + _ = [io:format("~p\n", [E]) || E <- X], + ok. + +opaque1() -> + List = gen_atom(), + %% This is a list of an externally defined opaque type. Since + %% we are not allowed to peek inside opaque types, there should + %% be a warning (even though the type in this case happens to be + %% an atom). + [E || E <- List], + + %% Suppressed. + _ = [E || E <- List], + ok. + +opaque2() -> + List = gen_array(), + %% This is an list of an externally defined opaque type. Since + %% we are not allowed to peek inside opaque types, there should + %% be a warning. + [E || E <- List], + + %% Suppressed. + _ = [E || E <- List], + ok. + +opaque3() -> + List = gen_int(), + + %% No warning, since we are allowed to look into the type and can + %% see that it is a simple type. + [E || E <- List], + + %% Suppressed. + _ = [E || E <- List], + ok. + +opaque4() -> + List = gen_tuple(), + + %% There should be a warning, since we are allowed to look inside + %% the opaque type and see that it is a tuple (non-simple). + [E || E <- List], + + %% Suppressed. + _ = [E || E <- List], + ok. + +gen_atom() -> + [opaque_atom_adt:atom(ok)]. + +gen_array() -> + [array:new()]. + + +gen_int() -> + [opaque_int(42)]. + +gen_tuple() -> + [opaque_tuple(x, 25)]. + +-opaque opaque_int() :: integer(). + +-spec opaque_int(integer()) -> opaque_int(). + +opaque_int(Int) -> Int. + +-opaque opaque_tuple() :: {any(),any()}. + +-spec opaque_tuple(any(), any()) -> opaque_tuple(). + +opaque_tuple(X, Y) -> + {X,Y}. diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/src/lc_warnings/opaque_atom_adt.erl b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/lc_warnings/opaque_atom_adt.erl new file mode 100644 index 0000000000..b5b51fe75b --- /dev/null +++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/lc_warnings/opaque_atom_adt.erl @@ -0,0 +1,9 @@ +-module(opaque_atom_adt). +-export([atom/1]). + +-opaque opaque_atom() :: atom(). + +-spec atom(atom()) -> opaque_atom(). + +atom(Atom) -> + Atom. diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index f5275e66b5..d0a01351f3 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -171,18 +171,33 @@ start_link(T) -> info({gen_sctp, Sock}) -> lists:flatmap(fun(K) -> info(K, Sock) end, - [{socket, sockname}, - {peer, peername}, + [{socket, socknames}, + {peer, peernames}, {statistics, getstat}]). info({K,F}, Sock) -> case inet:F(Sock) of {ok, V} -> - [{K,V}]; + [{K, map(F,V)}]; _ -> [] end. +%% inet:{sock,peer}names/1 returns [{Addr, Port}] but the port number +%% should be the same in each tuple. Map to a {[Addr], Port} tuple if +%% so. +map(K, [{_, Port} | _] = APs) + when K == socknames; + K == peernames -> + try [A || {A,P} <- APs, P == Port orelse throw(?MODULE)] of + As -> {As, Port} + catch + ?MODULE -> APs + end; + +map(_, V) -> + V. + %% --------------------------------------------------------------------------- %% # init/1 %% --------------------------------------------------------------------------- @@ -549,7 +564,7 @@ accept_peer(_, []) -> ok; accept_peer(Sock, Matches) -> - {RAddrs, _} = ok(inet:peername(Sock)), + RAddrs = [A || {A,_} <- ok(inet:peernames(Sock))], diameter_peer:match(RAddrs, Matches) orelse x({accept, RAddrs, Matches}), ok. diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl index 74702102f0..264a533a52 100644 --- a/lib/edoc/src/edoc_tags.erl +++ b/lib/edoc/src/edoc_tags.erl @@ -454,7 +454,7 @@ check_type(#tag{line = L, data = Data}, P0, Ls, Ts) -> check_type(#t_def{type = Type}, P, Ls, Ts) -> check_type(Type, P, Ls, Ts); check_type(#t_type{name = Name, args = Args}, P, Ls, Ts) -> - check_used_type(Name, Args, P, Ls), + _ = check_used_type(Name, Args, P, Ls), check_types3(Args++Ts, P, Ls); check_type(#t_var{}, P, Ls, Ts) -> check_types3(Ts, P, Ls); diff --git a/lib/gs/doc/src/gs.xml b/lib/gs/doc/src/gs.xml index e0d7fb20da..7b589d002d 100644 --- a/lib/gs/doc/src/gs.xml +++ b/lib/gs/doc/src/gs.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2000</year> - <year>2013</year> + <year>2014</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -39,7 +39,7 @@ graphical user interface. </p> <p> - GS is deprecated and will be removed in the R16 release. + GS is deprecated and will be removed in the 18.0 release. </p> </warning> <p>The Graphics System, GS, is easy to learn and diff --git a/lib/jinterface/doc/src/jinterface_users_guide.xml b/lib/jinterface/doc/src/jinterface_users_guide.xml index aaf84e2e3d..5dfe5c0c6d 100644 --- a/lib/jinterface/doc/src/jinterface_users_guide.xml +++ b/lib/jinterface/doc/src/jinterface_users_guide.xml @@ -112,6 +112,10 @@ <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangTuple">OtpErlangTuple</seealso></cell> </row> <row> + <cell align="left" valign="middle">map</cell> + <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangMap">OtpErlangMap</seealso></cell> + </row> + <row> <cell align="left" valign="middle">term</cell> <cell align="left" valign="middle"><seealso marker="java/com/ericsson/otp/erlang/OtpErlangObject">OtpErlangObject</seealso></cell> </row> diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangMap.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangMap.java new file mode 100644 index 0000000000..7c1cf84e98 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangMap.java @@ -0,0 +1,293 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2013. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang maps. Maps are created from one or + * more arbitrary Erlang terms. + * + * <p> + * The arity of the map is the number of elements it contains. The keys and + * values can be retrieved as arrays and the value for a key can be queried. + * + */ +public class OtpErlangMap extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + private static final long serialVersionUID = -6410770117696198497L; + + private static final OtpErlangObject[] NO_ELEMENTS = new OtpErlangObject[0]; + + private OtpErlangObject[] keys = NO_ELEMENTS; + private OtpErlangObject[] values = NO_ELEMENTS; + + /** + * Create a map from an array of keys and an array of values. + * + * @param keys + * the array of terms to create the map keys from. + * @param values + * the array of terms to create the map values from. + * + * @exception java.lang.IllegalArgumentException + * if any array is empty (null) or contains null elements. + */ + public OtpErlangMap(final OtpErlangObject[] keys, + final OtpErlangObject[] values) { + this(keys, 0, keys.length, values, 0, values.length); + } + + /** + * Create a map from an array of terms. + * + * @param elems + * the array of terms to create the map from. + * @param start + * the offset of the first term to insert. + * @param vcount + * the number of terms to insert. + * + * @exception java.lang.IllegalArgumentException + * if any array is empty (null) or contains null elements. + */ + public OtpErlangMap(final OtpErlangObject[] keys, final int kstart, + final int kcount, final OtpErlangObject[] values, final int vstart, + final int vcount) { + if (keys == null || values == null) { + throw new java.lang.IllegalArgumentException( + "Map content can't be null"); + } else if (kcount != vcount) { + throw new java.lang.IllegalArgumentException( + "Map keys and values must have same arity"); + } else if (vcount < 1) { + this.keys = NO_ELEMENTS; + this.values = NO_ELEMENTS; + } else { + this.keys = new OtpErlangObject[vcount]; + for (int i = 0; i < vcount; i++) { + if (keys[kstart + i] != null) { + this.keys[i] = keys[kstart + i]; + } else { + throw new java.lang.IllegalArgumentException( + "Map key cannot be null (element" + (kstart + i) + + ")"); + } + } + this.values = new OtpErlangObject[vcount]; + for (int i = 0; i < vcount; i++) { + if (values[vstart + i] != null) { + this.values[i] = values[vstart + i]; + } else { + throw new java.lang.IllegalArgumentException( + "Map value cannot be null (element" + (vstart + i) + + ")"); + } + } + } + } + + /** + * Create a map from a stream containing a map encoded in Erlang external + * format. + * + * @param buf + * the stream containing the encoded map. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang map. + */ + public OtpErlangMap(final OtpInputStream buf) + throws OtpErlangDecodeException { + final int arity = buf.read_map_head(); + + if (arity > 0) { + keys = new OtpErlangObject[arity]; + values = new OtpErlangObject[arity]; + + for (int i = 0; i < arity; i++) { + keys[i] = buf.read_any(); + } + for (int i = 0; i < arity; i++) { + values[i] = buf.read_any(); + } + } else { + keys = NO_ELEMENTS; + values = NO_ELEMENTS; + } + } + + /** + * Get the arity of the map. + * + * @return the number of elements contained in the map. + */ + public int arity() { + return keys.length; + } + + /** + * Get the specified value from the map. + * + * @param key + * the key of the requested value. + * + * @return the requested value, of null if key is not a valid key. + */ + public OtpErlangObject get(final OtpErlangObject key) { + if (key == null) { + return null; + } + for (int i = 0; i < keys.length; i++) { + if (key.equals(keys[i])) { + return values[i]; + } + } + return null; + } + + /** + * Get all the keys from the map as an array. + * + * @return an array containing all of the map's keys. + */ + public OtpErlangObject[] keys() { + final OtpErlangObject[] res = new OtpErlangObject[arity()]; + System.arraycopy(keys, 0, res, 0, res.length); + return res; + } + + /** + * Get all the values from the map as an array. + * + * @return an array containing all of the map's values. + */ + public OtpErlangObject[] values() { + final OtpErlangObject[] res = new OtpErlangObject[arity()]; + System.arraycopy(values, 0, res, 0, res.length); + return res; + } + + /** + * Get the string representation of the map. + * + * @return the string representation of the map. + */ + @Override + public String toString() { + int i; + final StringBuffer s = new StringBuffer(); + final int arity = values.length; + + s.append("#{"); + + for (i = 0; i < arity; i++) { + if (i > 0) { + s.append(","); + } + s.append(keys[i].toString()); + s.append(" => "); + s.append(values[i].toString()); + } + + s.append("}"); + + return s.toString(); + } + + /** + * Convert this map to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded map should be written. + */ + @Override + public void encode(final OtpOutputStream buf) { + final int arity = values.length; + + buf.write_map_head(arity); + + for (int i = 0; i < arity; i++) { + buf.write_any(keys[i]); + } + for (int i = 0; i < arity; i++) { + buf.write_any(values[i]); + } + } + + /** + * Determine if two maps are equal. Maps are equal if they have the same + * arity and all of the elements are equal. + * + * @param o + * the map to compare to. + * + * @return true if the maps have the same arity and all the elements are + * equal. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangMap)) { + return false; + } + + final OtpErlangMap t = (OtpErlangMap) o; + final int a = arity(); + + if (a != t.arity()) { + return false; + } + + for (int i = 0; i < a; i++) { + if (!keys[i].equals(t.keys[i])) { + return false; // early exit + } + } + for (int i = 0; i < a; i++) { + if (!values[i].equals(t.values[i])) { + return false; // early exit + } + } + + return true; + } + + @Override + protected int doHashCode() { + final OtpErlangObject.Hash hash = new OtpErlangObject.Hash(9); + final int a = arity(); + hash.combine(a); + for (int i = 0; i < a; i++) { + hash.combine(keys[i].hashCode()); + } + for (int i = 0; i < a; i++) { + hash.combine(values[i].hashCode()); + } + return hash.valueOf(); + } + + @Override + public Object clone() { + final OtpErlangMap newMap = (OtpErlangMap) super.clone(); + newMap.values = values.clone(); + return newMap; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpExternal.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpExternal.java index 45a82d6c94..fa0fe18e95 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpExternal.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpExternal.java @@ -85,6 +85,9 @@ public class OtpExternal { /** The tag used for new style references */ public static final int newRefTag = 114; + /** The tag used for maps */ + public static final int mapTag = 116; + /** The tag used for old Funs */ public static final int funTag = 117; diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java index 9dc1728346..0d1342d796 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java @@ -1202,6 +1202,9 @@ public class OtpInputStream extends ByteArrayInputStream { case OtpExternal.newRefTag: return new OtpErlangRef(this); + case OtpExternal.mapTag: + return new OtpErlangMap(this); + case OtpExternal.portTag: return new OtpErlangPort(this); @@ -1244,4 +1247,21 @@ public class OtpInputStream extends ByteArrayInputStream { throw new OtpErlangDecodeException("Uknown data type: " + tag); } } + + public int read_map_head() throws OtpErlangDecodeException { + int arity = 0; + final int tag = read1skip_version(); + + // decode the map header and get arity + switch (tag) { + case OtpExternal.mapTag: + arity = read4BE(); + break; + + default: + throw new OtpErlangDecodeException("Not valid map tag: " + tag); + } + + return arity; + } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java index 78f47aa32f..a78423db44 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java @@ -974,4 +974,9 @@ public class OtpOutputStream extends ByteArrayOutputStream { write_atom(function); write_long(arity); } + + public void write_map_head(final int arity) { + write1(OtpExternal.mapTag); + write4BE(arity); + } } diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files b/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files index 1390542194..62fa7f990e 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files @@ -74,6 +74,7 @@ ERL = \ OtpErlangShort\ OtpErlangString\ OtpErlangTuple \ + OtpErlangMap \ OtpErlangUInt \ OtpErlangUShort diff --git a/lib/jinterface/test/jinterface_SUITE.erl b/lib/jinterface/test/jinterface_SUITE.erl index de8d611efc..cb725164cd 100644 --- a/lib/jinterface/test/jinterface_SUITE.erl +++ b/lib/jinterface/test/jinterface_SUITE.erl @@ -37,7 +37,9 @@ erl_exit_with_reason_any_term/1, java_exit_with_reason_any_term/1, status_handler_localStatus/1, status_handler_remoteStatus/1, - status_handler_connAttempt/1]). + status_handler_connAttempt/1, + maps/1 + ]). -include_lib("common_test/include/ct.hrl"). -include("test_server_line.hrl"). @@ -103,7 +105,8 @@ fundamental() -> nodename, % Nodename.java register_and_whereis, % RegisterAndWhereis.java get_names, % GetNames.java - boolean_atom % BooleanAtom.java + boolean_atom, % BooleanAtom.java + maps % Maps.java ]. ping() -> @@ -675,6 +678,17 @@ status_handler_connAttempt(Config) when is_list(Config) -> "NodeStatusHandler", [erlang:get_cookie(),node(),?status_handler_connAttempt]). +%%%----------------------------------------------------------------- +maps(doc) -> + ["Maps.java: " + "Tests OtpErlangMap encoding, decoding, toString, get"]; +maps(suite) -> + []; +maps(Config) when is_list(Config) -> + ok = jitu:java(?config(java, Config), + ?config(data_dir, Config), + "Maps", + []). %%%----------------------------------------------------------------- %%% INTERNAL FUNCTIONS diff --git a/lib/jinterface/test/jinterface_SUITE_data/Makefile.src b/lib/jinterface/test/jinterface_SUITE_data/Makefile.src index 2a3dca463b..a15ed1aa63 100644 --- a/lib/jinterface/test/jinterface_SUITE_data/Makefile.src +++ b/lib/jinterface/test/jinterface_SUITE_data/Makefile.src @@ -46,7 +46,8 @@ JAVA_FILES = \ MboxPing.java \ MboxSendReceive.java \ MboxLinkUnlink.java \ - NodeStatusHandler.java + NodeStatusHandler.java \ + Maps.java CLASS_FILES = $(JAVA_FILES:.java=.class) diff --git a/lib/jinterface/test/jinterface_SUITE_data/Maps.java b/lib/jinterface/test/jinterface_SUITE_data/Maps.java new file mode 100644 index 0000000000..136a665f23 --- /dev/null +++ b/lib/jinterface/test/jinterface_SUITE_data/Maps.java @@ -0,0 +1,108 @@ +import java.util.Arrays; + +import com.ericsson.otp.erlang.OtpErlangAtom; +import com.ericsson.otp.erlang.OtpErlangDecodeException; +import com.ericsson.otp.erlang.OtpErlangList; +import com.ericsson.otp.erlang.OtpErlangLong; +import com.ericsson.otp.erlang.OtpErlangMap; +import com.ericsson.otp.erlang.OtpInputStream; +import com.ericsson.otp.erlang.OtpOutputStream; + +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2004-2010. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +class Maps { + + /* + * Implements test case jinterface_SUITE:maps/1 + * + * Test the class OtpErlangMap + */ + + @SuppressWarnings("resource") + public static void main(final String argv[]) { + + runTest(new byte[] { (byte) 131, 116, 0, 0, 0, 0 }, "#{}", 1); + runTest(new byte[] { (byte) 131, 116, 0, 0, 0, 1, 100, 0, 1, 97, 100, + 0, 1, 98 }, "#{a => b}", 2); + // make sure keys are sorted here, jinterface doesn't reorder them + runTest(new byte[] { (byte) 131, 116, 0, 0, 0, 2, 97, 2, 100, 0, 1, 97, + 106, 97, 1 }, "#{2 => [],a => 1}", 3); + runTest(new byte[] { (byte) 131, 116, 0, 0, 0, 1, 104, 1, 97, 3, 108, + 0, 0, 0, 1, 100, 0, 1, 114, 106 }, "#{{3} => [r]}", 4); + + try { + // #{2 => [],a => 1} + final OtpErlangMap map = new OtpErlangMap(new OtpInputStream( + new byte[] { (byte) 131, 116, 0, 0, 0, 2, 97, 2, 100, 0, 1, + 97, 106, 97, 1 })); + + if (map.arity() != 2) { + fail(5); + } + if (!new OtpErlangLong(1).equals(map.get(new OtpErlangAtom("a")))) { + fail(6); + } + if (!new OtpErlangList().equals(map.get(new OtpErlangLong(2)))) { + fail(7); + } + if (map.get(new OtpErlangLong(1)) != null) { + fail(8); + } + } catch (final OtpErlangDecodeException e) { + fail(99); + } + + } + + @SuppressWarnings("resource") + private static void runTest(final byte[] in, final String out, final int err) { + try { + final OtpInputStream is = new OtpInputStream(in); + + final OtpErlangMap map = new OtpErlangMap(is); + final String output = map.toString(); + if (!output.equals(out)) { + fail("toString mismatch " + output + " <> " + out, err); + } + + final OtpOutputStream os = new OtpOutputStream(map); + final byte[] outArray0 = os.toByteArray(); + final byte[] outArray = new byte[outArray0.length + 1]; + System.arraycopy(outArray0, 0, outArray, 1, outArray0.length); + outArray[0] = (byte) 131; + if (!Arrays.equals(in, outArray)) { + fail("encode error " + Arrays.toString(outArray), err); + } + } catch (final OtpErlangDecodeException e) { + fail("decode error " + e.getMessage(), err); + } catch (final Exception e) { + fail("error " + e.getMessage(), err); + } + } + + private static void fail(final int reason) { + System.exit(reason); + } + + private static void fail(final String str, final int reason) { + System.out.println(str); + System.exit(reason); + } +} diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml index 29eaf348a9..016151891c 100644 --- a/lib/kernel/doc/src/application.xml +++ b/lib/kernel/doc/src/application.xml @@ -239,10 +239,19 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> <desc> <p>Sets the value of the configuration parameter <c><anno>Par</anno></c> for <c><anno>Application</anno></c>.</p> - <p><c>set_env/3</c> uses the standard <c>gen_server</c> timeout - value (5000 ms). A <c><anno>Timeout</anno></c> argument can be provided + <p><c>set_env/4</c> uses the standard <c>gen_server</c> timeout + value (5000 ms). The <c>timeout</c> option can be provided if another timeout value is useful, for example, in situations where the application controller is heavily loaded.</p> + <p>If <c>set_env/4</c> is called before the application is loaded, + the application environment values specified in the <c>Application.app</c> + file will override the ones previously set. This is also true for application + reloads.</p> + <p>The <c>persistent</c> option can be set to <c>true</c> + when there is a need to guarantee parameters set with <c>set_env/4</c> + will not be overridden by the ones defined in the application resource + file on load. This means persistent values will stick after the application + is loaded and also on application reload.</p> <warning> <p>Use this function only if you know what you are doing, that is, on your own applications. It is very application @@ -406,9 +415,11 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> <p>Removes the configuration parameter <c><anno>Par</anno></c> and its value for <c><anno>Application</anno></c>.</p> <p><c>unset_env/2</c> uses the standard <c>gen_server</c> - timeout value (5000 ms). A <c><anno>Timeout</anno></c> argument can be + timeout value (5000 ms). The <c>timeout</c> option can be provided if another timeout value is useful, for example, in situations where the application controller is heavily loaded.</p> + <p><c>unset_env/3</c> also allows the persistent option to be passed + (see <c>set_env/4</c> above).</p> <warning> <p>Use this function only if you know what you are doing, that is, on your own applications. It is very application diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index 7b8d2940c1..b3ec9fd33d 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2013</year> + <year>1996</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -1751,7 +1751,7 @@ using async threads potentially makes your system volnerable to slow client attacks. If set to true and no async threads are available, the sendfile call will return <c>{error,einval}</c>. - Introduced in 17.0. Default is false.</item> + Introduced in Erlang/OTP 17.0. Default is false.</item> </taglist> </p> </desc> diff --git a/lib/kernel/src/application.erl b/lib/kernel/src/application.erl index 4e8ba1b78a..76a80553b0 100644 --- a/lib/kernel/src/application.erl +++ b/lib/kernel/src/application.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -132,7 +132,7 @@ ensure_all_started(Application, Type) -> {ok, Started} -> {ok, lists:reverse(Started)}; {error, Reason, Started} -> - [stop(App) || App <- Started], + _ = [stop(App) || App <- Started], {error, Reason} end. @@ -285,16 +285,18 @@ info() -> set_env(Application, Key, Val) -> application_controller:set_env(Application, Key, Val). --spec set_env(Application, Par, Val, Timeout) -> 'ok' when +-spec set_env(Application, Par, Val, Opts) -> 'ok' when Application :: atom(), Par :: atom(), Val :: term(), - Timeout :: timeout(). + Opts :: [{timeout, timeout()} | {persistent, boolean()}]. set_env(Application, Key, Val, infinity) -> - application_controller:set_env(Application, Key, Val, infinity); + set_env(Application, Key, Val, [{timeout, infinity}]); set_env(Application, Key, Val, Timeout) when is_integer(Timeout), Timeout>=0 -> - application_controller:set_env(Application, Key, Val, Timeout). + set_env(Application, Key, Val, [{timeout, Timeout}]); +set_env(Application, Key, Val, Opts) when is_list(Opts) -> + application_controller:set_env(Application, Key, Val, Opts). -spec unset_env(Application, Par) -> 'ok' when Application :: atom(), @@ -303,15 +305,17 @@ set_env(Application, Key, Val, Timeout) when is_integer(Timeout), Timeout>=0 -> unset_env(Application, Key) -> application_controller:unset_env(Application, Key). --spec unset_env(Application, Par, Timeout) -> 'ok' when +-spec unset_env(Application, Par, Opts) -> 'ok' when Application :: atom(), Par :: atom(), - Timeout :: timeout(). + Opts :: [{timeout, timeout()} | {persistent, boolean()}]. unset_env(Application, Key, infinity) -> - application_controller:unset_env(Application, Key, infinity); + unset_env(Application, Key, [{timeout, infinity}]); unset_env(Application, Key, Timeout) when is_integer(Timeout), Timeout>=0 -> - application_controller:unset_env(Application, Key, Timeout). + unset_env(Application, Key, [{timeout, Timeout}]); +unset_env(Application, Key, Opts) when is_list(Opts) -> + application_controller:unset_env(Application, Key, Opts). -spec get_env(Par) -> 'undefined' | {'ok', Val} when Par :: atom(), diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl index 1a4473593a..ed13035104 100644 --- a/lib/kernel/src/application_controller.erl +++ b/lib/kernel/src/application_controller.erl @@ -461,14 +461,16 @@ permit_application(ApplName, Flag) -> set_env(AppName, Key, Val) -> - gen_server:call(?AC, {set_env, AppName, Key, Val}). -set_env(AppName, Key, Val, Timeout) -> - gen_server:call(?AC, {set_env, AppName, Key, Val}, Timeout). + gen_server:call(?AC, {set_env, AppName, Key, Val, []}). +set_env(AppName, Key, Val, Opts) -> + Timeout = proplists:get_value(timeout, Opts, 5000), + gen_server:call(?AC, {set_env, AppName, Key, Val, Opts}, Timeout). unset_env(AppName, Key) -> - gen_server:call(?AC, {unset_env, AppName, Key}). -unset_env(AppName, Key, Timeout) -> - gen_server:call(?AC, {unset_env, AppName, Key}, Timeout). + gen_server:call(?AC, {unset_env, AppName, Key, []}). +unset_env(AppName, Key, Opts) -> + Timeout = proplists:get_value(timeout, Opts, 5000), + gen_server:call(?AC, {unset_env, AppName, Key, Opts}, Timeout). %%%----------------------------------------------------------------- %%% call-back functions from gen_server @@ -609,8 +611,8 @@ check_para([Else | _ParaList], AppName) -> | {'change_application_data', _, _} | {'permit_application', atom() | {'application',atom(),_},_} | {'start_application', _, _} - | {'unset_env', _, _} - | {'set_env', _, _, _}. + | {'unset_env', _, _, _} + | {'set_env', _, _, _, _}. -spec handle_call(calls(), {pid(), term()}, state()) -> {'noreply', state()} | {'reply', term(), state()}. @@ -858,13 +860,25 @@ handle_call(which_applications, _From, S) -> end, S#state.running), {reply, Reply, S}; -handle_call({set_env, AppName, Key, Val}, _From, S) -> +handle_call({set_env, AppName, Key, Val, Opts}, _From, S) -> ets:insert(ac_tab, {{env, AppName, Key}, Val}), - {reply, ok, S}; + case proplists:get_value(persistent, Opts, false) of + true -> + Fun = fun(Env) -> lists:keystore(Key, 1, Env, {Key, Val}) end, + {reply, ok, S#state{conf_data = change_app_env(S#state.conf_data, AppName, Fun)}}; + false -> + {reply, ok, S} + end; -handle_call({unset_env, AppName, Key}, _From, S) -> +handle_call({unset_env, AppName, Key, Opts}, _From, S) -> ets:delete(ac_tab, {env, AppName, Key}), - {reply, ok, S}; + case proplists:get_value(persistent, Opts, false) of + true -> + Fun = fun(Env) -> lists:keydelete(Key, 1, Env) end, + {reply, ok, S#state{conf_data = change_app_env(S#state.conf_data, AppName, Fun)}}; + false -> + {reply, ok, S} + end; handle_call({control_application, AppName}, {Pid, _Tag}, S) -> Control = S#state.control, @@ -1640,6 +1654,16 @@ merge_env([{App, AppEnv1} | T], Env2, Res) -> merge_env([], Env2, Res) -> Env2 ++ Res. +%% Changes the environment for the given application +%% If there is no application, an empty one is created +change_app_env(Env, App, Fun) -> + case get_env_key(App, Env) of + {value, AppEnv, RestEnv} -> + [{App, Fun(AppEnv)} | RestEnv]; + _ -> + [{App, Fun([])} | Env] + end. + %% Merges envs for an application. Env2 overrides Env1 merge_app_env(Env1, Env2) -> merge_app_env(Env1, Env2, []). diff --git a/lib/kernel/src/application_master.erl b/lib/kernel/src/application_master.erl index 68f78c6eb8..bc15b5a7de 100644 --- a/lib/kernel/src/application_master.erl +++ b/lib/kernel/src/application_master.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -27,7 +27,7 @@ -include("application_master.hrl"). --record(state, {child, appl_data, children = [], procs = 0, gleader}). +-record(state, {child, appl_data, children = [], procs = 0, gleader, req=[]}). %%----------------------------------------------------------------- %% Func: start_link/1 @@ -205,22 +205,25 @@ terminate_loop(Child, State) -> %%----------------------------------------------------------------- %% The Application Master is linked to *all* processes in the group -%% (application). +%% (application). %%----------------------------------------------------------------- handle_msg({get_child, Tag, From}, State) -> - From ! {Tag, get_child_i(State#state.child)}, - State; + get_child_i(State, Tag, From); handle_msg({stop, Tag, From}, State) -> catch terminate(normal, State), From ! {Tag, ok}, exit(normal); +handle_msg({child, Ref, GrandChild, Mod}, #state{req=Reqs0}=State) -> + {value, {_, Tag, From}, Reqs} = lists:keytake(Ref, 1, Reqs0), + From ! {Tag, {GrandChild, Mod}}, + State#state{req=Reqs}; handle_msg(_, State) -> State. - -terminate(Reason, State) -> - terminate_child(State#state.child, State), - kill_children(State#state.children), +terminate(Reason, State = #state{child=Child, children=Children, req=Reqs}) -> + _ = [From ! {Tag, error} || {_, Tag, From} <- Reqs], + terminate_child(Child, State), + kill_children(Children), exit(Reason). @@ -342,8 +345,8 @@ start_supervisor(Type, M, A) -> loop_it(Parent, Child, Mod, AppState) -> receive - {Parent, get_child} -> - Parent ! {self(), Child, Mod}, + {Parent, get_child, Ref} -> + Parent ! {child, Ref, Child, Mod}, loop_it(Parent, Child, Mod, AppState); {Parent, terminate} -> NewAppState = prep_stop(Mod, AppState), @@ -382,10 +385,15 @@ prep_stop(Mod, AppState) -> NewAppState end. -get_child_i(Child) -> - Child ! {self(), get_child}, - receive - {Child, GrandChild, Mod} -> {GrandChild, Mod} +get_child_i(#state{child=Child, req=Reqs}=State, Tag, From) -> + Ref = erlang:make_ref(), + case erlang:is_process_alive(Child) of + true -> + Child ! {self(), get_child, Ref}, + State#state{req=[{Ref, Tag, From}|Reqs]}; + false -> + From ! {Tag, error}, + State end. terminate_child_i(Child, State) -> diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl index 9ec8a15861..ccb3760309 100644 --- a/lib/kernel/test/application_SUITE.erl +++ b/lib/kernel/test/application_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -33,10 +33,10 @@ permit_false_start_local/1, permit_false_start_dist/1, script_start/1, nodedown_start/1, init2973/0, loop2973/0, loop5606/1]). --export([config_change/1, +-export([config_change/1, persistent_env/1, distr_changed_tc1/1, distr_changed_tc2/1, ensure_started/1, ensure_all_started/1, - shutdown_func/1, do_shutdown/1, shutdown_timeout/1]). + shutdown_func/1, do_shutdown/1, shutdown_timeout/1, shutdown_deadlock/1]). -define(TESTCASE, testcase_name). -define(testcase, ?config(?TESTCASE, Config)). @@ -53,7 +53,9 @@ all() -> load_use_cache, ensure_started, {group, reported_bugs}, start_phases, script_start, nodedown_start, permit_false_start_local, permit_false_start_dist, get_key, get_env, ensure_all_started, - {group, distr_changed}, config_change, shutdown_func, shutdown_timeout]. + {group, distr_changed}, config_change, shutdown_func, shutdown_timeout, + shutdown_deadlock, + persistent_env]. groups() -> [{reported_bugs, [], @@ -960,7 +962,7 @@ nodedown_start(Conf) when is_list(Conf) -> ensure_started(suite) -> []; ensure_started(doc) -> ["Test application:ensure_started/1."]; -ensure_started(Conf) -> +ensure_started(_Conf) -> {ok, Fd} = file:open("app1.app", [write]), w_app1(Fd), @@ -980,7 +982,7 @@ ensure_started(Conf) -> ensure_all_started(suite) -> []; ensure_all_started(doc) -> ["Test application:ensure_all_started/1-2."]; -ensure_all_started(Conf) -> +ensure_all_started(_Conf) -> {ok, Fd1} = file:open("app1.app", [write]), w_app1(Fd1), @@ -1987,6 +1989,50 @@ get_appls([_ | T], Res) -> get_appls([], Res) -> Res. +persistent_env(suite) -> + []; +persistent_env(doc) -> + ["Test set_env/4 and unset_env/3 with persistent true"]; +persistent_env(Conf) when is_list(Conf) -> + ok = application:set_env(appinc, own2, persist, [{persistent, true}]), + ok = application:set_env(appinc, key1, persist, [{persistent, true}]), + + %% own_env1 and own2 are set in appinc + ok = application:load(appinc()), + {ok, value1} = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, key1), + + %% Changing the environment after loaded reflects and should persist + ok = application:set_env(appinc, own_env1, persist, [{persistent, true}]), + {ok, persist} = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, key1), + + %% On reload, own_env1, own2 and key1 should all persist + ok = application:unload(appinc), + ok = application:load(appinc()), + {ok, persist} = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, key1), + + %% Unset own_env1 and key1, own2 should still persist + ok = application:unset_env(appinc, own_env1, [{persistent, true}]), + ok = application:unset_env(appinc, key1, [{persistent, true}]), + undefined = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + undefined = application:get_env(appinc, key1), + + %% own_env1 should be back to its application value on reload + ok = application:unload(appinc), + ok = application:load(appinc()), + {ok, value1} = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + undefined = application:get_env(appinc, key1), + + %% Clean up + ok = application:unload(appinc). + %%%----------------------------------------------------------------- %%% Tests the 'shutdown_func' kernel config parameter %%%----------------------------------------------------------------- @@ -2051,7 +2097,31 @@ shutdown_timeout(Config) when is_list(Config) -> end, ok. +%%%----------------------------------------------------------------- +%%% Provokes a (previous) application shutdown deadlock +%%%----------------------------------------------------------------- +shutdown_deadlock(Config) when is_list(Config) -> + DataDir = ?config(data_dir,Config), + code:add_path(filename:join([DataDir,deadlock])), + %% ok = rpc:call(Cp1, application, start, [sasl]), + ok = application:start(deadlock), + Tester = self(), + application:set_env(deadlock, fail_stop, Tester), + spawn(fun() -> Tester ! {stop, application:stop(deadlock)} end), + receive + {deadlock, Server} -> + spawn(fun() -> + Master = application_controller:get_master(deadlock), + Child = application_master:get_child(Master), + Tester ! {child, Child} + end), + timer:sleep(100), + erlang:display({self(), "Sending Continue", Server}), + Server ! continue + end, + [_|_] = application:which_applications(), + ok. %%----------------------------------------------------------------- diff --git a/lib/kernel/test/application_SUITE_data/deadlock/deadlock.app b/lib/kernel/test/application_SUITE_data/deadlock/deadlock.app index 0c1001bed6..233c7a3f76 100644 --- a/lib/kernel/test/application_SUITE_data/deadlock/deadlock.app +++ b/lib/kernel/test/application_SUITE_data/deadlock/deadlock.app @@ -4,5 +4,5 @@ {applications, [kernel, stdlib, sasl]}, {modules, [deadlock]}, {mod, {deadlock, []}}, - {env, [{fail_start, false}]} + {env, [{fail_start, false}, {fail_stop, false}]} ]}. diff --git a/lib/kernel/test/application_SUITE_data/deadlock/deadlock.erl b/lib/kernel/test/application_SUITE_data/deadlock/deadlock.erl index 5f68bf9078..3ef6105371 100644 --- a/lib/kernel/test/application_SUITE_data/deadlock/deadlock.erl +++ b/lib/kernel/test/application_SUITE_data/deadlock/deadlock.erl @@ -21,7 +21,7 @@ init([sup]) -> {ok, {{one_for_one, 5, 10}, [ { sasl_syslog_dm, {?MODULE, start_link, []}, - permanent, brutal_kill, worker, + permanent, 25000, worker, [deadlock] } ]}}; @@ -32,6 +32,8 @@ init([sup]) -> init([child]) -> case application:get_env(deadlock, fail_start) of {ok, false} -> + process_flag(trap_exit, true), + io:format("~p: Traps exit~n",[?MODULE]), %% we must not fail on the first init, otherwise supervisor %% terminates immediately {ok, []}; @@ -50,6 +52,14 @@ handle_info(_Msg, State) -> {noreply, State}. terminate(_Reason, _State) -> + case application:get_env(deadlock, fail_stop) of + {ok, false} -> ok; + {ok, Tester} -> + Tester ! {deadlock, self()}, + io:format("~p: Waiting in terminate (~p)~n",[?MODULE,Tester]), + receive continue -> ok end + end, + io:format("~p: terminates~n", [?MODULE]), ok. code_change(_OldVsn, State, _Extra) -> diff --git a/lib/mnesia/test/mnesia_frag_test.erl b/lib/mnesia/test/mnesia_frag_test.erl index d3f6762af7..6695fbc880 100644 --- a/lib/mnesia/test/mnesia_frag_test.erl +++ b/lib/mnesia/test/mnesia_frag_test.erl @@ -461,7 +461,7 @@ nice_iter_access(Tab, FragNames, RawRead) -> ExpectedLast = lists:last(Keys), ?match(ExpectedLast, mnesia:last(Tab)), - ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ExpectedAllPrev = ['$end_of_table' | lists:droplast(Keys)], ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], @@ -477,7 +477,7 @@ evil_iter_access(Tab, FragNames, RawRead) -> ExpectedLast = lists:last(Keys), ?match(ExpectedLast, mnesia:last(Tab)), - ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))], + ExpectedAllPrev = ['$end_of_table' | lists:droplast(Keys)], ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)), ExpectedAllNext = tl(Keys) ++ ['$end_of_table'], diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl index 0266047ee3..d5fbceff1e 100644 --- a/lib/observer/src/cdv_bin_cb.erl +++ b/lib/observer/src/cdv_bin_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -21,26 +21,26 @@ detail_pages/0]). %% Callbacks for cdv_detail_wx -get_details({_, {T,Key}}) -> +get_details({Type, {T,Key}}) -> [{Key,Term}] = ets:lookup(T,Key), - {ok,{"Expanded Binary", Term, []}}; + {ok,{"Expanded Binary", {Type, Term}, []}}; get_details({cdv, Id}) -> {ok,Bin} = crashdump_viewer:expand_binary(Id), - {ok,{"Expanded Binary", Bin, []}}. + {ok,{"Expanded Binary", {cvd, Bin}, []}}. detail_pages() -> [{"Binary", fun init_bin_page/2}]. -init_bin_page(Parent,Bin) -> +init_bin_page(Parent,{Type,Bin}) -> cdv_multi_wx:start_link( Parent, - [{"Format \~p",cdv_html_wx,format_bin_fun("~p",Bin)}, - {"Format \~tp",cdv_html_wx,format_bin_fun("~tp",Bin)}, - {"Format \~w",cdv_html_wx,format_bin_fun("~w",Bin)}, - {"Format \~s",cdv_html_wx,format_bin_fun("~s",Bin)}, - {"Format \~ts",cdv_html_wx,format_bin_fun("~ts",Bin)}, - {"Hex",cdv_html_wx,hex_binary_fun(Bin)}, - {"Term",cdv_html_wx,binary_to_term_fun(Bin)}]). + [{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin)}}, + {"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin)}}, + {"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin)}}, + {"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin)}}, + {"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin)}}, + {"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin)}}, + {"Term",cdv_html_wx,{Type,binary_to_term_fun(Bin)}}]). format_bin_fun(Format,Bin) -> fun() -> diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl index fe77a41f59..b79c647f63 100644 --- a/lib/observer/src/cdv_html_wx.erl +++ b/lib/observer/src/cdv_html_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -30,6 +30,7 @@ %% Records -record(state, {panel, + app, %% which tool is the user expand_table, expand_wins=[]}). @@ -38,16 +39,21 @@ start_link(ParentWin, Info) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -init([ParentWin, Fun]) when is_function(Fun) -> - init([ParentWin, Fun()]); +init([ParentWin, {App, Fun}]) when is_function(Fun) -> + init([ParentWin, {App, Fun()}]); init([ParentWin, {expand,HtmlText,Tab}]) -> - HtmlWin = observer_lib:html_window(ParentWin), - wxHtmlWindow:setPage(HtmlWin,HtmlText), - {HtmlWin, #state{panel=HtmlWin,expand_table=Tab}}; + init(ParentWin, HtmlText, Tab, cdv); +init([ParentWin, {App, {expand,HtmlText,Tab}}]) -> + init(ParentWin, HtmlText, Tab, App); +init([ParentWin, {App,HtmlText}]) -> + init(ParentWin, HtmlText, undefined, App); init([ParentWin, HtmlText]) -> + init(ParentWin, HtmlText, undefined, cdv). + +init(ParentWin, HtmlText, Tab, App) -> HtmlWin = observer_lib:html_window(ParentWin), wxHtmlWindow:setPage(HtmlWin,HtmlText), - {HtmlWin, #state{panel=HtmlWin}}. + {HtmlWin, #state{panel=HtmlWin,expand_table=Tab,app=App}}. %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -78,7 +84,7 @@ handle_cast(Msg, State) -> handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, linkInfo=#wxHtmlLinkInfo{href=Target}}}, - #state{expand_table=Tab}=State) -> + #state{expand_table=Tab, app=App}=State) -> NewState= case Target of "#Binary?" ++ BinSpec -> @@ -102,10 +108,12 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, list_to_integer(Key2), list_to_integer(Key3)}}}, expand(Id,cdv_term_cb,State); + _ when App =:= obs -> + observer ! {open_link, Target}; _ -> cdv_virtual_list_wx:start_detail_win(Target), State - end, + end, {noreply, NewState}; handle_event(Event, State) -> diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl index 6426cc0803..4451045012 100644 --- a/lib/observer/src/cdv_term_cb.erl +++ b/lib/observer/src/cdv_term_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -21,23 +21,23 @@ detail_pages/0]). %% Callbacks for cdv_detail_wx -get_details({_, {T,Key}}) -> +get_details({Type, {T,Key}}) -> [{Key,Term}] = ets:lookup(T,Key), - {ok,{"Expanded Term", [Term, T], []}}. + {ok,{"Expanded Term", {Type,[Term, T]}, []}}. detail_pages() -> [{"Term", fun init_term_page/2}]. -init_term_page(ParentWin, [Term, Tab]) -> +init_term_page(ParentWin, {Type, [Term, Tab]}) -> Expanded = expand(Term, true), BinSaved = expand(Term, Tab), cdv_multi_wx:start_link( ParentWin, - [{"Format \~p",cdv_html_wx,format_term_fun("~p",BinSaved,Tab)}, - {"Format \~tp",cdv_html_wx,format_term_fun("~tp",BinSaved,Tab)}, - {"Format \~w",cdv_html_wx,format_term_fun("~w",BinSaved,Tab)}, - {"Format \~s",cdv_html_wx,format_term_fun("~s",Expanded,Tab)}, - {"Format \~ts",cdv_html_wx,format_term_fun("~ts",Expanded,Tab)}]). + [{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab)}}, + {"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab)}}, + {"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab)}}, + {"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab)}}, + {"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab)}}]). format_term_fun(Format,Term,Tab) -> fun() -> @@ -56,8 +56,9 @@ expand(['#CDVBin',Offset,Size,Pos], true) -> {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}), Bin; expand(Bin, Tab) when is_binary(Bin), not is_boolean(Tab) -> - <<Preview:80, _/binary>> = Bin, Size = byte_size(Bin), + PrevSize = min(Size, 10) * 8, + <<Preview:PrevSize, _/binary>> = Bin, Hash = erlang:phash2(Bin), Key = {Preview, Size, Hash}, ets:insert(Tab, {Key,Bin}), diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl index 9f77891426..c279218707 100644 --- a/lib/observer/src/observer_html_lib.erl +++ b/lib/observer/src/observer_html_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2013. All Rights Reserved. +%% Copyright Ericsson AB 2003-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -146,7 +146,8 @@ all_or_expand(Tab,Term) -> Check = io_lib:format("~P",[Term,100]), Exp = Preview=/=Check, all_or_expand(Tab,Term,Preview,Exp). -all_or_expand(_Tab,_Term,Str,false) -> +all_or_expand(_Tab,Term,Str,false) + when not is_binary(Term) -> href_proc_port(lists:flatten(Str)); all_or_expand(Tab,Term,Preview,true) when not is_binary(Term) -> @@ -158,20 +159,16 @@ all_or_expand(Tab,Term,Preview,true) "&key2="++integer_to_list(Key2)++ "&key3="++integer_to_list(Key3)], "Click to expand above term")]; -all_or_expand(Tab,Bin,PreviewStr,true) when is_binary(Bin) -> - <<Preview:80, _/binary>> = Bin, +all_or_expand(Tab,Bin,_PreviewStr,_Expand) + when is_binary(Bin) -> Size = byte_size(Bin), + PrevSize = min(Size, 10) * 8, + <<Preview:PrevSize, _/binary>> = Bin, Hash = erlang:phash2(Bin), Key = {Preview, Size, Hash}, ets:insert(Tab,{Key,Bin}), - [href_proc_port(lists:flatten(PreviewStr), false), $\n, - href("TARGET=\"expanded\"", - ["#OBSBinary?key1="++integer_to_list(Preview)++ - "&key2="++integer_to_list(Size)++ - "&key3="++integer_to_list(Hash)], - "Click to expand above term")]. - - + Term = io_lib:format("~p", [['#OBSBin',Preview,Size,Hash]]), + href_proc_port(lists:flatten(Term), true). color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). @@ -242,7 +239,7 @@ href(Args,Link,Text) -> ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"]. font(Args,Text) -> ["<FONT ",Args,">\n",Text,"\n</FONT>\n"]. -p(Text) -> +p(Text) -> ["<P>",Text,"</P>\n"]. br() -> "<BR>\n". @@ -336,34 +333,28 @@ href_proc_bin(From, T, Acc, LTB) -> {OffsetSizePos,Rest} = split($],T), BinStr = case string:tokens(OffsetSizePos,",.| \n") of - [Offset,Size,Pos] when From =:= cdv -> + [Offset,SizeStr,Pos] when From =:= cdv -> Id = {list_to_integer(Offset),10,list_to_integer(Pos)}, {ok,PreviewBin} = crashdump_viewer:expand_binary(Id), - PreviewStr = ["<<", - [integer_to_list(X)++"," || <<X:8>> <= PreviewBin], - "...(", - observer_lib:to_str({bytes,Size}), - ")>>"], + PreviewStr = preview_string(list_to_integer(SizeStr), PreviewBin), if LTB -> href("TARGET=\"expanded\"", ["#Binary?offset="++Offset++ - "&size="++Size++ + "&size="++SizeStr++ "&pos="++Pos], PreviewStr); true -> PreviewStr end; - [Preview,Size,Md5] when From =:= obs -> - PreviewBin = <<(list_to_integer(Preview)):80>>, - PreviewStr = ["<<", - [integer_to_list(X)++"," || <<X:8>> <= PreviewBin], - "...(", - observer_lib:to_str({bytes,list_to_integer(Size)}), - ")>>"], + [Preview,SizeStr,Md5] when From =:= obs -> + Size = list_to_integer(SizeStr), + PrevSize = min(Size, 10) * 8, + PreviewStr = preview_string(Size, + <<(list_to_integer(Preview)):PrevSize>>), if LTB -> href("TARGET=\"expanded\"", ["#OBSBinary?key1="++Preview++ - "&key2="++Size++ + "&key2="++SizeStr++ "&key3="++Md5], PreviewStr); true -> @@ -374,6 +365,35 @@ href_proc_bin(From, T, Acc, LTB) -> end, href_proc_port(Rest,[BinStr|Acc],LTB). +preview_string(Size, PreviewBin) when Size > 10 -> + ["<<", + remove_lgt(io_lib:format("~p",[PreviewBin])), + "...(", + observer_lib:to_str({bytes,Size}), + ")", + ">>"]; +preview_string(_, PreviewBin) -> + ["<<", + remove_lgt(io_lib:format("~p",[PreviewBin])), + ">>"]. + +remove_lgt(Deep) -> + remove_lgt_1(lists:flatten(Deep)). + +remove_lgt_1([$<,$<|Rest]) -> + [$>,$>|BinStr] = lists:reverse(Rest), + replace_lgt(lists:reverse(BinStr)). + +replace_lgt([$<|R]) -> + ["<"|replace_lgt(R)]; +replace_lgt([$>|R]) -> + [">"|replace_lgt(R)]; +replace_lgt([L=[_|_]|R]) -> + [replace_lgt(L)|replace_lgt(R)]; +replace_lgt([A|R]) -> + [A|replace_lgt(R)]; +replace_lgt([]) -> []. + split(Char,Str) -> split(Char,Str,[]). split(Char,[Char|Str],Acc) -> % match Char @@ -381,7 +401,6 @@ split(Char,[Char|Str],Acc) -> % match Char split(Char,[H|T],Acc) -> split(Char,T,[H|Acc]). - warn([]) -> []; warn(Warning) -> diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index e0f7bf482b..cedaf7d2b8 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -699,7 +699,9 @@ progress_handler(Caller,Env,Title,Str) -> register(?progress_handler,self()), wx:set_env(Env), PD = progress_dialog(Env,Title,Str), - progress_loop(Title,PD,Caller). + try progress_loop(Title,PD,Caller) + catch closed -> normal end. + progress_loop(Title,PD,Caller) -> receive {progress,{ok,done}} -> % to make wait_for_progress/0 return @@ -738,8 +740,12 @@ progress_dialog(_Env,Title,Str) -> PD. update_progress(PD,Value) -> - wxProgressDialog:update(PD,Value). + try wxProgressDialog:update(PD,Value) + catch _:_ -> throw(closed) %% Port or window have died + end. update_progress_text(PD,Text) -> - wxProgressDialog:update(PD,0,[{newmsg,Text}]). + try wxProgressDialog:update(PD,0,[{newmsg,Text}]) + catch _:_ -> throw(closed) %% Port or window have died + end. finish_progress(PD) -> wxProgressDialog:destroy(PD). diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index 988e04993c..3ffa5fc77d 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -136,9 +136,9 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Href}}}, {noreply, State}; Callback -> [{"key1",Key1},{"key2",Key2},{"key3",Key3}] = httpd:parse_query(Rest), - Id = {observer, {T,{list_to_integer(Key1), - list_to_integer(Key2), - list_to_integer(Key3)}}}, + Id = {obs, {T,{list_to_integer(Key1), + list_to_integer(Key2), + list_to_integer(Key3)}}}, Opened = case lists:keyfind(Id,1,Opened0) of false -> diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 4c385b76aa..ecb8e132fe 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -214,7 +214,7 @@ handle_event(#wx{event = #wxClose{}}, State) -> {stop, normal, State}; handle_event(#wx{id = ?ID_CDV, event = #wxCommand{type = command_menu_selected}}, State) -> - crashdump_viewer:start(), + spawn(crashdump_viewer, start, []), {noreply, State}; handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) -> diff --git a/lib/orber/src/orber_interceptors.erl b/lib/orber/src/orber_interceptors.erl index 407823ea79..62870b35b5 100644 --- a/lib/orber/src/orber_interceptors.erl +++ b/lib/orber/src/orber_interceptors.erl @@ -112,7 +112,7 @@ pop_system_message_interceptor(out) -> [{_, []}] -> ok; [{_, Interceptors}] -> - ets:insert(orber_interceptors, {message_out_interceptors, remove_last_element(Interceptors)}); + ets:insert(orber_interceptors, {message_out_interceptors, lists:droplast(Interceptors)}); _ -> corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO}) end. @@ -151,12 +151,3 @@ apply_message_interceptors([], F, ObjRef, Bytes) -> apply_message_interceptors([M | Rest], F, ObjRef, Bytes) -> apply_message_interceptors(Rest, F, ObjRef, apply(M, F, [ObjRef, Bytes])). - -remove_last_element([]) -> - []; -remove_last_element([M]) -> - []; -remove_last_element([M |Tail]) -> - remove_last_element([Tail]). - - diff --git a/lib/parsetools/src/yecc.erl b/lib/parsetools/src/yecc.erl index b698beb558..f4657663e6 100644 --- a/lib/parsetools/src/yecc.erl +++ b/lib/parsetools/src/yecc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -423,7 +423,7 @@ infile(Parent, Infilex, Options) -> end, case {St#yecc.errors, werror(St)} of {[], false} -> ok; - _ -> _ = file:delete(St#yecc.outfile) + _ -> _ = file:delete(St#yecc.outfile), ok end, Parent ! {self(), yecc_ret(St)}. diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index bd19d0e434..fc3479cb64 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -95,7 +95,7 @@ crypto:rand_bytes(8)} | 'PBES2-params'}</code></p> <p><code>public_key() = rsa_public_key() | dsa_public_key() | ec_public_key()</code></p> - <p><code>private_key() = rsa_public_key() | dsa_public_key() | ec_public_key()</code></p> + <p><code>private_key() = rsa_private_key() | dsa_private_key() | ec_private_key()</code></p> <p><code>rsa_public_key() = #'RSAPublicKey'{}</code></p> <p><code>rsa_private_key() = #'RSAPrivateKey'{}</code></p> diff --git a/lib/public_key/include/public_key.hrl b/lib/public_key/include/public_key.hrl index 8afc841fa6..c6394115e3 100644 --- a/lib/public_key/include/public_key.hrl +++ b/lib/public_key/include/public_key.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -76,7 +76,6 @@ point }). - -define(unspecified, 0). -define(keyCompromise, 1). -define(cACompromise, 2). @@ -88,23 +87,4 @@ -define(privilegeWithdrawn, 9). -define(aACompromise, 10). --type public_key() :: rsa_public_key() | dsa_public_key() | ec_public_key(). --type private_key() :: rsa_private_key() | dsa_private_key() | ec_private_key(). --type rsa_public_key() :: #'RSAPublicKey'{}. --type rsa_private_key() :: #'RSAPrivateKey'{}. --type dsa_private_key() :: #'DSAPrivateKey'{}. --type dsa_public_key() :: {integer(), #'Dss-Parms'{}}. --type ec_public_key() :: {#'ECPoint'{},{namedCurve, Oid::tuple()} | #'ECParameters'{}}. --type ec_private_key() :: #'ECPrivateKey'{}. --type der_encoded() :: binary(). --type decrypt_der() :: binary(). --type pki_asn1_type() :: 'Certificate' | 'RSAPrivateKey' | 'RSAPublicKey' - | 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' - | 'SubjectPublicKeyInfo' | 'CertificationRequest' | 'CertificateList'. --type pem_entry() :: {pki_asn1_type(), binary(), %% DER or Encrypted DER - not_encrypted | {Cipher :: string(), Salt :: binary()}}. --type asn1_type() :: atom(). %% see "OTP-PUB-KEY.hrl --type ssh_file() :: openssh_public_key | rfc4716_public_key | known_hosts | - auth_keys. - -endif. % -ifdef(public_key). diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl index f7a361d5a8..9a8e49f265 100644 --- a/lib/public_key/src/pubkey_cert_records.erl +++ b/lib/public_key/src/pubkey_cert_records.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -99,7 +99,7 @@ transform(Other,_) -> Other. %%-------------------------------------------------------------------- --spec supportedPublicKeyAlgorithms(Oid::tuple()) -> asn1_type(). +-spec supportedPublicKeyAlgorithms(Oid::tuple()) -> public_key:asn1_type(). %% %% Description: Returns the public key type for an algorithm %% identifier tuple as found in SubjectPublicKeyInfo. diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl index c0a0de815b..3a1653d989 100644 --- a/lib/public_key/src/pubkey_pem.erl +++ b/lib/public_key/src/pubkey_pem.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -51,7 +51,7 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec decode(binary()) -> [pem_entry()]. +-spec decode(binary()) -> [public_key:pem_entry()]. %% %% Description: Decodes a PEM binary. %%-------------------------------------------------------------------- @@ -59,7 +59,7 @@ decode(Bin) -> decode_pem_entries(split_bin(Bin), []). %%-------------------------------------------------------------------- --spec encode([pem_entry()]) -> iolist(). +-spec encode([public_key:pem_entry()]) -> iolist(). %% %% Description: Encodes a list of PEM entries. %%-------------------------------------------------------------------- @@ -67,7 +67,7 @@ encode(PemEntries) -> encode_pem_entries(PemEntries). %%-------------------------------------------------------------------- --spec decipher({pki_asn1_type(), DerEncrypted::binary(), +-spec decipher({public_key:pki_asn1_type(), DerEncrypted::binary(), {Cipher :: string(), Salt :: iodata() | #'PBES2-params'{}}}, string()) -> Der::binary(). %% diff --git a/lib/public_key/src/pubkey_ssh.erl b/lib/public_key/src/pubkey_ssh.erl index 41280b9e14..0522ea6ac3 100644 --- a/lib/public_key/src/pubkey_ssh.erl +++ b/lib/public_key/src/pubkey_ssh.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -35,7 +35,8 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec decode(binary(), public_key | ssh_file()) -> [{public_key(), Attributes::list()}]. +-spec decode(binary(), public_key | public_key:ssh_file()) -> + [{public_key:public_key(), Attributes::list()}]. %% %% Description: Decodes a ssh file-binary. %%-------------------------------------------------------------------- @@ -52,7 +53,7 @@ decode(Bin, Type) -> openssh_decode(Bin, Type). %%-------------------------------------------------------------------- --spec encode([{public_key(), Attributes::list()}], ssh_file()) -> +-spec encode([{public_key:public_key(), Attributes::list()}], public_key:ssh_file()) -> binary(). %% %% Description: Encodes a list of ssh file entries. diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index ceecbcc7f2..a732455aa7 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -49,6 +49,27 @@ pkix_crls_validate/3 ]). +-export_type([public_key/0, private_key/0, pem_entry/0, + pki_asn1_type/0, asn1_type/0, ssh_file/0, der_encoded/0]). + +-type public_key() :: rsa_public_key() | dsa_public_key() | ec_public_key(). +-type private_key() :: rsa_private_key() | dsa_private_key() | ec_private_key(). + +-type rsa_public_key() :: #'RSAPublicKey'{}. +-type rsa_private_key() :: #'RSAPrivateKey'{}. +-type dsa_private_key() :: #'DSAPrivateKey'{}. +-type dsa_public_key() :: {integer(), #'Dss-Parms'{}}. +-type ec_public_key() :: {#'ECPoint'{},{namedCurve, Oid::tuple()} | #'ECParameters'{}}. +-type ec_private_key() :: #'ECPrivateKey'{}. +-type der_encoded() :: binary(). +-type pki_asn1_type() :: 'Certificate' | 'RSAPrivateKey' | 'RSAPublicKey' + | 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' + | 'SubjectPublicKeyInfo' | 'CertificationRequest' | 'CertificateList'. +-type pem_entry() :: {pki_asn1_type(), binary(), %% DER or Encrypted DER + not_encrypted | {Cipher :: string(), Salt :: binary()}}. +-type asn1_type() :: atom(). %% see "OTP-PUB-KEY.hrl +-type ssh_file() :: openssh_public_key | rfc4716_public_key | known_hosts | + auth_keys. -type rsa_padding() :: 'rsa_pkcs1_padding' | 'rsa_pkcs1_oaep_padding' | 'rsa_no_padding'. -type public_crypt_options() :: [{rsa_pad, rsa_padding()}]. diff --git a/lib/runtime_tools/src/observer_backend.erl b/lib/runtime_tools/src/observer_backend.erl index 68ef04f20c..fea0854042 100644 --- a/lib/runtime_tools/src/observer_backend.erl +++ b/lib/runtime_tools/src/observer_backend.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2013. All Rights Reserved. +%% Copyright Ericsson AB 2002-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -227,7 +227,9 @@ fetch_stats(Parent, Time) -> fetch_stats_loop(Parent, Time) -> erlang:system_flag(scheduler_wall_time, true), receive - _Msg -> erlang:system_flag(scheduler_wall_time, false) + _Msg -> + %% erlang:system_flag(scheduler_wall_time, false) + ok after Time -> _M = Parent ! {stats, 1, erlang:statistics(scheduler_wall_time), @@ -244,17 +246,6 @@ etop_collect(Collector) -> %% utilization in etop). Next time the flag will be true and then %% there will be a measurement. SchedulerWallTime = erlang:statistics(scheduler_wall_time), - - %% Turn off the flag while collecting data per process etc. - case erlang:system_flag(scheduler_wall_time,false) of - false -> - %% First time and the flag was false - start a monitoring - %% process to set the flag back to false when etop is stopped. - spawn(fun() -> flag_holder_proc(Collector) end); - _ -> - ok - end, - ProcInfo = etop_collect(processes(), []), Collector ! {self(),#etop_info{now = now(), @@ -264,13 +255,22 @@ etop_collect(Collector) -> memi = etop_memi(), procinfo = ProcInfo }}, + + case SchedulerWallTime of + undefined -> + spawn(fun() -> flag_holder_proc(Collector) end); + _ -> + ok + end, + erlang:system_flag(scheduler_wall_time,true). flag_holder_proc(Collector) -> Ref = erlang:monitor(process,Collector), receive {'DOWN',Ref,_,_,_} -> - erlang:system_flag(scheduler_wall_time,false) + %% erlang:system_flag(scheduler_wall_time,false) + ok end. etop_memi() -> diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl index 7e9d7c984a..ad2a8005b9 100644 --- a/lib/sasl/test/release_handler_SUITE.erl +++ b/lib/sasl/test/release_handler_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -1781,37 +1781,46 @@ otp_10463_upgrade_script_regexp(_Config) -> ok. no_dot_erlang(Conf) -> - PrivDir = priv_dir(Conf), + PrivDir = ?config(data_dir,Conf), {ok, OrigWd} = file:get_cwd(), try ok = file:set_cwd(PrivDir), - Erl = "\"" ++ filename:join([code:root_dir(),"bin","erl"]) ++ "\"", - Args = " -noinput -run io put_chars \"TESTOK\" -run erlang halt", + {ok, Wd} = file:get_cwd(), + io:format("Dir ~ts~n", [Wd]), + + Erl0 = filename:join([code:root_dir(),"bin","erl"]), + Erl = filename:nativename(Erl0), + Quote = "\"", + Args = " -noinput -run c pwd -run erlang halt", ok = file:write_file(".erlang", <<"io:put_chars(\"DOT_ERLANG_READ\\n\").\n">>), - case os:cmd(Erl ++ Args) of + CMD1 = Quote ++ Erl ++ Quote ++ Args , + case os:cmd(CMD1) of "DOT_ERLANG_READ" ++ _ -> ok; Other1 -> - io:format("Failed: ~ts~n",[Erl ++ Args]), + io:format("Failed: ~ts~n",[CMD1]), io:format("Expected: ~s ++ _~n",["DOT_ERLANG_READ "]), io:format("Got: ~ts~n",[Other1]), - exit(failed_to_start, test_error) + exit({failed_to_start, test_error}) end, NO_DOT_ERL = " -boot no_dot_erlang", - case os:cmd(Erl ++ NO_DOT_ERL ++ Args) of - "TESTOK" ++ _ -> ok; - Other2 -> - io:format("Failed: ~ts~n",[Erl ++ Args]), + CMD2 = Quote ++ Erl ++ Quote ++ NO_DOT_ERL ++ Args, + case lists:prefix(Wd, Other2 = os:cmd(CMD2)) of + true -> ok; + false -> + io:format("Failed: ~ts~n",[CMD2]), io:format("Expected: ~s~n",["TESTOK"]), io:format("Got: ~ts~n",[Other2]), - exit(failed_to_start, no_dot_erlang) + exit({failed_to_start, no_dot_erlang}) end after _ = file:delete(".erlang"), - ok = file:set_cwd(OrigWd) + ok = file:set_cwd(OrigWd), + ok end. + %%%================================================================= %%% Misceleaneous functions %%%================================================================= diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 5d5f2e5b91..eaf96d0230 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -367,8 +367,11 @@ </func> <func> - <name>stop() -> ok </name> + <name>stop() -> ok | {error, Reason}</name> <fsummary>Stops the SSH application.</fsummary> + <type> + <v>Reason = term()</v> + </type> <desc> <p>Stops the SSH application. See also <seealso marker="kernel:application">application(3)</seealso></p> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 9f571adba2..d50d5a0cb3 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -32,8 +32,8 @@ shell/1, shell/2, shell/3]). %%-------------------------------------------------------------------- --spec start() -> ok. --spec start(permanent | transient | temporary) -> ok. +-spec start() -> ok | {error, term()}. +-spec start(permanent | transient | temporary) -> ok | {error, term()}. %% %% Description: Starts the ssh application. Default type %% is temporary. see application(3) @@ -51,7 +51,7 @@ start(Type) -> application:start(ssh, Type). %%-------------------------------------------------------------------- --spec stop() -> ok. +-spec stop() -> ok | {error, term()}. %% %% Description: Stops the ssh application. %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 3462b98172..070a2db5a8 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -157,7 +157,7 @@ init([Role, Socket, SshOpts]) -> %%-------------------------------------------------------------------- -spec open_channel(pid(), string(), iodata(), integer(), integer(), - timeout()) -> {open, channel_id()} | {open_error, term(), string(), string()}. + timeout()) -> {open, channel_id()} | {error, term()}. %%-------------------------------------------------------------------- open_channel(ConnectionHandler, ChannelType, ChannelSpecificData, InitialWindowSize, diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 21cdedc156..5692138a8a 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -65,7 +65,7 @@ is_auth_key(Key, User,Opts) -> %% Used by client is_host_key(Key, PeerName, Algorithm, Opts) -> - case lookup_host_key(PeerName, Algorithm, Opts) of + case lookup_host_key(Key, PeerName, Algorithm, Opts) of {ok, Key} -> true; _ -> @@ -121,9 +121,9 @@ decode_ssh_file(Pem, Password) -> %% return {ok, Key(s)} or {error, not_found} %% -lookup_host_key(Host, Alg, Opts) -> +lookup_host_key(KeyToMatch, Host, Alg, Opts) -> Host1 = replace_localhost(Host), - do_lookup_host_key(Host1, Alg, Opts). + do_lookup_host_key(KeyToMatch, Host1, Alg, Opts). add_host_key(Host, Key, Opts) -> @@ -204,10 +204,10 @@ replace_localhost("localhost") -> replace_localhost(Host) -> Host. -do_lookup_host_key(Host, Alg, Opts) -> +do_lookup_host_key(KeyToMatch, Host, Alg, Opts) -> case file:open(file_name(user, "known_hosts", Opts), [read, binary]) of {ok, Fd} -> - Res = lookup_host_key_fd(Fd, Host, Alg), + Res = lookup_host_key_fd(Fd, KeyToMatch, Host, Alg), file:close(Fd), {ok, Res}; {error, enoent} -> {error, not_found}; @@ -228,16 +228,16 @@ identity_pass_phrase('ssh-rsa') -> identity_pass_phrase("ssh-rsa") -> rsa_pass_phrase. -lookup_host_key_fd(Fd, Host, KeyType) -> +lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) -> case io:get_line(Fd, '') of eof -> {error, not_found}; Line -> case ssh_decode_line(Line, known_hosts) of [{Key, Attributes}] -> - handle_host(Fd, Host, proplists:get_value(hostnames, Attributes), Key, KeyType); + handle_host(Fd, KeyToMatch, Host, proplists:get_value(hostnames, Attributes), Key, KeyType); [] -> - lookup_host_key_fd(Fd, Host, KeyType) + lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) end end. @@ -248,13 +248,13 @@ ssh_decode_line(Line, Type) -> [] end. -handle_host(Fd, Host, HostList, Key, KeyType) -> +handle_host(Fd, KeyToMatch, Host, HostList, Key, KeyType) -> Host1 = host_name(Host), - case lists:member(Host1, HostList) and key_match(Key, KeyType) of - true -> + case lists:member(Host1, HostList) andalso key_match(Key, KeyType) of + true when KeyToMatch == Key -> Key; - false -> - lookup_host_key_fd(Fd, Host, KeyType) + _ -> + lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) end. host_name(Atom) when is_atom(Atom) -> diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 01a0988718..8d6c77c0ed 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -315,8 +315,8 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?UINT32(Len), Data:Le recipient_channel = Recipient, data = Data }; -decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient), - ?UINT32(DataType), Data/binary>>) -> +decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient), + ?UINT32(DataType), ?UINT32(Len), Data:Len/binary>>) -> #ssh_msg_channel_extended_data{ recipient_channel = Recipient, data_type_code = DataType, @@ -380,27 +380,30 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_BANNER), language = Lang }; +decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary, + ?UINT32(Len1), Inst:Len1/binary, ?UINT32(Len2), Lang:Len2/binary, + ?UINT32(NumPromtps), Data/binary>>) -> + #ssh_msg_userauth_info_request{ + name = Name, + instruction = Inst, + language_tag = Lang, + num_prompts = NumPromtps, + data = Data}; + +%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST: decode(<<?BYTE(?SSH_MSG_USERAUTH_PK_OK), ?UINT32(Len), Alg:Len/binary, KeyBlob/binary>>) -> #ssh_msg_userauth_pk_ok{ algorithm_name = Alg, key_blob = KeyBlob }; +%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST: decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?UINT32(Len0), Prompt:Len0/binary, ?UINT32(Len1), Lang:Len1/binary>>) -> #ssh_msg_userauth_passwd_changereq{ prompt = Prompt, languge = Lang }; -decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary, - ?UINT32(Len1), Inst:Len1/binary, ?UINT32(Len2), Lang:Len2/binary, - ?UINT32(NumPromtps), Data/binary>>) -> - #ssh_msg_userauth_info_request{ - name = Name, - instruction = Inst, - language_tag = Lang, - num_prompts = NumPromtps, - data = Data}; decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) -> #ssh_msg_userauth_info_response{ @@ -424,8 +427,9 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?UINT32(N)>>) -> #ssh_msg_kex_dh_gex_request_old{ n = N }; -decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), ?UINT32(Len0), Prime:Len0/big-signed-integer, - ?UINT32(Len1), Generator:Len1/big-signed-integer>>) -> +decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), + ?UINT32(Len0), Prime:Len0/big-signed-integer-unit:8, + ?UINT32(Len1), Generator:Len1/big-signed-integer-unit:8>>) -> #ssh_msg_kex_dh_gex_group{ p = Prime, g = Generator diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 213b5c714d..52665635f0 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -673,7 +673,7 @@ resolve_symlinks_2(["." | RestPath], State0, LinkCnt, AccPath) -> resolve_symlinks_2([".." | RestPath], State0, LinkCnt, AccPath) -> %% Remove the last path component AccPathComps0 = filename:split(AccPath), - Path = case lists:reverse(tl(lists:reverse(AccPathComps0))) of + Path = case lists:droplast(AccPathComps0) of [] -> ""; AccPathComps -> diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 8c2b84bc1e..c3bdeb1a54 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -626,7 +626,7 @@ handle_options(Opts0, _Role) -> user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined), psk_identity = handle_option(psk_identity, Opts, undefined), srp_identity = handle_option(srp_identity, Opts, undefined), - ciphers = handle_option(ciphers, Opts, []), + ciphers = handle_cipher_option(proplists:get_value(ciphers, Opts, []), hd(Versions)), %% Server side option reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), reuse_sessions = handle_option(reuse_sessions, Opts, true), @@ -712,7 +712,7 @@ validate_option(certfile, undefined = Value) -> validate_option(certfile, Value) when is_binary(Value) -> Value; validate_option(certfile, Value) when is_list(Value) -> - list_to_binary(Value); + binary_filename(Value); validate_option(key, undefined) -> undefined; @@ -729,7 +729,7 @@ validate_option(keyfile, undefined) -> validate_option(keyfile, Value) when is_binary(Value) -> Value; validate_option(keyfile, Value) when is_list(Value), Value =/= "" -> - list_to_binary(Value); + binary_filename(Value); validate_option(password, Value) when is_list(Value) -> Value; @@ -743,7 +743,7 @@ validate_option(cacertfile, undefined) -> validate_option(cacertfile, Value) when is_binary(Value) -> Value; validate_option(cacertfile, Value) when is_list(Value), Value =/= ""-> - list_to_binary(Value); + binary_filename(Value); validate_option(dh, Value) when Value == undefined; is_binary(Value) -> Value; @@ -752,12 +752,12 @@ validate_option(dhfile, undefined = Value) -> validate_option(dhfile, Value) when is_binary(Value) -> Value; validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> - list_to_binary(Value); + binary_filename(Value); validate_option(psk_identity, undefined) -> undefined; validate_option(psk_identity, Identity) when is_list(Identity), Identity =/= "", length(Identity) =< 65535 -> - list_to_binary(Identity); + binary_filename(Identity); validate_option(user_lookup_fun, undefined) -> undefined; validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) -> @@ -766,17 +766,9 @@ validate_option(srp_identity, undefined) -> undefined; validate_option(srp_identity, {Username, Password}) when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 -> - {list_to_binary(Username), list_to_binary(Password)}; + {unicode:characters_to_binary(Username), + unicode:characters_to_binary(Password)}; -validate_option(ciphers, Value) when is_list(Value) -> - Version = tls_record:highest_protocol_version([]), - try cipher_suites(Version, Value) - catch - exit:_ -> - throw({error, {options, {ciphers, Value}}}); - error:_-> - throw({error, {options, {ciphers, Value}}}) - end; validate_option(reuse_session, Value) when is_function(Value) -> Value; validate_option(reuse_sessions, Value) when is_boolean(Value) -> @@ -936,16 +928,26 @@ emulated_options([Opt|Opts], Inet, Emulated) -> emulated_options([], Inet,Emulated) -> {Inet, Emulated}. -cipher_suites(Version, []) -> +handle_cipher_option(Value, Version) when is_list(Value) -> + try binary_cipher_suites(Version, Value) of + Suites -> + Suites + catch + exit:_ -> + throw({error, {options, {ciphers, Value}}}); + error:_-> + throw({error, {options, {ciphers, Value}}}) + end. +binary_cipher_suites(Version, []) -> %% Defaults to all supported suits ssl_cipher:suites(Version); -cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility +binary_cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0], - cipher_suites(Version, Ciphers); -cipher_suites(Version, [{_,_,_}| _] = Ciphers0) -> + binary_cipher_suites(Version, Ciphers); +binary_cipher_suites(Version, [{_,_,_}| _] = Ciphers0) -> Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0], - cipher_suites(Version, Ciphers); + binary_cipher_suites(Version, Ciphers); -cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> +binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> Supported0 = ssl_cipher:suites(Version) ++ ssl_cipher:anonymous_suites() ++ ssl_cipher:psk_suites(Version) @@ -953,18 +955,18 @@ cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> Supported = ssl_cipher:filter_suites(Supported0), case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of [] -> - Supported; + Supported; %% Defaults to all supported suits Ciphers -> Ciphers end; -cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> +binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> %% Format: ["RC4-SHA","RC4-MD5"] Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0], - cipher_suites(Version, Ciphers); -cipher_suites(Version, Ciphers0) -> + binary_cipher_suites(Version, Ciphers); +binary_cipher_suites(Version, Ciphers0) -> %% Format: "RC4-SHA:RC4-MD5" Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")], - cipher_suites(Version, Ciphers). + binary_cipher_suites(Version, Ciphers). unexpected_format(Error) -> lists:flatten(io_lib:format("Unexpected error: ~p", [Error])). @@ -1036,3 +1038,7 @@ connection_sup(tls_connection) -> tls_connection_sup; connection_sup(dtls_connection) -> dtls_connection_sup. + +binary_filename(FileName) -> + Enc = file:native_name_encoding(), + unicode:characters_to_binary(FileName, unicode, Enc). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 102215119d..64b89e9f95 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -82,13 +82,13 @@ validate_extensions_fun, depth :: integer(), certfile :: binary(), - cert :: der_encoded(), + cert :: public_key:der_encoded(), keyfile :: binary(), - key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', der_encoded()}, + key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()}, password :: string(), - cacerts :: [der_encoded()], + cacerts :: [public_key:der_encoded()], cacertfile :: binary(), - dh :: der_encoded(), + dh :: public_key:der_encoded(), dhfile :: binary(), user_lookup_fun, % server option, fun to lookup the user psk_identity :: binary(), diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index ddc511c652..2e216b32fa 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -86,6 +86,7 @@ basic_tests() -> [app, alerts, send_close, + version_option, connect_twice, connect_dist, clear_pem_cache @@ -1072,6 +1073,13 @@ send_close(Config) when is_list(Config) -> {error, _} = ssl:send(SslS, "Hello world"). %%-------------------------------------------------------------------- +version_option() -> + [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. +version_option(Config) when is_list(Config) -> + Versions = proplists:get_value(supported, ssl:versions()), + [version_option_test(Config, Version) || Version <- Versions]. + +%%-------------------------------------------------------------------- close_transport_accept() -> [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. @@ -3488,3 +3496,28 @@ shutdown_both_result(Socket, client) -> peername_result(S) -> ssl:peername(S). + +version_option_test(Config, Version) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {versions, [Version]}| ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {versions, [Version]}| ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml index 21cf8e4149..3df24bf688 100644 --- a/lib/stdlib/doc/src/ets.xml +++ b/lib/stdlib/doc/src/ets.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2013</year> + <year>1996</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -171,6 +171,10 @@ <p>Returns a list of all tables at the node. Named tables are given by their names, unnamed tables are given by their table identifiers.</p> + <p>There is no guarantee of consistency in the returned list. Tables created + or deleted by other processes "during" the ets:all() call may or may + not be included in the list. Only tables created/deleted <em>before</em> + ets:all() is called are guaranteed to be included/excluded.</p> </desc> </func> <func> diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml index 251a383cf8..ee3c51c62c 100644 --- a/lib/stdlib/doc/src/lists.xml +++ b/lib/stdlib/doc/src/lists.xml @@ -123,6 +123,14 @@ </desc> </func> <func> + <name name="droplast" arity="1"/> + <fsummary>Drop the last element of a list</fsummary> + <desc> + <p>Drops the last element of a <c><anno>List</anno></c>. The list should + be non-empty, otherwise the function will crash with a <c>function_clause</c></p> + </desc> + </func> + <func> <name name="dropwhile" arity="2"/> <fsummary>Drop elements from a list while a predicate is true</fsummary> <desc> diff --git a/lib/stdlib/doc/src/unicode_usage.xml b/lib/stdlib/doc/src/unicode_usage.xml index ee7dd128f1..75505d7d84 100644 --- a/lib/stdlib/doc/src/unicode_usage.xml +++ b/lib/stdlib/doc/src/unicode_usage.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>1999</year> - <year>2013</year> + <year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -41,10 +41,10 @@ future.</p> <p>The functionality described in EEP10 was implemented in Erlang/OTP - as of R13A, but that was by no means the end of it. In R14B01 support + R13A, but that was by no means the end of it. In Erlang/OTP R14B01 support for Unicode file names was added, although it was in no way complete and was by default disabled on platforms where no guarantee was given - for the file name encoding. With R16A came support for UTF-8 encoded + for the file name encoding. With Erlang/OTP R16A came support for UTF-8 encoded source code, among with enhancements to many of the applications to support both Unicode encoded file names as well as support for UTF-8 encoded files in several circumstances. Most notable is the support @@ -52,8 +52,8 @@ for UTF-8 and more support for Unicode character sets in the I/O-system.</p> - <p>In 17.0, the encoding default for Erlang source files was - switched to UTF-8 and in 18.0 Erlang will support atoms in the full + <p>In Erlang/OTP 17.0, the encoding default for Erlang source files was + switched to UTF-8 and in Erlang/OTP 18.0 Erlang will support atoms in the full Unicode range, meaning full Unicode function and module names</p> @@ -222,7 +222,7 @@ <tag>Representation</tag> <item>To handle Unicode characters in Erlang, we have to have a common representation both in lists and binaries. The EEP (10) and - the subsequent initial implementation in R13A settled a standard + the subsequent initial implementation in Erlang/OTP R13A settled a standard representation of Unicode characters in Erlang.</item> <tag>Manipulation</tag> <item>The Unicode characters need to be processed by the Erlang @@ -274,9 +274,9 @@ (<c>+fnu</c>) on platforms where this is not the default.</item> <tag>Source code encoding</tag> <item>When it comes to the Erlang source code, there is support - for the UTF-8 encoding and bytewise encoding. The default in R16B - is bytewise (or latin1) encoding. You can control the encoding by - a comment like: + for the UTF-8 encoding and bytewise encoding. The default in + Erlang/OTP R16B was bytewise (or latin1) encoding; in Erlang/OTP 17.0 + it was changed to UTF-8. You can control the encoding by a comment like: <code> %% -*- coding: utf-8 -*- </code> @@ -290,7 +290,7 @@ <item>Having the source code in UTF-8 also allows you to write string literals containing Unicode characters with code points > 255, although atoms, module names and function names will be - restricted to the ISO-Latin-1 range until the 18.0 release. Binary + restricted to the ISO-Latin-1 range until the Erlang/OTP 18.0 release. Binary literals where you use the <c>/utf8</c> type, can also be expressed using Unicode characters > 255. Having module names using characters other than 7-bit ASCII can cause trouble on @@ -304,7 +304,7 @@ <section> <title>Standard Unicode Representation</title> <p>In Erlang, strings are actually lists of integers. A string was - up until R13 defined to be encoded in the ISO-latin-1 (ISO8859-1) + up until Erlang/OTP R13 defined to be encoded in the ISO-latin-1 (ISO8859-1) character set, which is, code point by code point, a sub-range of the Unicode character set.</p> <p>The standard list encoding for strings was therefore easily @@ -321,7 +321,7 @@ encoding has to be decided upon and the string should be converted to a binary in the preferred encoding using <c>unicode:characters_to_binary/{1,2,3}</c>. Strings are not - generally lists of bytes, as they were before R13. They are lists of + generally lists of bytes, as they were before Erlang/OTP R13. They are lists of characters. Characters are not generally bytes, they are Unicode code points.</p> @@ -447,8 +447,8 @@ Bin4 = <<"Hello"/utf16>>,</code> probably will not appreciate). Another way is to keep it backwards compatible so that only the ISO-Latin-1 character set is used to detect a string. A third way would be to let the user decide - exactly what Unicode ranges are to be viewed as characters. In - R16B you can select either the whole Unicode range or the + exactly what Unicode ranges are to be viewed as characters. Since + Erlang/OTP R16B you can select either the whole Unicode range or the ISO-Latin-1 range by supplying the startup flag <c>+pc </c><i>Range</i>, where <i>Range</i> is either <c>latin1</c> or <c>unicode</c>. For backwards compatibility, the default is @@ -685,7 +685,7 @@ Eshell V5.10.1 (abort with ^G) </item> </taglist> - <p>The Unicode file naming support was introduced with OTP release + <p>The Unicode file naming support was introduced with Erlang/OTP R14B01. A VM operating in Unicode file name translation mode can work with files having names in any language or character set (as long as it is supported by the underlying OS and file system). The @@ -709,7 +709,7 @@ Eshell V5.10.1 (abort with ^G) problem even if it uses transparent file naming. Very few systems have mixed file name encodings. A consistent UTF-8 named system will work perfectly in Unicode file name mode. It was still however - considered experimental in R14B01 and is still not the default on + considered experimental in Erlang/OTP R14B01 and is still not the default on such systems. Unicode file name translation is turned on with the <c>+fnu</c> switch to the On Linux, a VM started without explicitly stating the file name translation mode will default to <c>latin1</c> @@ -757,7 +757,7 @@ Eshell V5.10.1 (abort with ^G) <title>Notes About Raw File Names</title> <marker id="notes-about-raw-filenames"/> <p>Raw file names were introduced together with Unicode file name - support in erts-5.8.2 (OTP R14B01). The reason "raw file + support in erts-5.8.2 (Erlang/OTP R14B01). The reason "raw file names" was introduced in the system was to be able to consistently represent file names given in different encodings on the same system. Having the VM automatically translate a file name @@ -798,10 +798,10 @@ Eshell V5.10.1 (abort with ^G) the argument as a binary.</p> <p>To force Unicode file name translation mode on systems where this - is not the default was considered experimental in OTP R14B01 due to + is not the default was considered experimental in Erlang/OTP R14B01 due to the fact that the initial implementation did not ignore wrongly encoded file names, so that raw file names could spread unexpectedly - throughout the system. Beginning with R16B, the wrongly encoded file + throughout the system. Beginning with Erlang/OTP R16B, the wrongly encoded file names are only retrieved by special functions (e.g. <c>file:list_dir_all/1</c>), so the impact on existing code is much lower, why it is now supported. Unicode file name translation @@ -1032,7 +1032,7 @@ ok <c>io</c>/<c>io_lib:format</c> with the <c>"~tp"</c> and <c>~tP</c> formatting instructions, as described above.</p> <p>You can check this option by calling io:printable_range/0, - which in R16B will return <c>unicode</c> or <c>latin1</c>. To be + which will return <c>unicode</c> or <c>latin1</c>. To be compatible with future (expected) extensions to the settings, one should rather use <c>io_lib:printable_list/1</c> to check if a list is printable according to the setting. That function will @@ -1070,8 +1070,8 @@ ok <item> <p>This function returns the default encoding for Erlang source files (if no encoding comment is present) in the currently - running release. For R16 this returns <c>latin1</c> (meaning - bytewise encoding). In 17.0 and forward it returns + running release. In Erlang/OTP R16B <c>latin1</c> was returned (meaning + bytewise encoding). In Erlang/OTP 17.0 and forward it returns <c>utf8</c>.</p> <p>The encoding of each file can be specified using comments as described in diff --git a/lib/stdlib/src/edlin_expand.erl b/lib/stdlib/src/edlin_expand.erl index 516c0aa30b..a2b4663219 100644 --- a/lib/stdlib/src/edlin_expand.erl +++ b/lib/stdlib/src/edlin_expand.erl @@ -73,7 +73,7 @@ to_atom(Str) -> error end. -match(Prefix, Alts, Extra) -> +match(Prefix, Alts, Extra0) -> Len = length(Prefix), Matches = lists:sort( [{S, A} || {H, A} <- Alts, @@ -89,7 +89,11 @@ match(Prefix, Alts, Extra) -> {yes, Remain, []} end; {complete, Str} -> - {yes, nthtail(Len, Str) ++ Extra, []}; + Extra = case {Extra0,Matches} of + {"(",[{Str,0}]} -> "()"; + {_,_} -> Extra0 + end, + {yes, nthtail(Len, Str) ++ Extra, []}; no -> {no, [], []} end. diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl index 5f96795d92..63e7be4b74 100644 --- a/lib/stdlib/src/erl_eval.erl +++ b/lib/stdlib/src/erl_eval.erl @@ -241,23 +241,15 @@ expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) -> erlang:raise(error, {undef_record,Name}, stacktrace()); %% map -expr({map_field_assoc,_,EK, EV}, Bs0, Lf, Ef, RBs) -> - {value,K,Bs1} = expr(EK, Bs0, Lf, Ef, none), - {value,V,Bs2} = expr(EV, Bs0, Lf, Ef, none), - ret_expr({map_assoc,K,V}, merge_bindings(Bs1,Bs2), RBs); -expr({map_field_exact,_,EK, EV}, Bs0, Lf, Ef, RBs) -> - {value,K,Bs1} = expr(EK, Bs0, Lf, Ef, none), - {value,V,Bs2} = expr(EV, Bs0, Lf, Ef, none), - ret_expr({map_exact,K,V}, merge_bindings(Bs1,Bs2), RBs); expr({map,_, Binding,Es}, Bs0, Lf, Ef, RBs) -> {value, Map0, Bs1} = expr(Binding, Bs0, Lf, Ef, RBs), - {Vs,Bs} = expr_list(Es, Bs1, Lf, Ef), + {Vs,Bs} = eval_map_fields(Es, Bs1, Lf, Ef), ret_expr(lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi); ({map_exact,K,V}, Mi) -> maps:update(K,V,Mi) end, Map0, Vs), Bs, RBs); expr({map,_,Es}, Bs0, Lf, Ef, RBs) -> - {Vs,Bs} = expr_list(Es, Bs0, Lf, Ef), + {Vs,Bs} = eval_map_fields(Es, Bs0, Lf, Ef), ret_expr(lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi) end, maps:new(), Vs), Bs, RBs); @@ -749,6 +741,24 @@ eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) -> end end. +%% eval_map_fields([Field], Bindings, LocalFunctionHandler, +%% ExternalFuncHandler) -> +%% {[{map_assoc | map_exact,Key,Value}],Bindings} + +eval_map_fields(Fs, Bs, Lf, Ef) -> + eval_map_fields(Fs, Bs, Lf, Ef, []). + +eval_map_fields([{map_field_assoc,_,K0,V0}|Fs], Bs0, Lf, Ef, Acc) -> + {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none), + {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none), + eval_map_fields(Fs, Bs2, Lf, Ef, [{map_assoc,K1,V1}|Acc]); +eval_map_fields([{map_field_exact,_,K0,V0}|Fs], Bs0, Lf, Ef, Acc) -> + {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none), + {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none), + eval_map_fields(Fs, Bs2, Lf, Ef, [{map_exact,K1,V1}|Acc]); +eval_map_fields([], Bs, _Lf, _Ef, Acc) -> + {lists:reverse(Acc),Bs}. + %% RBs is the bindings to return when the evalution of a function %% (fun) has finished. If RBs =:= none, then the evalution took place diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 4741bef6b9..f53c6e1278 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -135,9 +135,10 @@ pattern({tuple,Line,Ps}, St0) -> pattern({map,Line,Ps}, St0) -> {TPs,St1} = pattern_list(Ps, St0), {{map,Line,TPs},St1}; -pattern({map_field_exact,Line,Key,V0}, St0) -> - {V,St1} = pattern(V0, St0), - {{map_field_exact,Line,Key,V},St1}; +pattern({map_field_exact,Line,Key0,V0}, St0) -> + {Key,St1} = pattern(Key0, St0), + {V,St2} = pattern(V0, St1), + {{map_field_exact,Line,Key,V},St2}; %%pattern({struct,Line,Tag,Ps}, St0) -> %% {TPs,TPsvs,St1} = pattern_list(Ps, St0), %% {{struct,Line,Tag,TPs},TPsvs,St1}; @@ -310,9 +311,10 @@ expr({tuple,Line,Es0}, St0) -> expr({map,Line,Es0}, St0) -> {Es1,St1} = expr_list(Es0, St0), {{map,Line,Es1},St1}; -expr({map,Line,Var,Es0}, St0) -> - {Es1,St1} = expr_list(Es0, St0), - {{map,Line,Var,Es1},St1}; +expr({map,Line,Arg0,Es0}, St0) -> + {Arg1,St1} = expr(Arg0, St0), + {Es1,St2} = expr_list(Es0, St1), + {{map,Line,Arg1,Es1},St2}; expr({map_field_assoc,Line,K0,V0}, St0) -> {K,St1} = expr(K0, St0), {V,St2} = expr(V0, St1), diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index f630db6032..0c6f41f594 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -100,7 +100,7 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> compile=[], %Compile flags records=dict:new() :: dict(), %Record definitions locals=gb_sets:empty() :: gb_set(), %All defined functions (prescanned) - no_auto=gb_sets:empty() :: gb_set(), %Functions explicitly not autoimported + no_auto=gb_sets:empty() :: gb_set() | 'all', %Functions explicitly not autoimported defined=gb_sets:empty() :: gb_set(), %Defined fuctions on_load=[] :: [fa()], %On-load function on_load_line=0 :: line(), %Line for on_load @@ -3648,15 +3648,22 @@ is_imported_from_erlang(ImportSet,{Func,Arity}) -> {ok,erlang} -> true; _ -> false end. -%% Build set of functions where auto-import is explicitly supressed +%% Build set of functions where auto-import is explicitly suppressed auto_import_suppressed(CompileFlags) -> - L0 = [ X || {no_auto_import,X} <- CompileFlags ], - L1 = [ {Y,Z} || {Y,Z} <- lists:flatten(L0), is_atom(Y), is_integer(Z) ], - gb_sets:from_list(L1). -%% Predicate to find out if autoimport is explicitly supressed for a function + case lists:member(no_auto_import, CompileFlags) of + true -> + all; + false -> + L0 = [ X || {no_auto_import,X} <- CompileFlags ], + L1 = [ {Y,Z} || {Y,Z} <- lists:flatten(L0), is_atom(Y), is_integer(Z) ], + gb_sets:from_list(L1) + end. +%% Predicate to find out if autoimport is explicitly suppressed for a function +is_autoimport_suppressed(all,{_Func,_Arity}) -> + true; is_autoimport_suppressed(NoAutoSet,{Func,Arity}) -> gb_sets:is_element({Func,Arity},NoAutoSet). -%% Predicate to find out if a function specific bif-clash supression (old deprecated) is present +%% Predicate to find out if a function specific bif-clash suppression (old deprecated) is present bif_clash_specifically_disabled(St,{F,A}) -> Nowarn = nowarn_function(nowarn_bif_clash, St#lint.compile), lists:member({F,A},Nowarn). diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl index 8a1d8e0440..9dbe89da91 100644 --- a/lib/stdlib/src/erl_pp.erl +++ b/lib/stdlib/src/erl_pp.erl @@ -479,6 +479,15 @@ lexpr({record_field, _, Rec, F}, Prec, Opts) -> {L,P,R} = inop_prec('.'), El = [lexpr(Rec, L, Opts),$.,lexpr(F, R, Opts)], maybe_paren(P, Prec, El); +lexpr({map, _, Fs}, Prec, Opts) -> + {P,_R} = preop_prec('#'), + El = {first,leaf("#"),map_fields(Fs, Opts)}, + maybe_paren(P, Prec, El); +lexpr({map, _, Map, Fs}, Prec, Opts) -> + {L,P,_R} = inop_prec('#'), + Rl = lexpr(Map, L, Opts), + El = {first,[Rl,leaf("#")],map_fields(Fs, Opts)}, + maybe_paren(P, Prec, El); lexpr({block,_,Es}, _, Opts) -> {list,[{step,'begin',body(Es, Opts)},'end']}; lexpr({'if',_,Cs}, _, Opts) -> @@ -671,6 +680,16 @@ record_field({typed_record_field,Field,Type}, Opts) -> record_field({record_field,_,F}, Opts) -> lexpr(F, 0, Opts). +map_fields(Fs, Opts) -> + tuple(Fs, fun map_field/2, Opts). + +map_field({map_field_assoc,_,K,V}, Opts) -> + Pl = lexpr(K, 0, Opts), + {list,[{step,[Pl,leaf(" =>")],lexpr(V, 0, Opts)}]}; +map_field({map_field_exact,_,K,V}, Opts) -> + Pl = lexpr(K, 0, Opts), + {list,[{step,[Pl,leaf(" :=")],lexpr(V, 0, Opts)}]}. + list({cons,_,H,T}, Es, Opts) -> list(T, [H|Es], Opts); list({nil,_}, Es, Opts) -> diff --git a/lib/stdlib/src/lists.erl b/lib/stdlib/src/lists.erl index d6a9f4645d..6303465d3d 100644 --- a/lib/stdlib/src/lists.erl +++ b/lib/stdlib/src/lists.erl @@ -22,7 +22,7 @@ -compile({no_auto_import,[min/2]}). -export([append/2, append/1, subtract/2, reverse/1, - nth/2, nthtail/2, prefix/2, suffix/2, last/1, + nth/2, nthtail/2, prefix/2, suffix/2, droplast/1, last/1, seq/2, seq/3, sum/1, duplicate/2, min/1, max/1, sublist/2, sublist/3, delete/2, unzip/1, unzip3/1, zip/2, zip3/3, zipwith/3, zipwith3/4, @@ -203,6 +203,19 @@ suffix(Suffix, List) -> Delta = length(List) - length(Suffix), Delta >= 0 andalso nthtail(Delta, List) =:= Suffix. +%% droplast(List) returns the list dropping its last element + +-spec droplast(List) -> InitList when + List :: [T, ...], + InitList :: [T], + T :: term(). + +%% This is the simple recursive implementation +%% reverse(tl(reverse(L))) is faster on average, +%% but creates more garbage. +droplast([_T]) -> []; +droplast([H|T]) -> [H|droplast(T)]. + %% last(List) returns the last element in a list. -spec last(List) -> Last when diff --git a/lib/stdlib/src/re.erl b/lib/stdlib/src/re.erl index afc63496d0..7f3cd8f592 100644 --- a/lib/stdlib/src/re.erl +++ b/lib/stdlib/src/re.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -19,7 +19,6 @@ -module(re). -export([grun/3,urun/3,ucompile/2,replace/3,replace/4,split/2,split/3]). -%-opaque mp() :: {re_pattern, _, _, _, _}. -type mp() :: {re_pattern, _, _, _, _}. -type nl_spec() :: cr | crlf | lf | anycrlf | any. diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl index d18387568d..974583bc28 100644 --- a/lib/stdlib/src/supervisor.erl +++ b/lib/stdlib/src/supervisor.erl @@ -45,10 +45,13 @@ -type restart() :: 'permanent' | 'transient' | 'temporary'. -type shutdown() :: 'brutal_kill' | timeout(). -type worker() :: 'worker' | 'supervisor'. --type sup_name() :: {'local', Name :: atom()} | {'global', Name :: atom()}. +-type sup_name() :: {'local', Name :: atom()} + | {'global', Name :: atom()} + | {'via', Module :: module(), Name :: any()}. -type sup_ref() :: (Name :: atom()) | {Name :: atom(), Node :: node()} | {'global', Name :: atom()} + | {'via', Module :: module(), Name :: any()} | pid(). -type child_spec() :: {Id :: child_id(), StartFunc :: mfargs(), diff --git a/lib/stdlib/src/supervisor_bridge.erl b/lib/stdlib/src/supervisor_bridge.erl index e8405ab9a4..ff4502f0b9 100644 --- a/lib/stdlib/src/supervisor_bridge.erl +++ b/lib/stdlib/src/supervisor_bridge.erl @@ -101,7 +101,16 @@ handle_cast(_, State) -> {noreply, State}. handle_info({'EXIT', Pid, Reason}, State) when State#state.pid =:= Pid -> - report_error(child_terminated, Reason, State), + case Reason of + normal -> + ok; + shutdown -> + ok; + {shutdown, _Term} -> + ok; + _ -> + report_error(child_terminated, Reason, State) + end, {stop, Reason, State#state{pid = undefined}}; handle_info(_, State) -> {noreply, State}. diff --git a/lib/stdlib/test/edlin_expand_SUITE.erl b/lib/stdlib/test/edlin_expand_SUITE.erl index 0cd2688e2e..43c980e994 100644 --- a/lib/stdlib/test/edlin_expand_SUITE.erl +++ b/lib/stdlib/test/edlin_expand_SUITE.erl @@ -26,11 +26,11 @@ -include_lib("test_server/include/test_server.hrl"). -% Default timetrap timeout (set in init_per_testcase). +%% Default timetrap timeout (set in init_per_testcase). -define(default_timeout, ?t:minutes(1)). init_per_testcase(_Case, Config) -> - ?line Dog = ?t:timetrap(?default_timeout), + Dog = ?t:timetrap(?default_timeout), [{watchdog, Dog} | Config]. end_per_testcase(_Case, Config) -> Dog = ?config(watchdog, Config), @@ -67,20 +67,21 @@ normal(doc) -> normal(suite) -> []; normal(Config) when is_list(Config) -> - ?line {module,expand_test} = c:l(expand_test), - % These tests might fail if another module with the prefix "expand_" happens - % to also be loaded. - ?line {yes, "test:", []} = edlin_expand:expand(lists:reverse("expand_")), - ?line {no, [], []} = edlin_expand:expand(lists:reverse("expandXX_")), - ?line {no,[], - [{"a_fun_name",1}, - {"a_less_fun_name",1}, - {"b_comes_after_a",1}, - {"module_info",0}, - {"module_info",1}]} = edlin_expand:expand(lists:reverse("expand_test:")), - ?line {yes,[],[{"a_fun_name",1}, - {"a_less_fun_name",1}]} = edlin_expand:expand( - lists:reverse("expand_test:a_")), + {module,expand_test} = c:l(expand_test), + %% These tests might fail if another module with the prefix + %% "expand_" happens to also be loaded. + {yes, "test:", []} = do_expand("expand_"), + {no, [], []} = do_expand("expandXX_"), + {no,[], + [{"a_fun_name",1}, + {"a_less_fun_name",1}, + {"b_comes_after_a",1}, + {"expand0arity_entirely",0}, + {"module_info",0}, + {"module_info",1}]} = do_expand("expand_test:"), + {yes,[],[{"a_fun_name",1}, + {"a_less_fun_name",1}]} = do_expand("expand_test:a_"), + {yes,"arity_entirely()",[]} = do_expand("expand_test:expand0"), ok. quoted_fun(doc) -> @@ -88,38 +89,35 @@ quoted_fun(doc) -> quoted_fun(suite) -> []; quoted_fun(Config) when is_list(Config) -> - ?line {module,expand_test} = c:l(expand_test), - ?line {module,expand_test1} = c:l(expand_test1), + {module,expand_test} = c:l(expand_test), + {module,expand_test1} = c:l(expand_test1), %% should be no colon after test this time - ?line {yes, "test", []} = edlin_expand:expand(lists:reverse("expand_")), - ?line {no, [], []} = edlin_expand:expand(lists:reverse("expandXX_")), - ?line {no,[],[{"'#weird-fun-name'",0}, - {"'Quoted_fun_name'",0}, - {"'Quoted_fun_too'",0}, - {"a_fun_name",1}, - {"a_less_fun_name",1}, - {"b_comes_after_a",1}, - {"module_info",0}, - {"module_info",1}]} = edlin_expand:expand( - lists:reverse("expand_test1:")), - ?line {yes,"_",[]} = edlin_expand:expand( - lists:reverse("expand_test1:a")), - ?line {yes,[],[{"a_fun_name",1}, - {"a_less_fun_name",1}]} = edlin_expand:expand( - lists:reverse("expand_test1:a_")), - ?line {yes,[], - [{"'#weird-fun-name'",0}, + {yes, "test", []} = do_expand("expand_"), + {no, [], []} = do_expand("expandXX_"), + {no,[],[{"'#weird-fun-name'",1}, {"'Quoted_fun_name'",0}, - {"'Quoted_fun_too'",0}]} = edlin_expand:expand( - lists:reverse("expand_test1:'")), - ?line {yes,"uoted_fun_",[]} = edlin_expand:expand( - lists:reverse("expand_test1:'Q")), - ?line {yes,[], - [{"'Quoted_fun_name'",0}, - {"'Quoted_fun_too'",0}]} = edlin_expand:expand( - lists:reverse("expand_test1:'Quoted_fun_")), - ?line {yes,"weird-fun-name'(",[]} = edlin_expand:expand( - lists:reverse("expand_test1:'#")), + {"'Quoted_fun_too'",0}, + {"a_fun_name",1}, + {"a_less_fun_name",1}, + {"b_comes_after_a",1}, + {"module_info",0}, + {"module_info",1}]} = do_expand("expand_test1:"), + {yes,"_",[]} = do_expand("expand_test1:a"), + {yes,[],[{"a_fun_name",1}, + {"a_less_fun_name",1}]} = do_expand("expand_test1:a_"), + {yes,[], + [{"'#weird-fun-name'",1}, + {"'Quoted_fun_name'",0}, + {"'Quoted_fun_too'",0}]} = do_expand("expand_test1:'"), + {yes,"uoted_fun_",[]} = do_expand("expand_test1:'Q"), + {yes,[], + [{"'Quoted_fun_name'",0}, + {"'Quoted_fun_too'",0}]} = do_expand("expand_test1:'Quoted_fun_"), + {yes,"weird-fun-name'(",[]} = do_expand("expand_test1:'#"), + + %% Since there is a module_info/1 as well as a module_info/0 + %% there should not be a closing parenthesis added. + {yes,"(",[]} = do_expand("expand_test:module_info"), ok. quoted_module(doc) -> @@ -127,51 +125,46 @@ quoted_module(doc) -> quoted_module(suite) -> []; quoted_module(Config) when is_list(Config) -> - ?line {module,'ExpandTestCaps'} = c:l('ExpandTestCaps'), - ?line {yes, "Caps':", []} = edlin_expand:expand(lists:reverse("'ExpandTest")), - ?line {no,[], - [{"a_fun_name",1}, - {"a_less_fun_name",1}, - {"b_comes_after_a",1}, - {"module_info",0}, - {"module_info",1}]} = edlin_expand:expand(lists:reverse("'ExpandTestCaps':")), - ?line {yes,[],[{"a_fun_name",1}, - {"a_less_fun_name",1}]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps':a_")), + {module,'ExpandTestCaps'} = c:l('ExpandTestCaps'), + {yes, "Caps':", []} = do_expand("'ExpandTest"), + {no,[], + [{"a_fun_name",1}, + {"a_less_fun_name",1}, + {"b_comes_after_a",1}, + {"module_info",0}, + {"module_info",1}]} = do_expand("'ExpandTestCaps':"), + {yes,[],[{"a_fun_name",1}, + {"a_less_fun_name",1}]} = do_expand("'ExpandTestCaps':a_"), ok. quoted_both(suite) -> []; quoted_both(Config) when is_list(Config) -> - ?line {module,'ExpandTestCaps'} = c:l('ExpandTestCaps'), - ?line {module,'ExpandTestCaps1'} = c:l('ExpandTestCaps1'), + {module,'ExpandTestCaps'} = c:l('ExpandTestCaps'), + {module,'ExpandTestCaps1'} = c:l('ExpandTestCaps1'), %% should be no colon (or quote) after test this time - ?line {yes, "Caps", []} = edlin_expand:expand(lists:reverse("'ExpandTest")), - ?line {no,[],[{"'#weird-fun-name'",0}, - {"'Quoted_fun_name'",0}, - {"'Quoted_fun_too'",0}, - {"a_fun_name",1}, - {"a_less_fun_name",1}, - {"b_comes_after_a",1}, - {"module_info",0}, - {"module_info",1}]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps1':")), - ?line {yes,"_",[]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps1':a")), - ?line {yes,[],[{"a_fun_name",1}, - {"a_less_fun_name",1}]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps1':a_")), - ?line {yes,[], - [{"'#weird-fun-name'",0}, + {yes, "Caps", []} = do_expand("'ExpandTest"), + {no,[],[{"'#weird-fun-name'",0}, {"'Quoted_fun_name'",0}, - {"'Quoted_fun_too'",0}]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps1':'")), - ?line {yes,"uoted_fun_",[]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps1':'Q")), - ?line {yes,[], - [{"'Quoted_fun_name'",0}, - {"'Quoted_fun_too'",0}]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps1':'Quoted_fun_")), - ?line {yes,"weird-fun-name'(",[]} = edlin_expand:expand( - lists:reverse("'ExpandTestCaps1':'#")), + {"'Quoted_fun_too'",0}, + {"a_fun_name",1}, + {"a_less_fun_name",1}, + {"b_comes_after_a",1}, + {"module_info",0}, + {"module_info",1}]} = do_expand("'ExpandTestCaps1':"), + {yes,"_",[]} = do_expand("'ExpandTestCaps1':a"), + {yes,[],[{"a_fun_name",1}, + {"a_less_fun_name",1}]} = do_expand("'ExpandTestCaps1':a_"), + {yes,[], + [{"'#weird-fun-name'",0}, + {"'Quoted_fun_name'",0}, + {"'Quoted_fun_too'",0}]} = do_expand("'ExpandTestCaps1':'"), + {yes,"uoted_fun_",[]} = do_expand("'ExpandTestCaps1':'Q"), + {yes,[], + [{"'Quoted_fun_name'",0}, + {"'Quoted_fun_too'",0}]} = do_expand("'ExpandTestCaps1':'Quoted_fun_"), + {yes,"weird-fun-name'()",[]} = do_expand("'ExpandTestCaps1':'#"), ok. + +do_expand(String) -> + edlin_expand:expand(lists:reverse(String)). diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl index c4b6b35e72..b194c7cb41 100644 --- a/lib/stdlib/test/erl_eval_SUITE.erl +++ b/lib/stdlib/test/erl_eval_SUITE.erl @@ -42,7 +42,8 @@ try_catch/1, eval_expr_5/1, zero_width/1, - eep37/1]). + eep37/1, + eep43/1]). %% %% Define to run outside of test server @@ -82,7 +83,7 @@ all() -> simple_cases, unary_plus, apply_atom, otp_5269, otp_6539, otp_6543, otp_6787, otp_6977, otp_7550, otp_8133, otp_10622, funs, try_catch, eval_expr_5, zero_width, - eep37]. + eep37, eep43]. groups() -> []. @@ -1424,6 +1425,20 @@ eep37(Config) when is_list(Config) -> 720), ok. +eep43(Config) when is_list(Config) -> + check(fun () -> #{} end, " #{}.", #{}), + check(fun () -> #{a => b} end, "#{a => b}.", #{a => b}), + check(fun () -> + Map = #{a => b}, + {Map#{a := b},Map#{a => c},Map#{d => e}} + end, + "begin " + " Map = #{a => B=b}, " + " {Map#{a := B},Map#{a => c},Map#{d => e}} " + "end.", + {#{a => b},#{a => c},#{a => b,d => e}}), + ok. + %% Check the string in different contexts: as is; in fun; from compiled code. check(F, String, Result) -> check1(F, String, Result), diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl index 94b4397a9c..43e679f7ed 100644 --- a/lib/stdlib/test/erl_expand_records_SUITE.erl +++ b/lib/stdlib/test/erl_expand_records_SUITE.erl @@ -38,7 +38,7 @@ -export([attributes/1, expr/1, guard/1, init/1, pattern/1, strict/1, update/1, otp_5915/1, otp_7931/1, otp_5990/1, - otp_7078/1, otp_7101/1]). + otp_7078/1, otp_7101/1, maps/1]). % Default timetrap timeout (set in init_per_testcase). -define(default_timeout, ?t:minutes(1)). @@ -56,7 +56,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [attributes, expr, guard, init, - pattern, strict, update, {group, tickets}]. + pattern, strict, update, maps, {group, tickets}]. groups() -> [{tickets, [], @@ -402,7 +402,22 @@ update(Config) when is_list(Config) -> ], ?line run(Config, Ts), ok. - + +maps(Config) when is_list(Config) -> + Ts = [<<"-record(rr, {a,b,c}). + t() -> + R0 = id(#rr{a=1,b=2,c=3}), + R1 = id(#rr{a=4,b=5,c=6}), + [{R0,R1}] = + maps:to_list(#{#rr{a=1,b=2,c=3} => #rr{a=4,b=5,c=6}}), + #{#rr{a=1,b=2,c=3} := #rr{a=1,b=2,c=3}} = + #{#rr{a=1,b=2,c=3} => R1}#{#rr{a=1,b=2,c=3} := R0}, + ok. + + id(X) -> X. + ">>], + run(Config, Ts, [strict_record_tests]), + ok. otp_5915(doc) -> "Strict record tests in guards."; diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index 6e9a9dd7bf..48f50d5f43 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -2827,7 +2827,24 @@ bif_clash(Config) when is_list(Config) -> {6,erl_lint,{illegal_guard_local_call,{is_tuple,1}}}, {7,erl_lint,{illegal_guard_local_call,{is_list,1}}}, {8,erl_lint,{illegal_guard_local_call,{is_record,3}}}, - {9,erl_lint,{illegal_guard_local_call,{is_record,3}}}],[]}} + {9,erl_lint,{illegal_guard_local_call,{is_record,3}}}],[]}}, + %% We can also suppress all auto imports at once + {clash22, + <<"-export([size/1, binary_part/2]). + -compile(no_auto_import). + size([]) -> + 0; + size({N,_}) -> + N; + size([_|T]) -> + 1+size(T). + binary_part({B,_},{X,Y}) -> + binary_part(B,{X,Y}); + binary_part(B,{X,Y}) -> + binary:part(B,X,Y). + ">>, + [], + []} ], ?line [] = run(Config, Ts), diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl index cc744ee76b..390322a5fa 100644 --- a/lib/stdlib/test/erl_pp_SUITE.erl +++ b/lib/stdlib/test/erl_pp_SUITE.erl @@ -46,6 +46,7 @@ import_export/1, misc_attrs/1, dialyzer_attrs/1, hook/1, neg_indent/1, + maps_syntax/1, otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1, otp_8473/1, otp_8522/1, otp_8567/1, otp_8664/1, otp_9147/1, @@ -76,7 +77,8 @@ groups() -> [{expr, [], [func, call, recs, try_catch, if_then, receive_after, bits, head_tail, cond1, block, case1, ops, - messages, old_mnemosyne_syntax]}, + messages, old_mnemosyne_syntax, maps_syntax + ]}, {attributes, [], [misc_attrs, import_export, dialyzer_attrs]}, {tickets, [], [otp_6321, otp_6911, otp_6914, otp_8150, otp_8238, @@ -975,6 +977,25 @@ count_atom(L, A) when is_list(L) -> count_atom(_, _) -> 0. +maps_syntax(doc) -> "Maps syntax"; +maps_syntax(suite) -> []; +maps_syntax(Config) when is_list(Config) -> + Ts = [{map_fun_1, + <<"t() ->\n" + " M0 = #{ 1 => hi, hi => 42, 1.0 => {hi,world}},\n" + " M1 = M0#{ 1 := hello, new_val => 1337 },\n" + " map_fun_2:val(M1).\n">>}, + {map_fun_2, + <<"val(#{ 1 := V1, hi := V2, new_val := V3}) -> {V1,V2,V3}.\n">>}], + compile(Config, Ts), + + ok = pp_expr(<<"#{}">>), + ok = pp_expr(<<"#{ a => 1, <<\"hi\">> => \"world\", 33 => 1.0 }">>), + ok = pp_expr(<<"#{ a := V1, <<\"hi\">> := V2 } = M">>), + ok = pp_expr(<<"M#{ a => V1, <<\"hi\">> := V2 }">>), + ok. + + otp_8567(doc) -> "OTP_8567. Avoid duplicated 'undefined' in record field types."; otp_8567(suite) -> []; diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index 82c3e7ecaf..8dc8b2c291 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -75,6 +75,7 @@ -export([otp_9932/1]). -export([otp_9423/1]). -export([otp_10182/1]). +-export([ets_all/1]). -export([memory_check_summary/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -151,6 +152,7 @@ all() -> otp_10182, otp_9932, otp_9423, + ets_all, memory_check_summary]. % MUST BE LAST @@ -5565,7 +5567,19 @@ otp_10182(Config) when is_list(Config) -> ets:delete(Db), In = Out. - +%% Test that ets:all include/exclude tables that we know are created/deleted +ets_all(Config) when is_list(Config) -> + Pids = [spawn_link(fun() -> ets_all_run() end) || _ <- [1,2]], + receive after 3*1000 -> ok end, + [begin unlink(P), exit(P,kill) end || P <- Pids], + ok. + +ets_all_run() -> + Table = ets:new(undefined, []), + true = lists:member(Table, ets:all()), + ets:delete(Table), + false = lists:member(Table, ets:all()), + ets_all_run(). % diff --git a/lib/stdlib/test/expand_test.erl b/lib/stdlib/test/expand_test.erl index 63e4bc3aa0..b9db32c352 100644 --- a/lib/stdlib/test/expand_test.erl +++ b/lib/stdlib/test/expand_test.erl @@ -20,7 +20,8 @@ -export([a_fun_name/1, a_less_fun_name/1, - b_comes_after_a/1]). + b_comes_after_a/1, + expand0arity_entirely/0]). a_fun_name(X) -> X. @@ -30,3 +31,6 @@ a_less_fun_name(X) -> b_comes_after_a(X) -> X. + +expand0arity_entirely () -> + ok. diff --git a/lib/stdlib/test/expand_test1.erl b/lib/stdlib/test/expand_test1.erl index 11b6fec0f3..1d375e5677 100644 --- a/lib/stdlib/test/expand_test1.erl +++ b/lib/stdlib/test/expand_test1.erl @@ -23,7 +23,7 @@ b_comes_after_a/1, 'Quoted_fun_name'/0, 'Quoted_fun_too'/0, - '#weird-fun-name'/0]). + '#weird-fun-name'/1]). a_fun_name(X) -> X. @@ -40,5 +40,5 @@ b_comes_after_a(X) -> 'Quoted_fun_too'() -> too. -'#weird-fun-name'() -> +'#weird-fun-name'(_) -> weird. diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl index 92253ef5b9..f4589a8e24 100644 --- a/lib/stdlib/test/lists_SUITE.erl +++ b/lib/stdlib/test/lists_SUITE.erl @@ -61,7 +61,7 @@ zip_unzip/1, zip_unzip3/1, zipwith/1, zipwith3/1, filter_partition/1, otp_5939/1, otp_6023/1, otp_6606/1, otp_7230/1, - suffix/1, subtract/1]). + suffix/1, subtract/1, droplast/1]). %% Sort randomized lists until stopped. %% @@ -2641,4 +2641,12 @@ sub_non_matching(A, B) -> sub(A, B) -> Res = A -- B, Res = lists:subtract(A, B). - + +%% Test lists:droplast/1 +droplast(Config) when is_list(Config) -> + ?line [] = lists:droplast([x]), + ?line [x] = lists:droplast([x, y]), + ?line {'EXIT', {function_clause, _}} = (catch lists:droplast([])), + ?line {'EXIT', {function_clause, _}} = (catch lists:droplast(x)), + + ok. diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 1ffcf31134..877675772f 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -637,6 +637,14 @@ lay_2(Node, Ctxt) -> sep([follow(text("fun"), D, Ctxt1#ctxt.sub_indent), text("end")]); + named_fun_expr -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:named_fun_expr_name(Node), Ctxt1), + D = lay_clauses(erl_syntax:named_fun_expr_clauses(Node), + {function,D1}, Ctxt1), + sep([follow(text("fun"), D, Ctxt1#ctxt.sub_indent), + text("end")]); + module_qualifier -> {PrecL, _Prec, PrecR} = inop_prec(':'), D1 = lay(erl_syntax:module_qualifier_argument(Node), @@ -892,6 +900,32 @@ lay_2(Node, Ctxt) -> beside(floating(text(".")), D2)), maybe_parentheses(D3, Prec, Ctxt); + map_expr -> + {PrecL, Prec, _} = inop_prec('#'), + Ctxt1 = reset_prec(Ctxt), + D1 = par(seq(erl_syntax:map_expr_fields(Node), + floating(text(",")), Ctxt1, fun lay/2)), + D2 = beside(text("#{"), beside(D1, floating(text("}")))), + D3 = case erl_syntax:map_expr_argument(Node) of + none -> + D2; + A -> + beside(lay(A, set_prec(Ctxt, PrecL)), D2) + end, + maybe_parentheses(D3, Prec, Ctxt); + + map_field_assoc -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:map_field_assoc_name(Node), Ctxt1), + D2 = lay(erl_syntax:map_field_assoc_value(Node), Ctxt1), + par([D1, floating(text("=>")), D2], Ctxt1#ctxt.break_indent); + + map_field_exact -> + Ctxt1 = reset_prec(Ctxt), + D1 = lay(erl_syntax:map_field_exact_name(Node), Ctxt1), + D2 = lay(erl_syntax:map_field_exact_value(Node), Ctxt1), + par([D1, floating(text(":=")), D2], Ctxt1#ctxt.break_indent); + rule -> %% Comments on the name will be repeated; cf. %% `function'. diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 93187fa018..2f4c9ac309 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -220,6 +220,16 @@ macro/2, macro_arguments/1, macro_name/1, + map_expr/1, + map_expr/2, + map_expr_argument/1, + map_expr_fields/1, + map_field_assoc/2, + map_field_assoc_name/1, + map_field_assoc_value/1, + map_field_exact/2, + map_field_exact_name/1, + map_field_exact_value/1, match_expr/2, match_expr_body/1, match_expr_pattern/1, @@ -580,11 +590,12 @@ type(Node) -> {lc, _, _, _} -> list_comp; {bc, _, _, _} -> binary_comp; {match, _, _, _} -> match_expr; + {map, _, _, _} -> map_expr; + {map, _, _} -> map_expr; + {map_field_assoc, _, _, _} -> map_field_assoc; + {map_field_exact, _, _, _} -> map_field_exact; {op, _, _, _, _} -> infix_expr; {op, _, _, _} -> prefix_expr; - {map,_,_} -> map; - {map_field_assoc,_,_,_} -> map_field_assoc; - {map_field_exact,_,_,_} -> map_field_exact; {record, _, _, _, _} -> record_expr; {record, _, _, _} -> record_expr; {record_field, _, _, _, _} -> record_access; @@ -1913,26 +1924,206 @@ atom_literal(Node) -> %% ===================================================================== +%% @equiv map_expr(none, Fields) -map_elements(Node) -> +-spec map_expr([syntaxTree()]) -> syntaxTree(). + +map_expr(Fields) -> + map_expr(none, Fields). + + +%% ===================================================================== +%% @doc Creates an abstract map expression. If `Fields' is +%% `[F1, ..., Fn]', then if `Argument' is `none', the result represents +%% "<code>#{<em>F1</em>, ..., <em>Fn</em>}</code>", +%% otherwise it represents +%% "<code><em>Argument</em>#{<em>F1</em>, ..., <em>Fn</em>}</code>". +%% +%% @see map_expr/1 +%% @see map_expr_argument/1 +%% @see map_expr_fields/1 +%% @see map_field_assoc/2 +%% @see map_field_exact/2 + +-record(map_expr, {argument :: 'none' | syntaxTree(), + fields :: [syntaxTree()]}). + +%% `erl_parse' representation: +%% +%% {map, Pos, Fields} +%% {map, Pos, Argument, Fields} + +-spec map_expr('none' | syntaxTree(), [syntaxTree()]) -> syntaxTree(). + +map_expr(Argument, Fields) -> + tree(map_expr, #map_expr{argument = Argument, fields = Fields}). + +revert_map_expr(Node) -> + Pos = get_pos(Node), + Argument = map_expr_argument(Node), + Fields = map_expr_fields(Node), + case Argument of + none -> + {map, Pos, Fields}; + _ -> + {map, Pos, Argument, Fields} + end. + + +%% ===================================================================== +%% @doc Returns the argument subtree of a `map_expr' node, if any. If `Node' +%% represents "<code>#{...}</code>", `none' is returned. +%% Otherwise, if `Node' represents "<code><em>Argument</em>#{...}</code>", +%% `Argument' is returned. +%% +%% @see map_expr/3 + +-spec map_expr_argument(syntaxTree()) -> 'none' | syntaxTree(). + +map_expr_argument(Node) -> case unwrap(Node) of - {map, _, List} -> - List; - Node1 -> - data(Node1) + {map, _, _} -> + none; + {map, _, Argument, _} -> + Argument; + Node1 -> + (data(Node1))#map_expr.argument end. -map_field_elements({_,_,K,V}) -> - [K,V]. -map(List) -> - tree(map, List). +%% ===================================================================== +%% @doc Returns the list of field subtrees of a `map_expr' node. +%% +%% @see map_expr/3 + +-spec map_expr_fields(syntaxTree()) -> [syntaxTree()]. + +map_expr_fields(Node) -> + case unwrap(Node) of + {map, _, Fields} -> + Fields; + {map, _, _, Fields} -> + Fields; + Node1 -> + (data(Node1))#map_expr.fields + end. + -map_field_assoc(List) -> - tree(map_field_assoc, List). +%% ===================================================================== +%% @doc Creates an abstract map assoc field. The result represents +%% "<code><em>Name</em> => <em>Value</em></code>". +%% +%% @see map_field_assoc_name/1 +%% @see map_field_assoc_value/1 +%% @see map_expr/3 + +-record(map_field_assoc, {name :: syntaxTree(), value :: syntaxTree()}). + +%% `erl_parse' representation: +%% +%% {map_field_assoc, Pos, Name, Value} + +-spec map_field_assoc(syntaxTree(), syntaxTree()) -> syntaxTree(). + +map_field_assoc(Name, Value) -> + tree(map_field_assoc, #map_field_assoc{name = Name, value = Value}). + +revert_map_field_assoc(Node) -> + Pos = get_pos(Node), + Name = map_field_assoc_name(Node), + Value = map_field_assoc_value(Node), + {map_field_assoc, Pos, Name, Value}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `map_field_assoc' node. +%% +%% @see map_field_assoc/2 + +-spec map_field_assoc_name(syntaxTree()) -> syntaxTree(). + +map_field_assoc_name(Node) -> + case Node of + {map_field_assoc, _, Name, _} -> + Name; + _ -> + (data(Node))#map_field_assoc.name + end. + + +%% ===================================================================== +%% @doc Returns the value subtree of a `map_field_assoc' node. +%% +%% @see map_field_assoc/2 + +-spec map_field_assoc_value(syntaxTree()) -> syntaxTree(). + +map_field_assoc_value(Node) -> + case Node of + {map_field_assoc, _, _, Value} -> + Value; + _ -> + (data(Node))#map_field_assoc.name + end. + + +%% ===================================================================== +%% @doc Creates an abstract map exact field. The result represents +%% "<code><em>Name</em> := <em>Value</em></code>". +%% +%% @see map_field_exact_name/1 +%% @see map_field_exact_value/1 +%% @see map_expr/3 + +-record(map_field_exact, {name :: syntaxTree(), value :: syntaxTree()}). + +%% `erl_parse' representation: +%% +%% {map_field_exact, Pos, Name, Value} + +-spec map_field_exact(syntaxTree(), syntaxTree()) -> syntaxTree(). + +map_field_exact(Name, Value) -> + tree(map_field_exact, #map_field_exact{name = Name, value = Value}). + +revert_map_field_exact(Node) -> + Pos = get_pos(Node), + Name = map_field_exact_name(Node), + Value = map_field_exact_value(Node), + {map_field_exact, Pos, Name, Value}. + + +%% ===================================================================== +%% @doc Returns the name subtree of a `map_field_exact' node. +%% +%% @see map_field_exact/2 + +-spec map_field_exact_name(syntaxTree()) -> syntaxTree(). + +map_field_exact_name(Node) -> + case Node of + {map_field_exact, _, Name, _} -> + Name; + _ -> + (data(Node))#map_field_exact.name + end. + + +%% ===================================================================== +%% @doc Returns the value subtree of a `map_field_exact' node. +%% +%% @see map_field_exact/2 + +-spec map_field_exact_value(syntaxTree()) -> syntaxTree(). + +map_field_exact_value(Node) -> + case Node of + {map_field_exact, _, _, Value} -> + Value; + _ -> + (data(Node))#map_field_exact.name + end. -map_field_exact(List) -> - tree(map_field_exact, List). %% ===================================================================== %% @doc Creates an abstract tuple. If `Elements' is @@ -6117,6 +6308,12 @@ revert_root(Node) -> revert_list(Node); list_comp -> revert_list_comp(Node); + map_expr -> + revert_map_expr(Node); + map_field_assoc -> + revert_map_field_assoc(Node); + map_field_exact -> + revert_map_field_exact(Node); match_expr -> revert_match_expr(Node); module_qualifier -> @@ -6358,6 +6555,19 @@ subtrees(T) -> As -> [[macro_name(T)], As] end; + map_expr -> + case map_expr_argument(T) of + none -> + [map_expr_fields(T)]; + V -> + [[V], map_expr_fields(T)] + end; + map_field_assoc -> + [[map_field_assoc_name(T)], + [map_field_assoc_value(T)]]; + map_field_exact -> + [[map_field_exact_name(T)], + [map_field_exact_value(T)]]; match_expr -> [[match_expr_pattern(T)], [match_expr_body(T)]]; @@ -6421,12 +6631,6 @@ subtrees(T) -> try_expr_clauses(T), try_expr_handlers(T), try_expr_after(T)]; - map -> - [map_elements(T)]; - map_field_assoc -> - [map_field_elements(T)]; - map_field_exact -> - [map_field_elements(T)]; tuple -> [tuple_elements(T)] end @@ -6502,6 +6706,10 @@ make_tree(list, [P, [S]]) -> list(P, S); make_tree(list_comp, [[T], B]) -> list_comp(T, B); make_tree(macro, [[N]]) -> macro(N); make_tree(macro, [[N], A]) -> macro(N, A); +make_tree(map_expr, [Fs]) -> map_expr(Fs); +make_tree(map_expr, [[E], Fs]) -> map_expr(E, Fs); +make_tree(map_field_assoc, [[K], [V]]) -> map_field_assoc(K, V); +make_tree(map_field_exact, [[K], [V]]) -> map_field_exact(K, V); make_tree(match_expr, [[P], [E]]) -> match_expr(P, E); make_tree(named_fun_expr, [[N], C]) -> named_fun_expr(N, C); make_tree(module_qualifier, [[M], [N]]) -> module_qualifier(M, N); @@ -6522,10 +6730,7 @@ make_tree(record_index_expr, [[T], [F]]) -> make_tree(rule, [[N], C]) -> rule(N, C); make_tree(size_qualifier, [[N], [A]]) -> size_qualifier(N, A); make_tree(try_expr, [B, C, H, A]) -> try_expr(B, C, H, A); -make_tree(tuple, [E]) -> tuple(E); -make_tree(map, [E]) -> map(E); -make_tree(map_field_assoc, [E]) -> map_field_assoc(E); -make_tree(map_field_exact, [E]) -> map_field_exact(E). +make_tree(tuple, [E]) -> tuple(E). %% ===================================================================== diff --git a/lib/test_server/src/configure.in b/lib/test_server/src/configure.in index 067663feb4..cd723bcd4d 100644 --- a/lib/test_server/src/configure.in +++ b/lib/test_server/src/configure.in @@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script for Erlang. dnl dnl %CopyrightBegin% dnl -dnl Copyright Ericsson AB 1997-2013. All Rights Reserved. +dnl Copyright Ericsson AB 1997-2014. All Rights Reserved. dnl dnl The contents of this file are subject to the Erlang Public License, dnl Version 1.1, (the "License"); you may not use this file except in @@ -170,7 +170,30 @@ case $system in fi SHLIB_EXTRACT_ALL="" ;; - *-netbsd*|*-freebsd*|*-openbsd*|*-dragonfly*) + *-openbsd*) + # Not available on all versions: check for include file. + AC_CHECK_HEADER(dlfcn.h, [ + SHLIB_CFLAGS="-fpic" + SHLIB_LD="${CC}" + SHLIB_LDFLAGS="$LDFLAGS -shared" + SHLIB_SUFFIX=".so" + if test X${enable_m64_build} = Xyes; then + AC_MSG_ERROR(don't know how to link 64-bit dynamic drivers) + fi + if test X${enable_m32_build} = Xyes; then + AC_MSG_ERROR(don't know how to link 32-bit dynamic drivers) + fi + ], [ + # No dynamic loading. + SHLIB_CFLAGS="" + SHLIB_LD="ld" + SHLIB_LDFLAGS="" + SHLIB_SUFFIX="" + AC_MSG_ERROR(don't know how to compile and link dynamic drivers) + ]) + SHLIB_EXTRACT_ALL="" + ;; + *-netbsd*|*-freebsd*|*-dragonfly*) # Not available on all versions: check for include file. AC_CHECK_HEADER(dlfcn.h, [ SHLIB_CFLAGS="-fpic" diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 54be6d4c72..d367e7b5d6 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -176,8 +176,6 @@ module_names(Beams) -> do_cover_compile(Modules) -> do_cover_compile1(lists:usort(Modules)). % remove duplicates -do_cover_compile1([Dont|Rest]) when Dont=:=cover -> - do_cover_compile1(Rest); do_cover_compile1([M|Rest]) -> case {code:is_sticky(M),code:is_loaded(M)} of {true,_} -> @@ -405,6 +403,7 @@ run_test_case_apply({CaseNum,Mod,Func,Args,Name, ref :: reference(), pid :: pid(), mf :: {atom(),atom()}, + last_known_loc :: term(), status :: tc_status() | 'undefined', ret_val :: term(), comment :: list(char()), @@ -436,8 +435,9 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) -> LogOpts, TCCallback) end), put(test_server_detected_fail, []), - St = #st{ref=Ref,pid=Pid,mf={Mod,Func},status=starting,ret_val=[], - comment="",timeout=infinity,config=hd(Args)}, + St = #st{ref=Ref,pid=Pid,mf={Mod,Func},last_known_loc=unknown, + status=starting,ret_val=[],comment="",timeout=infinity, + config=hd(Args)}, run_test_case_msgloop(St). %% Ugly bug (pre R5A): @@ -537,7 +537,22 @@ run_test_case_msgloop(#st{ref=Ref,pid=Pid,end_conf_pid=EndConfPid0}=St0) -> St = setup_termination(RetVal, St0#st{config=undefined}), run_test_case_msgloop(St); {'EXIT',Pid,Reason} -> - St = handle_tc_exit(Reason, St0), + %% This exit typically happens when an unknown external process + %% has caused a test case process to terminate (e.g. if a linked + %% process has crashed). + St = + case Reason of + {What,[Loc0={_M,_F,A,[{file,_}|_]}|_]} when + is_integer(A) -> + Loc = rewrite_loc_item(Loc0), + handle_tc_exit(What, St0#st{last_known_loc=[Loc]}); + {What,[Details,Loc0={_M,_F,A,[{file,_}|_]}|_]} when + is_integer(A) -> + Loc = rewrite_loc_item(Loc0), + handle_tc_exit({What,Details}, St0#st{last_known_loc=[Loc]}); + _ -> + handle_tc_exit(Reason, St0) + end, run_test_case_msgloop(St); {EndConfPid0,{call_end_conf,Data,_Result}} -> #st{mf={Mod,Func},config=CurrConf} = St0, @@ -695,7 +710,7 @@ handle_tc_exit(Reason, #st{config=Config,mf={Mod,Func0},pid=Pid, {testcase_aborted=E,AbortReason,Loc0} -> {{E,AbortReason},Loc0}; Other -> - {Other,unknown} + {Other,St#st.last_known_loc} end, Func = case Status of init_per_testcase=F -> {F,Func0}; @@ -837,9 +852,13 @@ spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) -> spawn_link(FwCall); spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) -> + Func1 = case Func of + {_InitOrEndPerTC,F} -> F; + F -> F + end, FwCall = fun() -> - case catch fw_error_notify(Mod,Func,[], + case catch fw_error_notify(Mod,Func1,[], Error,Loc) of {'EXIT',FwErrorNotifyErr} -> exit({fw_notify_done,error_notification, @@ -847,8 +866,8 @@ spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) -> _ -> ok end, - Conf = [{tc_status,{failed,timetrap_timeout}}|CurrConf], - case catch do_end_tc_call(Mod,Func, + Conf = [{tc_status,{failed,Error}}|CurrConf], + case catch do_end_tc_call(Mod,Func1, {Pid,Error,[Conf]},Error) of {'EXIT',FwEndTCErr} -> exit({fw_notify_done,end_tc,FwEndTCErr}); diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index e24d6ceacb..dcf905db24 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -487,6 +487,7 @@ init([]) -> ok end, test_server_sup:cleanup_crash_dumps(), + test_server_sup:util_start(), State = #state{jobs=[],finish=false}, TI0 = test_server:init_target_info(), TargetHost = test_server_sup:hoststr(), @@ -1055,6 +1056,7 @@ handle_info(_, State) -> %% test suites (if any) and any possible remainting slave node terminate(_Reason, State) -> + test_server_sup:util_stop(), case State#state.trc of false -> ok; Sock -> test_server_node:stop_tracer_node(Sock) @@ -1725,30 +1727,33 @@ make_html_link(LinkName, Target, Explanation) -> ok = write_html_file(LinkName, H). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% start_minor_log_file(Mod, Func) -> AbsName +%% start_minor_log_file(Mod, Func, ParallelTC) -> AbsName %% Mod = atom() %% Func = atom() +%% ParallelTC = bool() %% AbsName = string() %% %% Create a minor log file for the test case Mod,Func,Args. The log file -%% will be stored in the log directory under the name <Mod>.<Func>.log. -%% Some header info will also be inserted into the log file. +%% will be stored in the log directory under the name <Mod>.<Func>.html. +%% Some header info will also be inserted into the log file. If the test +%% case runs in a parallel group, then to avoid clashing file names if the +%% case is executed more than once, the name <Mod>.<Func>.<Timestamp>.html +%% is used. -start_minor_log_file(Mod, Func) -> +start_minor_log_file(Mod, Func, ParallelTC) -> MFA = {Mod,Func,1}, LogDir = get(test_server_log_dir_base), Name0 = lists:flatten(io_lib:format("~w.~w~ts", [Mod,Func,?html_ext])), Name = downcase(Name0), AbsName = filename:join(LogDir, Name), - case file:read_file_info(AbsName) of - {error,_} -> %% normal case, unique name + case (ParallelTC orelse (element(1,file:read_file_info(AbsName))==ok)) of + false -> %% normal case, unique name start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA); - {ok,_} -> %% special case, duplicate names - {_,S,Us} = now(), + true -> %% special case, duplicate names + Tag = test_server_sup:unique_name(), Name1_0 = - lists:flatten(io_lib:format("~w.~w.~w.~w~ts", [Mod,Func,S, - trunc(Us/1000), - ?html_ext])), + lists:flatten(io_lib:format("~w.~w.~ts~ts", [Mod,Func,Tag, + ?html_ext])), Name1 = downcase(Name1_0), AbsName1 = filename:join(LogDir, Name1), start_minor_log_file1(Mod, Func, LogDir, AbsName1, MFA) @@ -3631,7 +3636,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, TSDir = get(test_server_dir), print(major, "=case ~w:~w", [Mod, Func]), - MinorName = start_minor_log_file(Mod, Func), + MinorName = start_minor_log_file(Mod, Func, self() /= Main), print(minor, "<a name=\"top\"></a>", [], internal_raw), MinorBase = filename:basename(MinorName), print(major, "=logfile ~ts", [filename:basename(MinorName)]), diff --git a/lib/test_server/src/test_server_sup.erl b/lib/test_server/src/test_server_sup.erl index 377aa21018..70ead42b1b 100644 --- a/lib/test_server/src/test_server_sup.erl +++ b/lib/test_server/src/test_server_sup.erl @@ -29,10 +29,12 @@ hostatom/0, hostatom/1, hoststr/0, hoststr/1, framework_call/2,framework_call/3,framework_call/4, format_loc/1, + util_start/0, util_stop/0, unique_name/0, call_trace/1]). -include("test_server_internal.hrl"). -define(crash_dump_tar,"crash_dumps.tar.gz"). -define(src_listing_ext, ".src.html"). +-record(util_state, {starter, latest_name}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% timetrap(Timeout,Scale,Pid) -> Handle @@ -583,6 +585,69 @@ downcase([], Result) -> lists:reverse(Result). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% util_start() -> ok +%% +%% Start local utility process +util_start() -> + Starter = self(), + case whereis(?MODULE) of + undefined -> + spawn_link(fun() -> + register(?MODULE, self()), + util_loop(#util_state{starter=Starter}) + end); + _Pid -> + ok + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% util_stop() -> ok +%% +%% Stop local utility process +util_stop() -> + try (?MODULE ! {self(),stop}) of + _ -> + receive {?MODULE,stopped} -> ok + after 5000 -> exit(whereis(?MODULE), kill) + end + catch + _:_ -> + ok + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% unique_name() -> string() +%% +unique_name() -> + ?MODULE ! {self(),unique_name}, + receive {?MODULE,Name} -> Name + after 5000 -> exit({?MODULE,no_util_process}) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% util_loop(State) -> ok +%% +util_loop(State) -> + receive + {From,unique_name} -> + {_,S,Us} = now(), + Ms = trunc(Us/1000), + Name = lists:flatten(io_lib:format("~w.~w", [S,Ms])), + if Name == State#util_state.latest_name -> + timer:sleep(1), + self() ! {From,unique_name}, + util_loop(State); + true -> + From ! {?MODULE,Name}, + util_loop(State#util_state{latest_name = Name}) + end; + {From,stop} -> + catch unlink(State#util_state.starter), + From ! {?MODULE,stopped}, + ok + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% call_trace(TraceSpecFile) -> ok %% %% Read terms on format {m,Mod} | {f,Mod,Func} diff --git a/lib/test_server/src/ts.erl b/lib/test_server/src/ts.erl index 189a71a8ce..11d6f7af4d 100644 --- a/lib/test_server/src/ts.erl +++ b/lib/test_server/src/ts.erl @@ -212,6 +212,12 @@ run_all(_Vars) -> run_some([], _Opts) -> ok; +run_some([{Spec,Mod}|Specs], Opts) -> + case run(Spec, Mod, Opts) of + ok -> ok; + Error -> io:format("~p: ~p~n",[{Spec,Mod},Error]) + end, + run_some(Specs, Opts); run_some([Spec|Specs], Opts) -> case run(Spec, Opts) of ok -> ok; @@ -263,8 +269,17 @@ run(List, Opts) when is_list(List), is_list(Opts) -> run_some(List, Opts); %% run/2 -%% Runs one test spec with Options -run(Testspec, Config) when is_atom(Testspec), is_list(Config) -> +%% Runs one test spec with list of suites or with options +run(Testspec, ModsOrConfig) when is_atom(Testspec), + is_list(ModsOrConfig) -> + case is_list_of_suites(ModsOrConfig) of + false -> + run(Testspec, {config_list,ModsOrConfig}); + true -> + run_some([{Testspec,M} || M <- ModsOrConfig], + [batch]) + end; +run(Testspec, {config_list,Config}) -> Options=check_test_get_opts(Testspec, Config), IsSmoke=proplists:get_value(smoke,Config), File=atom_to_list(Testspec), @@ -310,34 +325,85 @@ run(Testspec, Config) when is_atom(Testspec), is_list(Config) -> run_test(File, [{spec,[Spec]}], Options); %% Runs one module in a spec (interactive) run(Testspec, Mod) when is_atom(Testspec), is_atom(Mod) -> - run_test({atom_to_list(Testspec), Mod}, + run_test({atom_to_list(Testspec),Mod}, [{suite,Mod}], [interactive]). %% run/3 %% Run one module in a spec with Config -run(Testspec,Mod,Config) when is_atom(Testspec), is_atom(Mod), is_list(Config) -> +run(Testspec, Mod, Config) when is_atom(Testspec), + is_atom(Mod), + is_list(Config) -> Options=check_test_get_opts(Testspec, Config), - run_test({atom_to_list(Testspec), Mod}, - [{suite,Mod}], - Options); - -%% Runs one testcase in a module. -run(Testspec, Mod, Case) when is_atom(Testspec), is_atom(Mod), is_atom(Case) -> + run_test({atom_to_list(Testspec),Mod}, + [{suite,Mod}], Options); +%% Run multiple modules with Config +run(Testspec, Mods, Config) when is_atom(Testspec), + is_list(Mods), + is_list(Config) -> + run_some([{Testspec,M} || M <- Mods], Config); +%% Runs one test case in a module. +run(Testspec, Mod, Case) when is_atom(Testspec), + is_atom(Mod), + is_atom(Case) -> Options=check_test_get_opts(Testspec, []), - Args = [{suite,atom_to_list(Mod)},{testcase,atom_to_list(Case)}], + Args = [{suite,Mod},{testcase,Case}], + run_test(atom_to_list(Testspec), Args, Options); +%% Runs one or more groups in a module. +run(Testspec, Mod, Grs={group,_Groups}) when is_atom(Testspec), + is_atom(Mod) -> + Options=check_test_get_opts(Testspec, []), + Args = [{suite,Mod},Grs], + run_test(atom_to_list(Testspec), Args, Options); +%% Runs one or more test cases in a module. +run(Testspec, Mod, TCs={testcase,_Cases}) when is_atom(Testspec), + is_atom(Mod) -> + Options=check_test_get_opts(Testspec, []), + Args = [{suite,Mod},TCs], run_test(atom_to_list(Testspec), Args, Options). %% run/4 -%% Run one testcase in a module with Options. +%% Run one test case in a module with Options. run(Testspec, Mod, Case, Config) when is_atom(Testspec), is_atom(Mod), is_atom(Case), is_list(Config) -> Options=check_test_get_opts(Testspec, Config), - Args = [{suite,atom_to_list(Mod)}, {testcase,atom_to_list(Case)}], + Args = [{suite,Mod},{testcase,Case}], + run_test(atom_to_list(Testspec), Args, Options); +%% Run one or more test cases in a module with Options. +run(Testspec, Mod, {testcase,Cases}, Config) when is_atom(Testspec), + is_atom(Mod) -> + run(Testspec, Mod, Cases, Config); +run(Testspec, Mod, Cases, Config) when is_atom(Testspec), + is_atom(Mod), + is_list(Cases), + is_list(Config) -> + Options=check_test_get_opts(Testspec, Config), + Args = [{suite,Mod},Cases], + run_test(atom_to_list(Testspec), Args, Options); +%% Run one or more groups in a module with Options. +run(Testspec, Mod, Grs={group,_Groups}, Config) when is_atom(Testspec), + is_atom(Mod) -> + Options=check_test_get_opts(Testspec, Config), + Args = [{suite,Mod},Grs], run_test(atom_to_list(Testspec), Args, Options). + +is_list_of_suites(List) -> + lists:all(fun(Suite) -> + S = if is_atom(Suite) -> atom_to_list(Suite); + true -> Suite + end, + try lists:last(string:tokens(S,"_")) of + "SUITE" -> true; + "suite" -> true; + _ -> false + catch + _:_ -> false + end + end, List). + %% Create a spec to skip all SUITES, this is used when the application %% to be tested is not part of the OTP release to be tested. create_skip_spec(Testspec, SuitesToSkip) -> diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index 3a868f1300..f007f780eb 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -664,6 +664,7 @@ resulting regexp is surrounded by \\_< and \\_>." "is_function" "is_integer" "is_list" + "is_map" "is_number" "is_pid" "is_port" @@ -715,7 +716,8 @@ resulting regexp is surrounded by \\_< and \\_>." "pos_integer" "string" "term" - "timeout") + "timeout" + "map") "Erlang type specs types")) (eval-and-compile @@ -772,6 +774,7 @@ resulting regexp is surrounded by \\_< and \\_>." "is_function" "is_integer" "is_list" + "is_map" "is_number" "is_pid" "is_port" @@ -791,6 +794,7 @@ resulting regexp is surrounded by \\_< and \\_>." "list_to_tuple" "load_module" "make_ref" + "map_size" "max" "min" "module_loaded" diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 6aeb251ac2..a24d70ed92 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -89,8 +89,9 @@ flush/1, stop/0, stop/1]). -export([remote_start/1,get_main_node/0]). -%-export([bump/5]). --export([transform/4]). % for test purposes + +%% Used internally to ensure we upgrade the code to the latest version. +-export([main_process_loop/1,remote_process_loop/1]). -record(main_state, {compiled=[], % [{Module,File}] imported=[], % [{Module,File,ImportFile}] @@ -110,7 +111,6 @@ -define(BUMP_REC_NAME,bump). -record(vars, {module, % atom() Module name - vsn, % atom() init_info=[], % [{M,F,A,C,L}] @@ -230,25 +230,9 @@ compile_directory(Dir) when is_list(Dir) -> compile_directory(Dir, Options) when is_list(Dir), is_list(Options) -> case file:list_dir(Dir) of {ok, Files} -> - - %% Filter out all erl files (except cover.erl) - ErlFileNames = - lists:filter(fun("cover.erl") -> - false; - (File) -> - case filename:extension(File) of - ".erl" -> true; - _ -> false - end - end, - Files), - - %% Create a list of .erl file names (incl path) and call - %% compile_modules/2 with the list of file names. - ErlFiles = lists:map(fun(ErlFileName) -> - filename:join(Dir, ErlFileName) - end, - ErlFileNames), + ErlFiles = [filename:join(Dir, File) || + File <- Files, + filename:extension(File) =:= ".erl"], compile_modules(ErlFiles, Options); Error -> Error @@ -262,7 +246,7 @@ compile_modules([File|Files], Options, Result) -> R = call({compile, File, Options}), compile_modules(Files,Options,[R|Result]); compile_modules([],_Opts,Result) -> - reverse(Result). + lists:reverse(Result). filter_options(Options) -> lists:filter(fun(Option) -> @@ -320,25 +304,9 @@ compile_beam_directory() -> compile_beam_directory(Dir) when is_list(Dir) -> case file:list_dir(Dir) of {ok, Files} -> - - %% Filter out all beam files (except cover.beam) - BeamFileNames = - lists:filter(fun("cover.beam") -> - false; - (File) -> - case filename:extension(File) of - ".beam" -> true; - _ -> false - end - end, - Files), - - %% Create a list of .beam file names (incl path) and call - %% compile_beam/1 for each such file name - BeamFiles = lists:map(fun(BeamFileName) -> - filename:join(Dir, BeamFileName) - end, - BeamFileNames), + BeamFiles = [filename:join(Dir, File) || + File <- Files, + filename:extension(File) =:= ".beam"], compile_beams(BeamFiles); Error -> Error @@ -350,7 +318,7 @@ compile_beams([File|Files],Result) -> R = compile_beam(File), compile_beams(Files,[R|Result]); compile_beams([],Result) -> - reverse(Result). + lists:reverse(Result). %% analyse(Module) -> @@ -613,8 +581,11 @@ main_process_loop(State) -> Compiled = add_compiled(Module, File, State#main_state.compiled), Imported = remove_imported(Module,State#main_state.imported), - main_process_loop(State#main_state{compiled = Compiled, - imported = Imported}); + NewState = State#main_state{compiled = Compiled, + imported = Imported}, + %% This module (cover) could have been reloaded. Make + %% sure we run the new code. + ?MODULE:main_process_loop(NewState); error -> reply(From, {error, File}), main_process_loop(State) @@ -639,8 +610,11 @@ main_process_loop(State) -> end, reply(From,Reply), Imported = remove_imported(Module,State#main_state.imported), - main_process_loop(State#main_state{compiled = Compiled, - imported = Imported}); + NewState = State#main_state{compiled = Compiled, + imported = Imported}, + %% This module (cover) could have been reloaded. Make + %% sure we run the new code. + ?MODULE:main_process_loop(NewState); {error,no_beam} -> %% The module has first been compiled from .erl, and now %% someone tries to compile it from .beam @@ -857,7 +831,7 @@ remote_process_loop(State) -> {remote,load_compiled,Compiled} -> Compiled1 = load_compiled(Compiled,State#remote_state.compiled), remote_reply(State#remote_state.main_node, ok), - remote_process_loop(State#remote_state{compiled=Compiled1}); + ?MODULE:remote_process_loop(State#remote_state{compiled=Compiled1}); {remote,unload,UnloadedModules} -> unload(UnloadedModules), @@ -1257,12 +1231,12 @@ add_imported(M, F1, ImportFile, [{M,_F2,ImportFiles}|Imported], Acc) -> dont_import; false -> NewEntry = {M, F1, [ImportFile | ImportFiles]}, - {ok, reverse([NewEntry | Acc]) ++ Imported} + {ok, lists:reverse([NewEntry | Acc]) ++ Imported} end; add_imported(M, F, ImportFile, [H|Imported], Acc) -> add_imported(M, F, ImportFile, Imported, [H|Acc]); add_imported(M, F, ImportFile, [], Acc) -> - {ok, reverse([{M, F, [ImportFile]} | Acc])}. + {ok, lists:reverse([{M, F, [ImportFile]} | Acc])}. %% Removes a module from the list of imported modules and writes a warning %% This is done when a module is compiled. @@ -1383,9 +1357,9 @@ do_compile_beam(Module,Beam,UserOptions) -> {error,E}; encrypted_abstract_code=E -> {error,E}; - {Vsn,Code} -> + {raw_abstract_v1,Code} -> Forms0 = epp:interpret_file_attribute(Code), - {Forms,Vars} = transform(Vsn, Forms0, Module, Beam), + {Forms,Vars} = transform(Forms0, Module), %% We need to recover the source from the compilation %% info otherwise the newly compiled module will have @@ -1400,7 +1374,7 @@ do_compile_beam(Module,Beam,UserOptions) -> {module, Module} -> %% Store info about all function clauses in database - InitInfo = reverse(Vars#vars.init_info), + InitInfo = lists:reverse(Vars#vars.init_info), ets:insert(?COVER_CLAUSE_TABLE, {Module, InitInfo}), %% Store binary code so it can be loaded on remote nodes @@ -1411,7 +1385,11 @@ do_compile_beam(Module,Beam,UserOptions) -> _Error -> do_clear(Module), error - end + end; + {_VSN,_Code} -> + %% Wrong version of abstract code. Just report that there + %% is no abstract code. + {error,no_abstract_code} end. get_abstract_code(Module, Beam) -> @@ -1445,28 +1423,9 @@ get_compile_info(Module, Beam) -> [] end. -transform(Vsn, Code, Module, Beam) when Vsn=:=abstract_v1; Vsn=:=abstract_v2 -> - Vars0 = #vars{module=Module, vsn=Vsn}, - MainFile=find_main_filename(Code), - {ok, MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on), - - %% Add module and export information to the munged forms - %% Information about module_info must be removed as this function - %% is added at compilation - {ok, {Module, [{exports,Exports1}]}} = beam_lib:chunks(Beam, [exports]), - Exports2 = lists:filter(fun(Export) -> - case Export of - {module_info,_} -> false; - _ -> true - end - end, - Exports1), - Forms = [{attribute,1,module,Module}, - {attribute,2,export,Exports2}]++ MungedForms, - {Forms,Vars}; -transform(Vsn=raw_abstract_v1, Code, Module, _Beam) -> +transform(Code, Module) -> MainFile=find_main_filename(Code), - Vars0 = #vars{module=Module, vsn=Vsn}, + Vars0 = #vars{module=Module}, {ok,MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on), {MungedForms,Vars}. @@ -1486,7 +1445,7 @@ transform_2([Form0|Forms],MungedForms,Vars,MainFile,Switch) -> transform_2(Forms,[MungedForm|MungedForms],Vars2,MainFile,NewSwitch) end; transform_2([],MungedForms,Vars,_,_) -> - {ok, reverse(MungedForms), Vars}. + {ok, lists:reverse(MungedForms), Vars}. %% Expand short-circuit Boolean expressions. expand(Expr) -> @@ -1553,14 +1512,9 @@ aux_var(Vars, N) -> end. %% This code traverses the abstract code, stored as the abstract_code -%% chunk in the BEAM file, as described in absform(3) for Erlang/OTP R8B -%% (Vsn=abstract_v2). -%% The abstract format after preprocessing differs slightly from the abstract -%% format given eg using epp:parse_form, this has been noted in comments. -%% The switch is turned off when we encounter other files then the main file. +%% chunk in the BEAM file, as described in absform(3). +%% The switch is turned off when we encounter other files than the main file. %% This way we will be able to exclude functions defined in include files. -munge({function,0,module_info,_Arity,_Clauses},_Vars,_MainFile,_Switch) -> - ignore; % module_info will be added again when the forms are recompiled munge({function,Line,Function,Arity,Clauses},Vars,_MainFile,on) -> Vars2 = Vars#vars{function=Function, arity=Arity, @@ -1618,7 +1572,7 @@ munge_clauses([Clause|Clauses], Vars, Lines, MClauses) -> MClauses]) end; munge_clauses([], Vars, Lines, MungedClauses) -> - {reverse(MungedClauses), Vars#vars{lines = Lines}}. + {lists:reverse(MungedClauses), Vars#vars{lines = Lines}}. munge_body(Expr, Vars) -> munge_body(Expr, Vars, [], []). @@ -1662,7 +1616,7 @@ munge_body([Expr|Body], Vars, MungedBody, LastExprBumpLines) -> munge_body(Body, Vars3, MungedExprs1, NewBumps) end; munge_body([], Vars, MungedBody, _LastExprBumpLines) -> - {reverse(MungedBody), Vars}. + {lists:reverse(MungedBody), Vars}. %%% Fix last expression (OTP-8188). A typical example: %%% @@ -1800,16 +1754,16 @@ munge_expr({match,Line,ExprL,ExprR}, Vars) -> munge_expr({tuple,Line,Exprs}, Vars) -> {MungedExprs, Vars2} = munge_exprs(Exprs, Vars, []), {{tuple,Line,MungedExprs}, Vars2}; -munge_expr({record,Line,Expr,Exprs}, Vars) -> - %% Only for Vsn=raw_abstract_v1 - {MungedExprName, Vars2} = munge_expr(Expr, Vars), +munge_expr({record,Line,Name,Exprs}, Vars) -> + {MungedExprFields, Vars2} = munge_exprs(Exprs, Vars, []), + {{record,Line,Name,MungedExprFields}, Vars2}; +munge_expr({record,Line,Arg,Name,Exprs}, Vars) -> + {MungedArg, Vars2} = munge_expr(Arg, Vars), {MungedExprFields, Vars3} = munge_exprs(Exprs, Vars2, []), - {{record,Line,MungedExprName,MungedExprFields}, Vars3}; + {{record,Line,MungedArg,Name,MungedExprFields}, Vars3}; munge_expr({record_field,Line,ExprL,ExprR}, Vars) -> - %% Only for Vsn=raw_abstract_v1 - {MungedExprL, Vars2} = munge_expr(ExprL, Vars), - {MungedExprR, Vars3} = munge_expr(ExprR, Vars2), - {{record_field,Line,MungedExprL,MungedExprR}, Vars3}; + {MungedExprR, Vars2} = munge_expr(ExprR, Vars), + {{record_field,Line,ExprL,MungedExprR}, Vars2}; munge_expr({cons,Line,ExprH,ExprT}, Vars) -> {MungedExprH, Vars2} = munge_expr(ExprH, Vars), {MungedExprT, Vars3} = munge_expr(ExprT, Vars2), @@ -1871,23 +1825,10 @@ munge_expr({'try',Line,Body,Clauses,CatchClauses,After}, Vars) -> {MungedAfter, Vars4} = munge_body(After, Vars3), {{'try',Line,MungedBody,MungedClauses,MungedCatchClauses,MungedAfter}, Vars4}; -%% Difference in abstract format after preprocessing: Funs get an extra -%% element Extra. -%% NOT NECESSARY FOR Vsn=raw_abstract_v1 -munge_expr({'fun',Line,{function,Name,Arity},_Extra}, Vars) -> - {{'fun',Line,{function,Name,Arity}}, Vars}; -munge_expr({'fun',Line,{clauses,Clauses},_Extra}, Vars) -> - {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), - {{'fun',Line,{clauses,MungedClauses}}, Vars2}; munge_expr({'fun',Line,{clauses,Clauses}}, Vars) -> - %% Only for Vsn=raw_abstract_v1 {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), {{'fun',Line,{clauses,MungedClauses}}, Vars2}; -munge_expr({named_fun,Line,Name,Clauses,_Extra}, Vars) -> - {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), - {{named_fun,Line,Name,MungedClauses}, Vars2}; munge_expr({named_fun,Line,Name,Clauses}, Vars) -> - %% Only for Vsn=raw_abstract_v1 {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), {{named_fun,Line,Name,MungedClauses}, Vars2}; munge_expr({bin,Line,BinElements}, Vars) -> @@ -1908,7 +1849,7 @@ munge_exprs([Expr|Exprs], Vars, MungedExprs) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_exprs(Exprs, Vars2, [MungedExpr|MungedExprs]); munge_exprs([], Vars, MungedExprs) -> - {reverse(MungedExprs), Vars}. + {lists:reverse(MungedExprs), Vars}. %% Every qualifier is decorated with a counter. munge_qualifiers(Qualifiers, Vars) -> @@ -1927,7 +1868,7 @@ munge_qs([Expr|Qs], Vars, MQs) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_qs1(Qs, L, MungedExpr, Vars, Vars2, MQs); munge_qs([], Vars, MQs) -> - {reverse(MQs), Vars}. + {lists:reverse(MQs), Vars}. munge_qs1(Qs, Line, NQ, Vars, Vars2, MQs) -> case new_bumps(Vars2, Vars) of @@ -2120,7 +2061,7 @@ merge_clauses([{{M,F,A,_C1},R1},{{M,F,A,C2},R2}|Clauses], MFun, Result) -> merge_clauses([{{M,F,A,_C},R}|Clauses], MFun, Result) -> merge_clauses(Clauses, MFun, [{{M,F,A},R}|Result]); merge_clauses([], _Fun, Result) -> - reverse(Result). + lists:reverse(Result). merge_functions([{_MFA,R}|Functions], MFun) -> merge_functions(Functions, MFun, R); @@ -2441,14 +2382,6 @@ not_loaded(_Module,_Else, State) -> %%%--Div----------------------------------------------------------------- -reverse(List) -> - reverse(List,[]). -reverse([H|T],Acc) -> - reverse(T,[H|Acc]); -reverse([],Acc) -> - Acc. - - escape_lt_and_gt(Rawline,HTML) when HTML =/= true -> Rawline; escape_lt_and_gt(Rawline,_HTML) -> diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl index bd71218474..ec61c57cec 100644 --- a/lib/tools/test/cover_SUITE.erl +++ b/lib/tools/test/cover_SUITE.erl @@ -22,7 +22,8 @@ suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). --export([start/1, compile/1, analyse/1, misc/1, stop/1, +-export([coverage/1, coverage_analysis/1, + start/1, compile/1, analyse/1, misc/1, stop/1, distribution/1, reconnect/1, die_and_reconnect/1, dont_reconnect_after_stop/1, stop_node_after_disconnect/1, export_import/1, @@ -30,6 +31,8 @@ otp_8188/1, otp_8270/1, otp_8273/1, otp_8340/1, otp_10979_hanging_node/1, compile_beam_opts/1, eep37/1]). +-export([do_coverage/1]). + -include_lib("test_server/include/test_server.hrl"). %%---------------------------------------------------------------------- @@ -46,25 +49,25 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> + NoStartStop = [eif,otp_5305,otp_5418,otp_7095,otp_8273, + otp_8340,otp_8188,compile_beam_opts,eep37], + StartStop = [start, compile, analyse, misc, stop, + distribution, reconnect, die_and_reconnect, + dont_reconnect_after_stop, stop_node_after_disconnect, + export_import, otp_5031, otp_6115, + otp_8270, otp_10979_hanging_node], case whereis(cover_server) of undefined -> - [start, compile, analyse, misc, stop, - distribution, reconnect, die_and_reconnect, - dont_reconnect_after_stop, stop_node_after_disconnect, - export_import, otp_5031, eif, otp_5305, otp_5418, - otp_6115, otp_7095, otp_8188, otp_8270, otp_8273, - otp_8340, otp_10979_hanging_node, compile_beam_opts, eep37]; + [coverage,StartStop ++ NoStartStop]; _pid -> - {skip, - "It looks like the test server is running " - "cover. Can't run cover test."} + [coverage|NoStartStop++[coverage_analysis]] end. groups() -> []. init_per_suite(Config) -> - Config. + [{ct_is_running_cover,whereis(cover_server) =/= undefined}|Config]. end_per_suite(_Config) -> ok. @@ -90,13 +93,64 @@ init_per_testcase(TC, Config) when TC =:= misc; init_per_testcase(_TestCase, Config) -> Config. -end_per_testcase(TestCase, _Config) -> - case lists:member(TestCase,[start,compile,analyse,misc]) of +end_per_testcase(TestCase, Config) -> + NoStop = [start,compile,analyse,misc], + DontStop = proplists:get_bool(ct_is_running_cover, Config) orelse + lists:member(TestCase, NoStop), + case DontStop of true -> ok; false -> cover:stop() end, ok. +coverage(Config) when is_list(Config) -> + {ok,?MODULE} = cover:compile_beam(?MODULE), + ?MODULE:do_coverage(Config). + +do_coverage(Config) -> + Outdir = ?config(priv_dir, Config), + ExportFile = filename:join(Outdir, "export"), + ok = cover:export(ExportFile, ?MODULE), + {error,{already_started,_}} = cover:start(), + {error,_} = cover:compile_beam(non_existing_module), + _ = cover:which_nodes(), + _ = cover:modules(), + _ = cover:imported(), + {error,{not_cover_compiled,lists}} = cover:analyze(lists), + + %% Cover escaping of '&' in HTML files. + + case proplists:get_bool(ct_is_running_cover, Config) of + false -> + %% Cover server was implicitly started when this module + %% was cover-compiled. We must stop the cover server, but + %% we must ensure that this module is not on the call + %% stack when it is unloaded. Therefore, the call that + %% follows MUST be tail-recursive. + cover:stop(); + true -> + %% Cover server was started by common_test; don't stop it. + ok + end. + +%% This test case will only be run when common_test is running cover. +coverage_analysis(Config) when is_list(Config) -> + {ok,Analysis1} = cover:analyze(?MODULE), + io:format("~p\n", [Analysis1]), + {ok,Analysis2} = cover:analyze(?MODULE, calls), + io:format("~p\n", [Analysis2]), + {ok,_Analysis3} = cover:analyze(?MODULE, calls, line), + + Outdir = ?config(priv_dir, Config), + Outfile = filename:join(Outdir, ?MODULE), + + {ok,Outfile} = cover:analyze_to_file(?MODULE, Outfile), + {ok,Contents} = file:read_file(Outfile), + ok = file:delete(Outfile), + ok = io:put_chars(Contents), + {ok,Outfile} = cover:analyze_to_file(?MODULE, Outfile, [html]), + ok. + start(suite) -> []; start(Config) when is_list(Config) -> ?line ok = file:set_cwd(?config(data_dir, Config)), @@ -759,7 +813,6 @@ eif(Config) when is_list(Config) -> %% in cover_inc.beam - not the ones from the included file. ?line cover_inc:func(), ?line {ok, [_, _]} = cover:analyse(cover_inc, line), - ?line cover:stop(), ok. otp_5305(suite) -> []; @@ -775,7 +828,6 @@ otp_5305(Config) when is_list(Config) -> ">>, ?line ok = file:write_file(File, Test), ?line {ok, t} = cover:compile(File), - ?line cover:stop(), ?line ok = file:delete(File), ok. @@ -790,7 +842,6 @@ otp_5418(Config) when is_list(Config) -> ?line ok = file:write_file(File, Test), ?line {ok, t} = cover:compile(File), ?line {ok,{t,{0,0}}} = cover:analyse(t, module), - ?line cover:stop(), ?line ok = file:delete(File), ok. @@ -952,7 +1003,6 @@ otp_7095(Config) when is_list(Config) -> {{t,67},1},{{t,69},1},{{t,71},1},{{t,74},1}, {{t,76},0},{{t,78},1}, {{t,82},2}]} = cover:analyse(t, calls, line), - ?line cover:stop(), ?line ok = file:delete(File), ok. @@ -1028,7 +1078,6 @@ otp_8273(Config) when is_list(Config) -> ">>, ?line File = cc_mod(t, Test, Config), ?line ok = t:t(), - ?line cover:stop(), ?line ok = file:delete(File), ok. @@ -1066,7 +1115,6 @@ otp_8188(Config) when is_list(Config) -> ?line File = cc_mod(t, Test, Config), ?line false = t:test(nok), ?line {ok,[{{t,11},1},{{t,12},1}]} = cover:analyse(t, calls, line), - ?line cover:stop(), ?line ok = file:delete(File), %% Bit string comprehensions are now traversed; @@ -1433,6 +1481,8 @@ compile_beam_opts(Config) when is_list(Config) -> export_all, debug_info, return_errors]), + code:purge(t), + code:delete(t), Exports = [{func1,0}, {macro, 0}, @@ -1443,7 +1493,6 @@ compile_beam_opts(Config) when is_list(Config) -> Exports = t:module_info(exports), {ok, t} = cover:compile_beam("t"), Exports = t:module_info(exports), - cover:stop(), ok = file:delete("t.beam"), ok = file:set_cwd(Cwd), ok. diff --git a/lib/wx/api_gen/Makefile b/lib/wx/api_gen/Makefile index 8adb485ba9..3e41ac7bc5 100644 --- a/lib/wx/api_gen/Makefile +++ b/lib/wx/api_gen/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2008-2012. All Rights Reserved. +# Copyright Ericsson AB 2008-2014. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -60,7 +60,7 @@ $(GL): glxml_generated $(GL_COMP_T) glapi.conf erl -noshell -run gl_gen code && touch gl_code_generated %.beam: %.erl wx_gen.hrl gl_gen.hrl - $(ERLC) -W $(ERL_FLAGS) $(ERL_COMPILE_FLAGS) $< -o$(EBIN) + $(ERLC) -W $(ERL_FLAGS) $(ERL_COMPILE_FLAGS) -o$(EBIN) $< # TODO split cleans into separate targets? complete_clean: diff --git a/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src b/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src index 9c5f46b253..5d20019d8f 100644 --- a/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src +++ b/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src @@ -1,17 +1,5 @@ -case 98: { // wxeEvtListener::wxeEvtListener - wxeEvtListener *Result = new wxeEvtListener(Ecmd.port); - rt.addRef(getRef((void *)Result,memenv), "wxeEvtListener"); - break; -} -case 99: { // wxeEvtListener::destroy - wxObject *This = (wxObject *) getPtr(bp,memenv); - rt.addAtom("ok"); - delete This; - break; -} -case 100: { // wxEvtHandler::Connect - wxeEvtListener *Listener = (wxeEvtListener *) getPtr(bp,memenv); bp += 4; +case 100: { // wxEvtHandler::Connect wxEvtHandler *This = (wxEvtHandler *) getPtr(bp, memenv); bp += 4; int * winid = (int *) bp; bp += 4; int * lastId = (int *) bp; bp += 4; @@ -22,20 +10,22 @@ case 100: { // wxEvtHandler::Connect int * eventTypeLen = (int *) bp; bp += 4; int * class_nameLen = (int *) bp; bp += 4; - if(*haveUserData) { + if(*haveUserData) { userData = new wxeErlTerm(Ecmd.bin[0]); } int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; char *class_name = bp; bp+= *class_nameLen; if(eventType > 0 ) { - wxeCallbackData * Evt_cb = new wxeCallbackData(Ecmd.caller,getRef(This, memenv), - class_name,*fun_cb, - *skip, userData, Listener); + wxeEvtListener * Evt_cb = new wxeEvtListener(Ecmd.caller,getRef(This, memenv), + class_name,*fun_cb, + *skip, userData, Ecmd.port); This->Connect((int) *winid,(int) *lastId,eventType, (wxObjectEventFunction)(wxEventFunction) &wxeEvtListener::forward, - Evt_cb, Listener); + Evt_cb, Evt_cb); rt.addAtom("ok"); + rt.addRef(getRef((void *)Evt_cb,memenv), "wxeEvtListener"); + rt.addTupleCount(2); } else { rt.addAtom("badarg"); rt.addAtom("event_type"); @@ -43,7 +33,7 @@ case 100: { // wxEvtHandler::Connect } break; } -case 101: { // wxEvtHandler::Disconnect +case 101: { // wxEvtHandler::Disconnect wxeEvtListener *Listener = (wxeEvtListener *) getPtr(bp,memenv); bp += 4; wxEvtHandler *This = (wxEvtHandler *) getPtr(bp, memenv); bp += 4; int * winid = (int *) bp; bp += 4; @@ -53,14 +43,14 @@ case 101: { // wxEvtHandler::Disconnect int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; if(eventType > 0) { bool Result = This->Disconnect((int) *winid,(int) *lastId,eventType, - (wxObjectEventFunction)(wxEventFunction) - &wxeEvtListener::forward, - NULL, Listener); + (wxObjectEventFunction)(wxEventFunction) + &wxeEvtListener::forward, + NULL, Listener); rt.addBool(Result); } else { rt.addAtom("badarg"); rt.addAtom("event_type"); - rt.addTupleCount(2); + rt.addTupleCount(2); } break; } diff --git a/lib/wx/api_gen/wx_extra/wxEvtHandler.erl b/lib/wx/api_gen/wx_extra/wxEvtHandler.erl index c5802af679..c9726fd475 100644 --- a/lib/wx/api_gen/wx_extra/wxEvtHandler.erl +++ b/lib/wx/api_gen/wx_extra/wxEvtHandler.erl @@ -27,15 +27,11 @@ -export([connect/2, connect/3, disconnect/1, disconnect/2, disconnect/3]). %% internal exports --export([connect_impl/3, disconnect_impl/2, disconnect_impl/3, - new_evt_listener/0, destroy_evt_listener/1, - get_callback/1, replace_fun_with_id/2]). +-export([connect_impl/2, disconnect_impl/2]). -export_type([wxEvtHandler/0, wx/0, event/0]). -type wxEvtHandler() :: wx:wx_object(). --record(evh, {et=null,id=?wxID_ANY,lastId=?wxID_ANY,skip=undefined,userdata=[],cb=0}). - %% @doc Equivalent to {@link connect/3. connect(This, EventType, [])} -spec connect(This::wxEvtHandler(), EventType::wxEventType()) -> ok. connect(This, EventType) -> @@ -130,54 +126,34 @@ disconnect(This=#wx_ref{type=ThisT,ref=_ThisRef}, EventType, Opts) -> %% @hidden -connect_impl(#wx_ref{type=wxeEvtListener,ref=EvtList}, - #wx_ref{type=ThisT,ref=ThisRef}, - #evh{id=Winid, lastId=LastId, et=EventType, - skip=Skip, userdata=Userdata, cb=FunID}) +connect_impl(#wx_ref{type=ThisT,ref=ThisRef}, + #evh{id=Winid, lastId=LastId, et=EventType, + skip=Skip, userdata=Userdata, cb=FunID}) when is_integer(FunID)-> EventTypeBin = list_to_binary([atom_to_list(EventType)|[0]]), ThisTypeBin = list_to_binary([atom_to_list(ThisT)|[0]]), UD = if Userdata =:= [] -> 0; - true -> + true -> wxe_util:send_bin(term_to_binary(Userdata)), 1 end, - wxe_util:call(100, <<EvtList:32/?UI,ThisRef:32/?UI, + wxe_util:call(100, <<ThisRef:32/?UI, Winid:32/?UI,LastId:32/?UI, (wxe_util:from_bool(Skip)):32/?UI, UD:32/?UI, FunID:32/?UI, (size(EventTypeBin)):32/?UI, - (size(ThisTypeBin)):32/?UI, + (size(ThisTypeBin)):32/?UI, %% Note no alignment EventTypeBin/binary,ThisTypeBin/binary>>). %% @hidden -disconnect_impl(Listener, Object) -> - disconnect_impl(Listener, Object, #evh{}). -%% @hidden -disconnect_impl(#wx_ref{type=wxeEvtListener,ref=EvtList}, - #wx_ref{type=_ThisT,ref=ThisRef}, - #evh{id=Winid, lastId=LastId, et=EventType}) -> +disconnect_impl(#wx_ref{type=_ThisT,ref=ThisRef}, + #evh{id=Winid, lastId=LastId, et=EventType, + handler=#wx_ref{type=wxeEvtListener,ref=EvtList}}) -> EventTypeBin = list_to_binary([atom_to_list(EventType)|[0]]), - wxe_util:call(101, <<EvtList:32/?UI, + wxe_util:call(101, <<EvtList:32/?UI, ThisRef:32/?UI,Winid:32/?UI,LastId:32/?UI, (size(EventTypeBin)):32/?UI, %% Note no alignment EventTypeBin/binary>>). - -%% @hidden -new_evt_listener() -> - wxe_util:call(98, <<>>). - -%% @hidden -destroy_evt_listener(#wx_ref{type=wxeEvtListener,ref=EvtList}) -> - wxe_util:call(99, <<EvtList:32/?UI>>). - -%% @hidden -get_callback(#evh{cb=Callback}) -> - Callback. - -%% @hidden -replace_fun_with_id(Evh, Id) -> - Evh#evh{cb=Id}. diff --git a/lib/wx/api_gen/wx_gen.erl b/lib/wx/api_gen/wx_gen.erl index 3ca8cd7d14..0f28b3dd5e 100644 --- a/lib/wx/api_gen/wx_gen.erl +++ b/lib/wx/api_gen/wx_gen.erl @@ -25,7 +25,7 @@ -include_lib("xmerl/include/xmerl.hrl"). --import(lists, [foldl/3,foldr/3,reverse/1, keysearch/3, map/2, filter/2]). +-import(lists, [foldl/3,foldr/3,reverse/1,keysearch/3,map/2,filter/2,droplast/1]). -import(proplists, [get_value/2,get_value/3]). -compile(export_all). @@ -69,7 +69,7 @@ gen_code() -> gen_xml() -> %% {ok, Defs} = file:consult("wxapi.conf"), -%% Rel = reverse(tl(reverse(os:cmd("wx-config --release")))), +%% Rel = droplast(os:cmd("wx-config --release")), %% Dir = " /usr/include/wx-" ++ Rel ++ "/wx/", %% Files0 = [Dir ++ File || {class, File, _, _, _} <- Defs], %% Files1 = [Dir ++ File || {doxygen, File} <- Defs], @@ -172,7 +172,7 @@ parse_defs([], Acc) -> reverse(Acc). meta_info(C=#class{name=CName,methods=Ms0}) -> Ms = lists:append(Ms0), HaveConstructor = lists:keymember(constructor, #method.method_type, Ms), - case lists:keysearch(destructor, #method.method_type, Ms) of + case keysearch(destructor, #method.method_type, Ms) of false when HaveConstructor -> Dest = #method{name = "destroy", id = next_id(func_id), method_type = destructor, params = [this(CName)]}, @@ -288,7 +288,7 @@ parse_attr1([{{attr,_}, #xmlElement{content=C, attributes=Attrs}}|R], AttrList0, parse_attr1([{_Id,_}|R],AttrList,Info, Res) -> parse_attr1(R,AttrList,Info, Res); parse_attr1([],Left,_, Res) -> - {lists:reverse(Res), Left}. + {reverse(Res), Left}. attr_acc(#param{name=N}, List) -> Name = list_to_atom(N), @@ -994,7 +994,7 @@ erl_skip_opt2([F={_,{N,In,_},M=#method{where=Where}}|Ms],Acc1,Acc2,Check) -> [] -> erl_skip_opt2(Ms,[F|Acc1],[M#method{where=erl_no_opt}|Acc2],[]); _ -> - Skipped = reverse(tl(reverse(In))), + Skipped = droplast(In), T = fun({_,{_,Args,_},_}) -> true =:= types_differ(Skipped,Args) end, case lists:all(T, Check) of true -> diff --git a/lib/wx/api_gen/wx_gen_cpp.erl b/lib/wx/api_gen/wx_gen_cpp.erl index 5ac57e4929..ea5d89be72 100644 --- a/lib/wx/api_gen/wx_gen_cpp.erl +++ b/lib/wx/api_gen/wx_gen_cpp.erl @@ -1190,15 +1190,6 @@ find_id(OtherClass) -> encode_events(Evs) -> ?WTC("encode_events"), - w("void wxeEvtListener::forward(wxEvent& event)~n" - "{~n" - "#ifdef DEBUG~n" - " if(!sendevent(&event, port))~n" - " fprintf(stderr, \"Couldn't send event!\\r\\n\");~n" - "#else~n" - "sendevent(&event, port);~n" - "#endif~n" - "}~n~n"), w("int getRef(void* ptr, wxeMemEnv* memenv)~n" "{~n" " WxeApp * app = (WxeApp *) wxTheApp;~n" @@ -1209,7 +1200,7 @@ encode_events(Evs) -> " char * evClass = NULL;~n" " wxMBConvUTF32 UTFconverter;~n" " wxeEtype *Etype = etmap[event->GetEventType()];~n" - " wxeCallbackData *cb = (wxeCallbackData *)event->m_callbackUserData;~n" + " wxeEvtListener *cb = (wxeEvtListener *)event->m_callbackUserData;~n" " WxeApp * app = (WxeApp *) wxTheApp;~n" " wxeMemEnv *memenv = app->getMemEnv(port);~n" " if(!memenv) return 0;~n~n" diff --git a/lib/wx/api_gen/wx_gen_erl.erl b/lib/wx/api_gen/wx_gen_erl.erl index c87187edc5..a4b03d3fd1 100644 --- a/lib/wx/api_gen/wx_gen_erl.erl +++ b/lib/wx/api_gen/wx_gen_erl.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -137,15 +137,27 @@ gen_class1(C=#class{name=Name,parent=Parent,methods=Ms,options=Opts}) -> w("%% inherited exports~n",[]), Done0 = ["Destroy", "New", "Create", "destroy", "new", "create"], Done = gb_sets:from_list(Done0 ++ [M|| #method{name=M} <- lists:append(Ms)]), - {_, InExported} = gen_inherited(Parents, Done, []), - w("-export([~s]).~n~n", [args(fun(EF) -> EF end, ",", - lists:usort(["parent_class/1"|InExported]), + {_, InExported0} = gen_inherited(Parents, Done, []), + InExported = lists:ukeysort(2, [{?MODULE,{"parent_class","1"},false}|InExported0]), + w("-export([~s]).~n~n", [args(fun({_M,{F,A},_Dep}) -> F ++ "/" ++ A end, ",", + InExported, 60)]), w("-export_type([~s/0]).~n", [Name]), case lists:filter(fun({_F,Depr}) -> Depr end, ExportList) of [] -> ok; Depr -> w("-deprecated([~s]).~n~n", [args(fun({EF,_}) -> EF end, ",", Depr, 60)]) end, + case lists:filter(fun({_,_,Depr}) -> Depr end, InExported) of + [] -> ok; + NoWDepr -> w("-compile([~s]).~n~n", + [args(fun({M,{F,A},_}) -> + DStr=io_lib:format("{nowarn_deprecated_function, {~s,~s,~s}}", + [M,F,A]), + lists:flatten(DStr) + end, ",", NoWDepr, 60)]) + end, + + w("%% @hidden~n", []), parents_check(Parents), w("-type ~s() :: wx:wx_object().~n", [Name]), @@ -375,7 +387,7 @@ gen_inherited([Parent|Ps], Done0, Exported0) -> {Done,Exported} = gen_inherited_ms(Ms, Class, Done0, gb_sets:empty(), Exported0), gen_inherited(Ps, gb_sets:union(Done,Done0), Exported). -gen_inherited_ms([[#method{name=Name,alias=A,params=Ps0,where=W,method_type=MT}|_]|R], +gen_inherited_ms([[M=#method{name=Name,alias=A,params=Ps0,where=W,method_type=MT}|_]|R], Class,Skip,Done, Exported) when W =/= merged_c -> case gb_sets:is_member(Name,Skip) of @@ -399,8 +411,10 @@ gen_inherited_ms([[#method{name=Name,alias=A,params=Ps0,where=W,method_type=MT}| _ when W =:= erl_no_opt -> 0; _ -> 1 end, - Export = erl_func_name(Name,A) ++ "/" ++ integer_to_list(length(Args) + OptLen), - gen_inherited_ms(R,Class,Skip, gb_sets:add(Name,Done), [Export|Exported]); + {_, Depr} = deprecated(M,ignore), + Export = {Class,{erl_func_name(Name,A),integer_to_list(length(Args) + OptLen)}, Depr}, + gen_inherited_ms(R,Class,Skip, gb_sets:add(Name,Done), + [Export|Exported]); _ -> gen_inherited_ms(R,Class, Skip, Done, Exported) end; diff --git a/lib/wx/c_src/gen/wxe_events.cpp b/lib/wx/c_src/gen/wxe_events.cpp index a1b4d090b3..1bd17366a2 100644 --- a/lib/wx/c_src/gen/wxe_events.cpp +++ b/lib/wx/c_src/gen/wxe_events.cpp @@ -316,16 +316,6 @@ void initEventTable() } } -void wxeEvtListener::forward(wxEvent& event) -{ -#ifdef DEBUG - if(!sendevent(&event, port)) - fprintf(stderr, "Couldn't send event!\r\n"); -#else -sendevent(&event, port); -#endif -} - int getRef(void* ptr, wxeMemEnv* memenv) { WxeApp * app = (WxeApp *) wxTheApp; @@ -338,7 +328,7 @@ bool sendevent(wxEvent *event, ErlDrvTermData port) char * evClass = NULL; wxMBConvUTF32 UTFconverter; wxeEtype *Etype = etmap[event->GetEventType()]; - wxeCallbackData *cb = (wxeCallbackData *)event->m_callbackUserData; + wxeEvtListener *cb = (wxeEvtListener *)event->m_callbackUserData; WxeApp * app = (WxeApp *) wxTheApp; wxeMemEnv *memenv = app->getMemEnv(port); if(!memenv) return 0; diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp index 82dd414911..3f5cb4c0f5 100644 --- a/lib/wx/c_src/gen/wxe_funcs.cpp +++ b/lib/wx/c_src/gen/wxe_funcs.cpp @@ -67,20 +67,8 @@ void WxeApp::wxe_dispatch(wxeCommand& Ecmd) case WXE_INIT_OPENGL: wxe_initOpenGL(rt, bp); break; -case 98: { // wxeEvtListener::wxeEvtListener - wxeEvtListener *Result = new wxeEvtListener(Ecmd.port); - rt.addRef(getRef((void *)Result,memenv), "wxeEvtListener"); - break; -} -case 99: { // wxeEvtListener::destroy - wxObject *This = (wxObject *) getPtr(bp,memenv); - rt.addAtom("ok"); - delete This; - break; -} -case 100: { // wxEvtHandler::Connect - wxeEvtListener *Listener = (wxeEvtListener *) getPtr(bp,memenv); bp += 4; +case 100: { // wxEvtHandler::Connect wxEvtHandler *This = (wxEvtHandler *) getPtr(bp, memenv); bp += 4; int * winid = (int *) bp; bp += 4; int * lastId = (int *) bp; bp += 4; @@ -91,20 +79,22 @@ case 100: { // wxEvtHandler::Connect int * eventTypeLen = (int *) bp; bp += 4; int * class_nameLen = (int *) bp; bp += 4; - if(*haveUserData) { + if(*haveUserData) { userData = new wxeErlTerm(Ecmd.bin[0]); } int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; char *class_name = bp; bp+= *class_nameLen; if(eventType > 0 ) { - wxeCallbackData * Evt_cb = new wxeCallbackData(Ecmd.caller,getRef(This, memenv), - class_name,*fun_cb, - *skip, userData, Listener); + wxeEvtListener * Evt_cb = new wxeEvtListener(Ecmd.caller,getRef(This, memenv), + class_name,*fun_cb, + *skip, userData, Ecmd.port); This->Connect((int) *winid,(int) *lastId,eventType, (wxObjectEventFunction)(wxEventFunction) &wxeEvtListener::forward, - Evt_cb, Listener); + Evt_cb, Evt_cb); rt.addAtom("ok"); + rt.addRef(getRef((void *)Evt_cb,memenv), "wxeEvtListener"); + rt.addTupleCount(2); } else { rt.addAtom("badarg"); rt.addAtom("event_type"); @@ -112,7 +102,7 @@ case 100: { // wxEvtHandler::Connect } break; } -case 101: { // wxEvtHandler::Disconnect +case 101: { // wxEvtHandler::Disconnect wxeEvtListener *Listener = (wxeEvtListener *) getPtr(bp,memenv); bp += 4; wxEvtHandler *This = (wxEvtHandler *) getPtr(bp, memenv); bp += 4; int * winid = (int *) bp; bp += 4; @@ -122,14 +112,14 @@ case 101: { // wxEvtHandler::Disconnect int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; if(eventType > 0) { bool Result = This->Disconnect((int) *winid,(int) *lastId,eventType, - (wxObjectEventFunction)(wxEventFunction) - &wxeEvtListener::forward, - NULL, Listener); + (wxObjectEventFunction)(wxEventFunction) + &wxeEvtListener::forward, + NULL, Listener); rt.addBool(Result); } else { rt.addAtom("badarg"); rt.addAtom("event_type"); - rt.addTupleCount(2); + rt.addTupleCount(2); } break; } diff --git a/lib/wx/c_src/wxe_callback_impl.cpp b/lib/wx/c_src/wxe_callback_impl.cpp index 8ba2557d82..e06f68dcbf 100644 --- a/lib/wx/c_src/wxe_callback_impl.cpp +++ b/lib/wx/c_src/wxe_callback_impl.cpp @@ -30,27 +30,27 @@ * CallbackData * * ****************************************************************************/ -wxeCallbackData::wxeCallbackData(ErlDrvTermData caller, int req, char *req_type, - int funcb, int skip_ev, wxeErlTerm * userData, - wxeEvtListener *handler_cb) - : wxObject() +wxeEvtListener::wxeEvtListener(ErlDrvTermData caller, int req, char *req_type, + int funcb, int skip_ev, wxeErlTerm * userData, + ErlDrvTermData from_port) + : wxEvtHandler() { + port=from_port; listener = caller; obj = req; fun_id = funcb; strcpy(class_name, req_type); skip = skip_ev; user_data = userData; - handler = handler_cb; } -wxeCallbackData::~wxeCallbackData() { - // fprintf(stderr, "CBD Deleteing %p %s\r\n", this, class_name); fflush(stderr); +wxeEvtListener::~wxeEvtListener() { + // fprintf(stderr, "CBD Deleteing %p %s\r\n", this, class_name); fflush(stderr); if(user_data) { delete user_data; } ptrMap::iterator it; - it = ((WxeApp *)wxTheApp)->ptr2ref.find(handler); + it = ((WxeApp *)wxTheApp)->ptr2ref.find(this); if(it != ((WxeApp *)wxTheApp)->ptr2ref.end()) { wxeRefData *refd = it->second; wxeReturn rt = wxeReturn(WXE_DRV_PORT, refd->memenv->owner, false); @@ -61,6 +61,17 @@ wxeCallbackData::~wxeCallbackData() { rt.addTupleCount(4); rt.send(); } + ((WxeApp *)wxTheApp)->clearPtr(this); +} + +void wxeEvtListener::forward(wxEvent& event) +{ +#ifdef DEBUG + if(!sendevent(&event, port)) + fprintf(stderr, "Couldn't send event!\r\n"); +#else +sendevent(&event, port); +#endif } /* *****************************************************************/ diff --git a/lib/wx/c_src/wxe_callback_impl.h b/lib/wx/c_src/wxe_callback_impl.h index c7243e23a4..1c355e4d38 100644 --- a/lib/wx/c_src/wxe_callback_impl.h +++ b/lib/wx/c_src/wxe_callback_impl.h @@ -23,6 +23,24 @@ void pre_callback(); void handle_event_callback(ErlDrvPort port, ErlDrvTermData process); +/* Fun Callback id */ +class wxeEvtListener : public wxEvtHandler +{ +public: + wxeEvtListener(ErlDrvTermData caller, int req, char *req_type, + int funcb, int skip_ev, wxeErlTerm * userData, + ErlDrvTermData Thisport); + ~wxeEvtListener(); + void forward(wxEvent& event); + ErlDrvTermData port; + ErlDrvTermData listener; + int fun_id; + int obj; + char class_name[40]; + int skip; + wxeErlTerm * user_data; +}; + class wxEPrintout : public wxPrintout { public: diff --git a/lib/wx/c_src/wxe_events.h b/lib/wx/c_src/wxe_events.h index 22a737d854..93b5551123 100644 --- a/lib/wx/c_src/wxe_events.h +++ b/lib/wx/c_src/wxe_events.h @@ -32,38 +32,7 @@ public: int cID; }; -/* One EvtListener per listening erlang process */ -/* If callbacks are used the receiver is wxe_master process */ -/* and a wxeEvtListener pre callback is registered */ -class wxeEvtListener : public wxEvtHandler -{ - public: - wxeEvtListener(ErlDrvTermData Thisport) : port(Thisport) - {} - // {fprintf(stderr, "Creating %x\r\n", (unsigned int) this); fflush(stderr);} - ~wxeEvtListener() {} - void forward(wxEvent& event); - ErlDrvTermData port; -}; - void initEventTable(); int wxeEventTypeFromAtom(char *etype_atom); -/* Fun Callback id */ -class wxeCallbackData : public wxObject -{ -public: - wxeCallbackData(ErlDrvTermData caller, int req, char *req_type, - int funcb, int skip_ev, wxeErlTerm * userData, - wxeEvtListener *handler_cb); - ~wxeCallbackData(); - wxeEvtListener * handler; - ErlDrvTermData listener; - int fun_id; - int obj; - char class_name[40]; - int skip; - wxeErlTerm * user_data; -}; - #endif diff --git a/lib/wx/src/gen/wxBufferedDC.erl b/lib/wx/src/gen/wxBufferedDC.erl index c69a426d7f..e2504bbaaa 100644 --- a/lib/wx/src/gen/wxBufferedDC.erl +++ b/lib/wx/src/gen/wxBufferedDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -54,6 +54,8 @@ setUserScale/3,startDoc/2,startPage/1]). -export_type([wxBufferedDC/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxMemoryDC) -> true; parent_class(wxDC) -> true; diff --git a/lib/wx/src/gen/wxBufferedPaintDC.erl b/lib/wx/src/gen/wxBufferedPaintDC.erl index 0e11826da0..c3fa80703c 100644 --- a/lib/wx/src/gen/wxBufferedPaintDC.erl +++ b/lib/wx/src/gen/wxBufferedPaintDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -56,6 +56,8 @@ startPage/1]). -export_type([wxBufferedPaintDC/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxBufferedDC) -> true; parent_class(wxMemoryDC) -> true; diff --git a/lib/wx/src/gen/wxClientDC.erl b/lib/wx/src/gen/wxClientDC.erl index 45909859ce..ae16196774 100644 --- a/lib/wx/src/gen/wxClientDC.erl +++ b/lib/wx/src/gen/wxClientDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -56,6 +56,8 @@ -export_type([wxClientDC/0]). -deprecated([new/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxWindowDC) -> true; parent_class(wxDC) -> true; diff --git a/lib/wx/src/gen/wxEvtHandler.erl b/lib/wx/src/gen/wxEvtHandler.erl index 22c203392c..44b7254cfb 100644 --- a/lib/wx/src/gen/wxEvtHandler.erl +++ b/lib/wx/src/gen/wxEvtHandler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -46,15 +46,11 @@ -export([connect/2, connect/3, disconnect/1, disconnect/2, disconnect/3]). %% internal exports --export([connect_impl/3, disconnect_impl/2, disconnect_impl/3, - new_evt_listener/0, destroy_evt_listener/1, - get_callback/1, replace_fun_with_id/2]). +-export([connect_impl/2, disconnect_impl/2]). -export_type([wxEvtHandler/0, wx/0, event/0]). -type wxEvtHandler() :: wx:wx_object(). --record(evh, {et=null,id=?wxID_ANY,lastId=?wxID_ANY,skip=undefined,userdata=[],cb=0}). - %% @doc Equivalent to {@link connect/3. connect(This, EventType, [])} -spec connect(This::wxEvtHandler(), EventType::wxEventType()) -> ok. connect(This, EventType) -> @@ -149,55 +145,35 @@ disconnect(This=#wx_ref{type=ThisT,ref=_ThisRef}, EventType, Opts) -> %% @hidden -connect_impl(#wx_ref{type=wxeEvtListener,ref=EvtList}, - #wx_ref{type=ThisT,ref=ThisRef}, - #evh{id=Winid, lastId=LastId, et=EventType, - skip=Skip, userdata=Userdata, cb=FunID}) +connect_impl(#wx_ref{type=ThisT,ref=ThisRef}, + #evh{id=Winid, lastId=LastId, et=EventType, + skip=Skip, userdata=Userdata, cb=FunID}) when is_integer(FunID)-> EventTypeBin = list_to_binary([atom_to_list(EventType)|[0]]), ThisTypeBin = list_to_binary([atom_to_list(ThisT)|[0]]), UD = if Userdata =:= [] -> 0; - true -> + true -> wxe_util:send_bin(term_to_binary(Userdata)), 1 end, - wxe_util:call(100, <<EvtList:32/?UI,ThisRef:32/?UI, + wxe_util:call(100, <<ThisRef:32/?UI, Winid:32/?UI,LastId:32/?UI, (wxe_util:from_bool(Skip)):32/?UI, UD:32/?UI, FunID:32/?UI, (size(EventTypeBin)):32/?UI, - (size(ThisTypeBin)):32/?UI, + (size(ThisTypeBin)):32/?UI, %% Note no alignment EventTypeBin/binary,ThisTypeBin/binary>>). %% @hidden -disconnect_impl(Listener, Object) -> - disconnect_impl(Listener, Object, #evh{}). -%% @hidden -disconnect_impl(#wx_ref{type=wxeEvtListener,ref=EvtList}, - #wx_ref{type=_ThisT,ref=ThisRef}, - #evh{id=Winid, lastId=LastId, et=EventType}) -> +disconnect_impl(#wx_ref{type=_ThisT,ref=ThisRef}, + #evh{id=Winid, lastId=LastId, et=EventType, + handler=#wx_ref{type=wxeEvtListener,ref=EvtList}}) -> EventTypeBin = list_to_binary([atom_to_list(EventType)|[0]]), - wxe_util:call(101, <<EvtList:32/?UI, + wxe_util:call(101, <<EvtList:32/?UI, ThisRef:32/?UI,Winid:32/?UI,LastId:32/?UI, (size(EventTypeBin)):32/?UI, %% Note no alignment EventTypeBin/binary>>). -%% @hidden -new_evt_listener() -> - wxe_util:call(98, <<>>). - -%% @hidden -destroy_evt_listener(#wx_ref{type=wxeEvtListener,ref=EvtList}) -> - wxe_util:call(99, <<EvtList:32/?UI>>). - -%% @hidden -get_callback(#evh{cb=Callback}) -> - Callback. - -%% @hidden -replace_fun_with_id(Evh, Id) -> - Evh#evh{cb=Id}. - diff --git a/lib/wx/src/gen/wxGridCellBoolEditor.erl b/lib/wx/src/gen/wxGridCellBoolEditor.erl index bf7e21d11d..c4d6d92618 100644 --- a/lib/wx/src/gen/wxGridCellBoolEditor.erl +++ b/lib/wx/src/gen/wxGridCellBoolEditor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2013. All Rights Reserved. +%% Copyright Ericsson AB 2009-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -34,6 +34,8 @@ parent_class/1,reset/1,setSize/2,show/2,show/3,startingClick/1,startingKey/2]). -export_type([wxGridCellBoolEditor/0]). +-compile([{nowarn_deprecated_function, {wxGridCellEditor,endEdit,4}},{nowarn_deprecated_function, {wxGridCellEditor,paintBackground,3}}]). + %% @hidden parent_class(wxGridCellEditor) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxGridCellChoiceEditor.erl b/lib/wx/src/gen/wxGridCellChoiceEditor.erl index 08c5f9e147..a49680ffda 100644 --- a/lib/wx/src/gen/wxGridCellChoiceEditor.erl +++ b/lib/wx/src/gen/wxGridCellChoiceEditor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2013. All Rights Reserved. +%% Copyright Ericsson AB 2009-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -34,6 +34,8 @@ parent_class/1,reset/1,setSize/2,show/2,show/3,startingClick/1,startingKey/2]). -export_type([wxGridCellChoiceEditor/0]). +-compile([{nowarn_deprecated_function, {wxGridCellEditor,endEdit,4}},{nowarn_deprecated_function, {wxGridCellEditor,paintBackground,3}}]). + %% @hidden parent_class(wxGridCellEditor) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxGridCellFloatEditor.erl b/lib/wx/src/gen/wxGridCellFloatEditor.erl index 4b6b3b46e1..5cd0bd6cb5 100644 --- a/lib/wx/src/gen/wxGridCellFloatEditor.erl +++ b/lib/wx/src/gen/wxGridCellFloatEditor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2013. All Rights Reserved. +%% Copyright Ericsson AB 2009-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -34,6 +34,8 @@ parent_class/1,reset/1,setSize/2,show/2,show/3,startingClick/1,startingKey/2]). -export_type([wxGridCellFloatEditor/0]). +-compile([{nowarn_deprecated_function, {wxGridCellEditor,endEdit,4}},{nowarn_deprecated_function, {wxGridCellEditor,paintBackground,3}}]). + %% @hidden parent_class(wxGridCellEditor) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxGridCellNumberEditor.erl b/lib/wx/src/gen/wxGridCellNumberEditor.erl index 04214be6b8..7cc682a10e 100644 --- a/lib/wx/src/gen/wxGridCellNumberEditor.erl +++ b/lib/wx/src/gen/wxGridCellNumberEditor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2013. All Rights Reserved. +%% Copyright Ericsson AB 2009-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -35,6 +35,8 @@ parent_class/1,reset/1,setSize/2,show/2,show/3,startingClick/1,startingKey/2]). -export_type([wxGridCellNumberEditor/0]). +-compile([{nowarn_deprecated_function, {wxGridCellEditor,endEdit,4}},{nowarn_deprecated_function, {wxGridCellEditor,paintBackground,3}}]). + %% @hidden parent_class(wxGridCellTextEditor) -> true; parent_class(wxGridCellEditor) -> true; diff --git a/lib/wx/src/gen/wxGridCellTextEditor.erl b/lib/wx/src/gen/wxGridCellTextEditor.erl index 5755be8638..a024da56c4 100644 --- a/lib/wx/src/gen/wxGridCellTextEditor.erl +++ b/lib/wx/src/gen/wxGridCellTextEditor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2013. All Rights Reserved. +%% Copyright Ericsson AB 2009-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -34,6 +34,8 @@ parent_class/1,reset/1,setSize/2,show/2,show/3,startingClick/1,startingKey/2]). -export_type([wxGridCellTextEditor/0]). +-compile([{nowarn_deprecated_function, {wxGridCellEditor,endEdit,4}},{nowarn_deprecated_function, {wxGridCellEditor,paintBackground,3}}]). + %% @hidden parent_class(wxGridCellEditor) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxMemoryDC.erl b/lib/wx/src/gen/wxMemoryDC.erl index c123f0e35d..8de412bdc7 100644 --- a/lib/wx/src/gen/wxMemoryDC.erl +++ b/lib/wx/src/gen/wxMemoryDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -53,6 +53,8 @@ startPage/1]). -export_type([wxMemoryDC/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxDC) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxMirrorDC.erl b/lib/wx/src/gen/wxMirrorDC.erl index cfae34cb36..9a681bff2e 100644 --- a/lib/wx/src/gen/wxMirrorDC.erl +++ b/lib/wx/src/gen/wxMirrorDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -53,6 +53,8 @@ startPage/1]). -export_type([wxMirrorDC/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxDC) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxPaintDC.erl b/lib/wx/src/gen/wxPaintDC.erl index 3c9b321496..0ff27a8c7a 100644 --- a/lib/wx/src/gen/wxPaintDC.erl +++ b/lib/wx/src/gen/wxPaintDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -56,6 +56,8 @@ -export_type([wxPaintDC/0]). -deprecated([new/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxWindowDC) -> true; parent_class(wxDC) -> true; diff --git a/lib/wx/src/gen/wxPostScriptDC.erl b/lib/wx/src/gen/wxPostScriptDC.erl index e0b22c87eb..e7e498efa1 100644 --- a/lib/wx/src/gen/wxPostScriptDC.erl +++ b/lib/wx/src/gen/wxPostScriptDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -55,6 +55,8 @@ -export_type([wxPostScriptDC/0]). -deprecated([getResolution/0,setResolution/1]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxDC) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxScreenDC.erl b/lib/wx/src/gen/wxScreenDC.erl index 54bdc2d351..21ca4bacfc 100644 --- a/lib/wx/src/gen/wxScreenDC.erl +++ b/lib/wx/src/gen/wxScreenDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -53,6 +53,8 @@ startPage/1]). -export_type([wxScreenDC/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxDC) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/gen/wxWindowDC.erl b/lib/wx/src/gen/wxWindowDC.erl index f5c482d8ca..6bb303cfe6 100644 --- a/lib/wx/src/gen/wxWindowDC.erl +++ b/lib/wx/src/gen/wxWindowDC.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -55,6 +55,8 @@ -export_type([wxWindowDC/0]). -deprecated([new/0]). +-compile([{nowarn_deprecated_function, {wxDC,computeScaleAndOrigin,1}}]). + %% @hidden parent_class(wxDC) -> true; parent_class(_Class) -> erlang:error({badtype, ?MODULE}). diff --git a/lib/wx/src/wxe.hrl b/lib/wx/src/wxe.hrl index bd34b13385..66ec9ac45e 100644 --- a/lib/wx/src/wxe.hrl +++ b/lib/wx/src/wxe.hrl @@ -29,6 +29,11 @@ -record(wx_mem, {bin, size}). +-record(evh, {et=null,id=-1,lastId=-1,cb=0, + skip=undefined,userdata=[], % temp + handler=undefined % added after connect + }). + -define(CLASS(Type,Class), ((Type) =:= Class) orelse (Type):parent_class(Class)). -define(CLASS_T(Type,Class), diff --git a/lib/wx/src/wxe_server.erl b/lib/wx/src/wxe_server.erl index 04e0d0faf4..aed9dca7ce 100644 --- a/lib/wx/src/wxe_server.erl +++ b/lib/wx/src/wxe_server.erl @@ -36,8 +36,8 @@ terminate/2, code_change/3]). -record(state, {port,cb_port,users,cleaners=[],cb,cb_cnt}). --record(user, {events=[], evt_handler}). --record(event, {object, callback, cb_handler}). +-record(user, {events=[]}). +%%-record(event, {object, callback, cb_handler}). -define(APPLICATION, wxe). -define(log(S,A), log(?MODULE,?LINE,S,A)). @@ -119,7 +119,7 @@ handle_call(stop,{_From,_},State = #state{users=Users0, cleaners=Cs0}) -> Env = get(?WXE_IDENTIFIER), Users = gb_trees:to_list(Users0), Cs = lists:map(fun({_Pid,User}) -> - spawn_link(fun() -> cleanup(Env,[User], false) end) + spawn_link(fun() -> cleanup(Env,[User]) end) end, Users), {noreply, State#state{users=gb_trees:empty(), cleaners=Cs ++ Cs0}}; @@ -178,13 +178,13 @@ handle_info({'DOWN',_,process,Pid,_}, State=#state{users=Users0,cleaners=Cs}) -> Users = gb_trees:delete(Pid,Users0), Env = wx:get_env(), case User of - #user{events=[], evt_handler=undefined} -> %% No need to spawn + #user{events=[]} -> %% No need to spawn case Cs =:= [] andalso gb_trees:is_empty(Users) of - true -> {stop, normal, State#state{cleaners=Cs}}; - false -> {noreply, State#state{users=Users,cleaners=Cs}} + true -> {stop, normal, State#state{users=Users}}; + false -> {noreply, State#state{users=Users}} end; _ -> - Cleaner = spawn_link(fun() -> cleanup(Env,[User],true) end), + Cleaner = spawn_link(fun() -> cleanup(Env,[User]) end), {noreply, State#state{users=Users,cleaners=[Cleaner|Cs]}} end catch _E:_R -> @@ -222,36 +222,26 @@ code_change(_OldVsn, State, _Extra) -> log(Mod,Line,Str,Args) -> error_logger:format("~p:~p: " ++ Str, [Mod,Line|Args]). -handle_connect(Object, EvData, From, State0 = #state{users=Users}) -> - User0 = #user{events=Evs0,evt_handler=Handler0} = gb_trees:get(From, Users), - Callback = wxEvtHandler:get_callback(EvData), - case Handler0 of - #wx_ref{} when Callback =:= 0 -> - CBHandler = Handler0, - Handler = Handler0; - undefined when Callback =:= 0 -> - Handler = new_evt_listener(State0, From), - CBHandler = Handler; - _ -> - CBHandler = new_evt_listener(State0, From), - Handler = Handler0 - end, - Evs = [#event{object=Object,callback=Callback, cb_handler=CBHandler}|Evs0], - User = User0#user{events=Evs, evt_handler=Handler}, - State1 = State0#state{users=gb_trees:update(From, User, Users)}, - if is_function(Callback) orelse is_pid(Callback) -> - {FunId, State} = attach_fun(Callback,State1), - Res = wxEvtHandler:connect_impl(CBHandler,Object, - wxEvtHandler:replace_fun_with_id(EvData,FunId)), - case Res of - ok -> {reply,Res,State}; - _Error -> {reply,Res,State0} - end; - - true -> - Res = {call_impl, connect_cb, CBHandler}, - {reply, Res, State1} - end. +handle_connect(Object, #evh{handler=undefined, cb=Callback} = EvData0, + From, State0) -> + %% Callback let this process listen to the events + {FunId, State} = attach_fun(Callback,State0), + EvData1 = EvData0#evh{cb=FunId}, + case wxEvtHandler:connect_impl(Object,EvData1) of + {ok, Handler} -> + EvData = EvData1#evh{handler=Handler,userdata=undefined}, + handle_connect(Object, EvData, From, State); + Error -> + {reply, Error, State0} + end; +handle_connect(Object, EvData=#evh{handler=Handler}, + From, State0 = #state{users=Users}) -> + %% Correct process is already listening just register it + put(Handler, From), + User0 = #user{events=Listeners0} = gb_trees:get(From, Users), + User = User0#user{events=[{Object,EvData}|Listeners0]}, + State = State0#state{users=gb_trees:update(From, User, Users)}, + {reply, ok, State}. invoke_cb({{Ev=#wx{}, Ref=#wx_ref{}}, FunId,_}, _S) -> %% Event callbacks @@ -329,20 +319,6 @@ get_wx_object_state(Pid) -> _ -> ignore end. -new_evt_listener(State, Owner) -> - #wx_env{port=Port} = wx:get_env(), - _ = erlang:port_control(Port,98,<<>>), - Listener = get_result(State), - put(Listener, Owner), - Listener. - -get_result(_State) -> - receive - {'_wxe_result_', Res} -> Res; - {'_wxe_error_', Op, Error} -> - erlang:error({Error, {wxEvtHandler, {internal_installer, Op}}}) - end. - attach_fun(Fun, S = #state{cb=CB,cb_cnt=Next}) -> case gb_trees:lookup(Fun,CB) of {value, ID} -> @@ -367,90 +343,72 @@ delete_fun(FunId, State = #state{cb=CB}) -> State end. -cleanup_evt_listener(U=#user{events=Evs0,evt_handler=Handler}, EvtListener, Object) -> - {Evs, Del} = lists:foldl(fun(#event{object=Obj,cb_handler=CBH}, {Acc, Delete}) - when CBH =:= EvtListener, Obj =:= Object -> - {Acc, Delete}; - (Event = #event{cb_handler=CBH}, {Acc, _Delete}) - when CBH =:= EvtListener -> - {[Event|Acc], false}; - (Event, {Acc, Delete}) -> - {[Event|Acc], Delete} - end, {[], true}, Evs0), - Del andalso wxEvtHandler:destroy_evt_listener(EvtListener), - case Del andalso Handler =:= EvtListener of - true -> - U#user{events=Evs, evt_handler=undefined}; - false -> - U#user{events=Evs} - end. +cleanup_evt_listener(U=#user{events=Evs0}, EvtListener, Object) -> + Filter = fun({Obj,#evh{handler=Evl}}) -> + not (Object =:= Obj andalso Evl =:= EvtListener) + end, + U#user{events=lists:filter(Filter, Evs0)}. -handle_disconnect(Object, Evh, From, State0 = #state{users=Users0}) -> +handle_disconnect(Object, Evh = #evh{cb=Fun}, From, + State0 = #state{users=Users0, cb=Callbacks}) -> #user{events=Evs0} = gb_trees:get(From, Users0), - Fun = wxEvtHandler:get_callback(Evh), - case find_handler(Evs0, Object, Fun) of - [] -> {reply, false, State0}; + FunId = gb_trees:lookup(Fun, Callbacks), + case find_handler(Evs0, Object, Evh#evh{cb=FunId}) of + [] -> + {reply, false, State0}; Handlers -> - case disconnect(Object,Evh, Handlers) of - #event{} -> - {reply, true, State0}; - Result -> - {reply, Result, State0} + case disconnect(Object,Handlers) of + #evh{} -> {reply, true, State0}; + Result -> {reply, Result, State0} end end. -disconnect(Object,Evh,[Ev=#event{cb_handler=Handler}|Evs]) -> - try wxEvtHandler:disconnect_impl(Handler,Object,Evh) of +disconnect(Object,[Ev|Evs]) -> + try wxEvtHandler:disconnect_impl(Object,Ev) of true -> Ev; - false -> disconnect(Object, Evh, Evs); + false -> disconnect(Object, Evs); Error -> Error catch _:_ -> false end; -disconnect(_, _, []) -> false. - -find_handler(Evs, Object, Fun) -> - find_handler(Evs, Object, Fun, []). - -find_handler([Ev =#event{object=Object,callback=FunReg}|Evs],Object,Search,Acc) -> - case FunReg =:= Search of - true -> find_handler(Evs,Object,Search,[Ev|Acc]); - false when is_function(FunReg), Search =:= 0 -> - find_handler(Evs,Object,Search,[Ev|Acc]); - _ -> - find_handler(Evs,Object,Search,Acc) +disconnect(_, []) -> false. + +find_handler([{Object,Evh}|Evs], Object, Match) -> + case match_handler(Match, Evh) of + false -> find_handler(Evs, Object, Match); + Res -> [Res|find_handler(Evs,Object,Match)] end; -find_handler([_|Evs],Object,Fun,Res) -> - find_handler(Evs,Object,Fun,Res); -find_handler([],_Object,_Fun,Res) -> - Res. +find_handler([_|Evs], Object, Match) -> + find_handler(Evs, Object, Match); +find_handler([], _, _) -> []. + +match_handler(M=#evh{et=MET, cb=MCB}, + #evh{et=ET, cb=CB, handler=Handler}) -> + %% Let wxWidgets handle the id matching + Match = match_et(MET, ET) + andalso match_cb(MCB, CB), + Match andalso M#evh{handler=Handler}. +match_et(null, _) -> true; +match_et(Met, Et) -> Met =:= Et. + +match_cb(none, _) -> true; +match_cb({value,MId}, Id) -> MId =:= Id. %% Cleanup %% The server handles callbacks from driver so every other wx call must %% be called from another process, therefore the cleaning must be spawned. %% -%% Using Disconnect when we terminate can crash, it is timing releated -%% but it seems that disconnect on windows that are being deleted are bad. -%% Since we are terminating the data will be cleaned up anyway. -cleanup(Env, Data, Disconnect) -> +cleanup(Env, Data) -> put(?WXE_IDENTIFIER, Env), - lists:foreach(fun(User) -> cleanup(User, Disconnect) end, Data), + Disconnect = fun({Object, Ev}) -> + try wxEvtHandler:disconnect_impl(Object,Ev) + catch _:_ -> ok + end + end, + + lists:foreach(fun(#user{events=Evs}) -> + [Disconnect(Ev) || Ev <- Evs] + end, Data), gen_server:cast(Env#wx_env.sv, {cleaned, self()}), normal. - -cleanup(#user{events=Evs, evt_handler=Handler}, Disconnect) -> - lists:foreach(fun(#event{object=O, callback=CB, cb_handler=CbH}) -> - Disconnect andalso (catch wxEvtHandler:disconnect_impl(CbH,O)), - case is_function(CB) of - true -> - wxEvtHandler:destroy_evt_listener(CbH); - false -> - ignore - end - end, Evs), - case Handler of - undefined -> ignore; - _ -> wxEvtHandler:destroy_evt_listener(Handler) - end, - ok. diff --git a/lib/wx/src/wxe_util.erl b/lib/wx/src/wxe_util.erl index a31c3e30b8..02ac4ddfa6 100644 --- a/lib/wx/src/wxe_util.erl +++ b/lib/wx/src/wxe_util.erl @@ -74,7 +74,7 @@ call(Op, Args) -> true -> debug_call(Dbg band 15, Op, Args, Port) end. - + rec(Op) -> receive {'_wxe_result_', Res} -> Res; @@ -108,21 +108,26 @@ send_bin(Bin) when is_binary(Bin) -> get_cbId(Fun) -> gen_server:call((wx:get_env())#wx_env.sv,{register_cb, Fun}, infinity). -connect_cb(Object,EvData) -> - handle_listener(connect_cb, Object, EvData). - -disconnect_cb(Object,EvData) -> - handle_listener(disconnect_cb, Object, EvData). - -handle_listener(Op,Object,EvData) -> - Listener = gen_server:call((wx:get_env())#wx_env.sv, {Op,Object,EvData}, infinity), - case Listener of - {call_impl, connect_cb, EvtList} -> - wxEvtHandler:connect_impl(EvtList,Object,EvData); - Res -> - Res +connect_cb(Object,EvData0 = #evh{cb=Callback}) -> + Server = (wx:get_env())#wx_env.sv, + case Callback of + 0 -> %% Message api connect from this process + case wxEvtHandler:connect_impl(Object,EvData0) of + {ok, Listener} -> + EvData = EvData0#evh{handler=Listener, userdata=undefined}, + gen_server:call(Server, {connect_cb,Object,EvData}, infinity); + Error -> + Error + end; + _ -> %% callback, fun or pid (pid for wx_object:sync_events masked callbacks) + %% let the server do the connect + gen_server:call(Server, {connect_cb,Object,EvData0}, infinity) end. +disconnect_cb(Object,EvData) -> + Server = (wx:get_env())#wx_env.sv, + gen_server:call(Server, {disconnect_cb,Object,EvData}, infinity). + debug_cast(1, Op, _Args, _Port) -> check_previous(), case ets:lookup(wx_debug_info,Op) of diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl index 79dbea0575..7bdbd4594c 100644 --- a/lib/wx/test/wx_basic_SUITE.erl +++ b/lib/wx/test/wx_basic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -98,8 +98,23 @@ several_apps(Config) -> Pids = [spawn_link(fun() -> several_apps(Parent, N, Config) end) || N <- lists:seq(1,4)], process_flag(trap_exit,true), - ?m_multi_receive([{complete,Pid} || Pid <- Pids]), + Wait = fun(Pid,Acc) -> + receive {complete, Pid} -> Acc + after 20000 -> [Pid|Acc] + end + end, + Res = lists:foldl(Wait, [], Pids), [Pid ! quit || Pid <- Pids], + + Dbg = fun(Pid) -> + io:format("Stack ~p~n",[erlang:process_info(Pid, current_stacktrace)]), + io:format("Stack ~p~n",[erlang:process_info(Pid)]) + end, + case Res of + [] -> ok; + Failed -> + [Dbg(Pid)|| Pid <- Failed] + end, case wx_test_lib:user_available(Config) of true -> receive {'EXIT',_,foo} -> ok end; @@ -216,7 +231,7 @@ create_menus(Frame) -> %% Test the wx_misc.erl api functionality. wx_misc(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); -wx_misc(Config) -> +wx_misc(_Config) -> wx:new([{debug, trace}]), put(wx_test_verbose, true), ?m(ok, wx_misc:bell()), diff --git a/lib/wx/test/wx_event_SUITE.erl b/lib/wx/test/wx_event_SUITE.erl index b9c2fafe0e..076f16ba16 100644 --- a/lib/wx/test/wx_event_SUITE.erl +++ b/lib/wx/test/wx_event_SUITE.erl @@ -149,7 +149,7 @@ disconnect(Config) -> timer:sleep(1000), wx_test_lib:flush(), - ?m({'EXIT', {{badarg,_},_}}, wxEvtHandler:disconnect(Panel, non_existing_event_type)), + ?m(false, wxEvtHandler:disconnect(Panel, non_existing_event_type)), ?m(true, wxEvtHandler:disconnect(Panel, size)), ?m(ok, wxWindow:setSize(Panel, {200,102})), timer:sleep(1000), @@ -499,14 +499,14 @@ callback_clean(Config) -> end end), timer:sleep(500), %% Give it time to remove it - ?m({[{Pid,_,_}],[_],[_]}, white_box_check_event_handlers()), + ?m({[{Pid,_}],[_],[_]}, white_box_check_event_handlers()), Pid ! remove, timer:sleep(500), %% Give it time to remove it ?m({[],[],[]}, white_box_check_event_handlers()), SetupEventHandlers(), - ?m({[{_,_,_}],[_],[_]}, white_box_check_event_handlers()), + ?m({[{_,_}],[_],[_]}, white_box_check_event_handlers()), wxDialog:show(Dlg), wx_test_lib:wx_close(Dlg, Config), @@ -526,9 +526,9 @@ white_box_check_event_handlers() -> {status, _, _, [Env, _, _, _, Data]} = sys:get_status(Server), [_H, _data, {data, [{_, Record}]}] = Data, {state, _Port1, _Port2, Users, [], CBs, _Next} = Record, - {[{Pid, Evs, Listener} || - {Pid, {user, Evs, Listener}} <- gb_trees:to_list(Users), - (Evs =/= [] orelse Listener =/= undefined)], %% Ignore empty + {[{Pid, Evs} || + {Pid, {user, Evs}} <- gb_trees:to_list(Users), + Evs =/= []], %% Ignore empty gb_trees:to_list(CBs), [Funs || Funs = {Id, {Fun,_}} <- Env, is_integer(Id), is_function(Fun)] }. @@ -2,7 +2,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2002-2012. All Rights Reserved. +# Copyright Ericsson AB 2002-2014. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -52,6 +52,9 @@ usage () echo " release [-a] <target_dir> - creates full release to <target_dir>" echo " smp [-a] - build an Erlang system, smp flavor only" echo " tests <dir> - Build testsuites to <dir>" + echo " patch_app <target_dir> <app1>... - build given apps to <target_dir>" + echo " If core apps are patched, new start scripts will be created" + echo " and 'Install' must be run again." echo "" echo "These are for cleaning up an open source distribution" echo "with prebuilt files, so that it resembles the clean developers" @@ -1191,6 +1194,125 @@ do_release () release || exit 1 } +do_patch_app () +{ + # If target dir exists and has an installation of same major release, then + # build given apps. + # If patch includes erts, kernel, stdlib, sasl, then find latest + # erts, kernel, stdlib, sasl and create .rel files. + # Create .script/.boot + + if [ $# -lt 2 ]; then + usage + exit 1 + fi + + setup_make + if [ X`$MAKE is_cross_configured` = Xyes ]; then + TARGET=`$MAKE target_configured` + fi + target_dir=$1 + if [ ! -d $target_dir/releases/$otp_major_vsn ]; then + echo "No OTP $otp_major_vsn installation in $target_dir" 1>&2 + exit 1 + fi + shift + + # Build all applications to target + for app in "$@"; do + if [ "$app" = "erts" ] && [ -d $ERL_TOP/$app ]; then + (cd $ERL_TOP/$app && $MAKE MAKE="$MAKE" TARGET=$TARGET \ + TESTROOT=$target_dir release) || exit 1 + elif [ "$app" != "erts" ] && [ -d $ERL_TOP/lib/$app ]; then + (cd $ERL_TOP/lib/$app && $MAKE MAKE="$MAKE" TARGET=$TARGET \ + TESTROOT=$target_dir release) || exit 1 + else + echo "Invalid application $app" 1>&2 + exit 1 + fi + done + + # If erts, kernel, stdlib or sasl is included, find versions + for app in "$@"; do + if [ "$app" = "erts" ]; then + erts_vsn=`grep '^VSN' erts/vsn.mk | sed "s|^VSN.*=[^0-9]*\([0-9].*\)$|\1|g"` + update_rel=true + elif [ "$app" = "kernel" ]; then + kernel_vsn=`sed "s|^KERNEL_VSN[^=]*=[^0-9]*\([0-9].*\)$|\1|g" lib/kernel/vsn.mk` + update_rel=true + elif [ "$app" = "stdlib" ]; then + stdlib_vsn=`sed "s|^STDLIB_VSN[^=]*=[^0-9]*\([0-9].*\)$|\1|g" lib/stdlib/vsn.mk` + update_rel=true + elif [ "$app" = "sasl" ]; then + sasl_vsn=`sed "s|^SASL_VSN[^=]*=[^0-9]*\([0-9].*\)$|\1|g" lib/sasl/vsn.mk` + update_rel=true + fi + done + + # and find the old versions for those not included + if [ "X$update_rel" != "X" ]; then + if [ "X$erts_vsn" = "X" ]; then + erts_vsns=`ls -d $target_dir/erts-* | sed "s|$target_dir/erts-\([0-9\.].*\)|\1|g"` + erts_vsn=`echo "$erts_vsns" | sort -t '.' -g | tail -n 1` + fi + if [ "X$kernel_vsn" = "X" ]; then + kernel_vsns=`ls -d $target_dir/lib/kernel-* | sed "s|$target_dir/lib/kernel-\([0-9\.].*\)|\1|g"` + kernel_vsn=`echo "$kernel_vsns" | sort -t '.' -g | tail -n 1` + fi + if [ "X$stdlib_vsn" = "X" ]; then + stdlib_vsns=`ls -d $target_dir/lib/stdlib-* | sed "s|$target_dir/lib/stdlib-\([0-9\.].*\)|\1|g"` + stdlib_vsn=`echo "$stdlib_vsns" | sort -t '.' -g | tail -n 1` + fi + if [ "X$sasl_vsn" = "X" ]; then + sasl_vsns=`ls -d $target_dir/lib/sasl-* | sed "s|$target_dir/lib/sasl-\([0-9\.].*\)|\1|g"` + sasl_vsn=`echo "$sasl_vsns" | sort -t '.' -g | tail -n 1` + fi + + # Generate .rel, .script and .boot - to tmp dir + start_clean="{release, {\"OTP APN 181 01\",\"$otp_major_vsn\"}, {erts, \"$erts_vsn\"},\n [{kernel,\"$kernel_vsn\"},\n {stdlib,\"$stdlib_vsn\"}]}.\n" + start_sasl="{release, {\"OTP APN 181 01\",\"$otp_major_vsn\"}, {erts, \"$erts_vsn\"},\n [{kernel,\"$kernel_vsn\"},\n {stdlib,\"$stdlib_vsn\"},\n {sasl,\"$sasl_vsn\"}]}.\n" + + tmp_dir=$target_dir/tmp; + if [ ! -d $tmp_dir ]; then + mkdir $tmp_dir + fi + echo $start_sasl > $tmp_dir/start_sasl.rel + echo $start_clean > $tmp_dir/start_clean.rel + echo $start_clean > $tmp_dir/no_dot_erlang.rel + + erlc=$ERL_TOP/bootstrap/bin/erlc + if [ ! -x $erlc ]; then + echo "erlc not found, can not create .script and .boot files" 1>&2 + exit 1 + fi + + $erlc -I$target_dir/lib/*/ebin -o$tmp_dir $tmp_dir/start_sasl.rel || exit 1 + $erlc -I$target_dir/lib/*/ebin -o$tmp_dir +no_warn_sasl $tmp_dir/start_clean.rel || exit 1 + $erlc -I$target_dir/lib/*/ebin -o$tmp_dir +no_warn_sasl +no_dot_erlang $tmp_dir/no_dot_erlang.rel || exit 1 + + # Generate RELEASES file + erl=$ERL_TOP/bootstrap/bin/erl + if [ ! -x $erl ]; then + echo "erl not found, can not create RELEASES file" 1>&2 + exit 1 + fi + $erl -noinput +B -eval "release_handler:create_RELEASES(\"%ERL_ROOT%\", \"$tmp_dir\", \"$tmp_dir/start_sasl.rel\", []), halt()" || exit 1 + + # If all good so far, move generated files into target area + mv $tmp_dir/RELEASES $target_dir/releases/RELEASES.src + mv $tmp_dir/* $target_dir/releases/$otp_major_vsn + rmdir $tmp_dir + + # Remove old start scripts (forces a new run of Install) + rm -f $target_dir/releases/RELEASES + rm -f $target_dir/bin/*.script + rm -f $target_dir/bin/*.boot + rm -f $target_dir/bin/erl + fi + +} + + do_tests () { setup_make @@ -1451,6 +1573,9 @@ case "$1" in shift fi; do_release "$2";; + patch_app) + shift; + do_patch_app "$@";; tests) if [ $minus_a_flag = true ]; then shift diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml index 635476737d..71aec732cf 100644 --- a/system/doc/reference_manual/typespec.xml +++ b/system/doc/reference_manual/typespec.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>2003</year><year>2013</year> + <year>2003</year><year>2014</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -118,15 +118,15 @@ | fun((TList) -> Type) Integer :: integer() - | Erlang_Integer %% ..., -1, 0, 1, ... 42 ... - | Erlang_Integer..Erlang_Integer %% specifies an integer range + | Erlang_Integer %% ..., -1, 0, 1, ... 42 ... + | Erlang_Integer..Erlang_Integer %% specifies an integer range - List :: list(Type) %% Proper list ([]-terminated) - | improper_list(Type1, Type2) %% Type1=contents, Type2=termination - | maybe_improper_list(Type1, Type2) %% Type1 and Type2 as above - | nonempty_list(Type) %% Proper non-empty list + List :: list(Type) %% Proper list ([]-terminated) + | maybe_improper_list(Type1, Type2) %% Type1=contents, Type2=termination + | nonempty_improper_list(Type1, Type2) %% Type1 and Type2 as above + | nonempty_list(Type) %% Proper non-empty list - Tuple :: tuple() %% stands for a tuple of any size + Tuple :: tuple() %% stands for a tuple of any size | {} | {TList} |