diff options
68 files changed, 1468 insertions, 726 deletions
diff --git a/Makefile.in b/Makefile.in index 761d37ac02..b3327b5c65 100644 --- a/Makefile.in +++ b/Makefile.in @@ -121,7 +121,7 @@ BINDIR = $(DESTDIR)$(EXTRA_PREFIX)$(bindir) # # Erlang base public files # -ERL_BASE_PUB_FILES=erl erlc epmd run_erl to_erl dialyzer typer escript run_test +ERL_BASE_PUB_FILES=erl erlc epmd run_erl to_erl dialyzer typer escript ct_run run_test # ERLANG_INST_LIBDIR is the top directory where the Erlang installation # will be located when running. diff --git a/bootstrap/lib/compiler/ebin/beam_disasm.beam b/bootstrap/lib/compiler/ebin/beam_disasm.beam Binary files differindex f90b5c9ae7..ec48b89de7 100644 --- a/bootstrap/lib/compiler/ebin/beam_disasm.beam +++ b/bootstrap/lib/compiler/ebin/beam_disasm.beam diff --git a/bootstrap/lib/compiler/ebin/sys_core_fold.beam b/bootstrap/lib/compiler/ebin/sys_core_fold.beam Binary files differindex 046a358910..c878868598 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 a053c54295..12026564d9 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 ba92ff6fb0..84c005edc9 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 0c2e54032a..cd4bf48540 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_life.beam b/bootstrap/lib/compiler/ebin/v3_life.beam Binary files differindex ec8fcda8ee..aa8b6b227b 100644 --- a/bootstrap/lib/compiler/ebin/v3_life.beam +++ b/bootstrap/lib/compiler/ebin/v3_life.beam diff --git a/bootstrap/lib/kernel/ebin/auth.beam b/bootstrap/lib/kernel/ebin/auth.beam Binary files differindex 3796221d6b..33f1ab875b 100644 --- a/bootstrap/lib/kernel/ebin/auth.beam +++ b/bootstrap/lib/kernel/ebin/auth.beam diff --git a/bootstrap/lib/stdlib/ebin/digraph_utils.beam b/bootstrap/lib/stdlib/ebin/digraph_utils.beam Binary files differindex a578c67e00..39cb9d90da 100644 --- a/bootstrap/lib/stdlib/ebin/digraph_utils.beam +++ b/bootstrap/lib/stdlib/ebin/digraph_utils.beam diff --git a/bootstrap/lib/stdlib/ebin/gen.beam b/bootstrap/lib/stdlib/ebin/gen.beam Binary files differindex bd08292a87..0a39dc1c7e 100644 --- a/bootstrap/lib/stdlib/ebin/gen.beam +++ b/bootstrap/lib/stdlib/ebin/gen.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_event.beam b/bootstrap/lib/stdlib/ebin/gen_event.beam Binary files differindex c381a17787..23c4f53c30 100644 --- a/bootstrap/lib/stdlib/ebin/gen_event.beam +++ b/bootstrap/lib/stdlib/ebin/gen_event.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_fsm.beam b/bootstrap/lib/stdlib/ebin/gen_fsm.beam Binary files differindex 99410275e2..c2713544ff 100644 --- a/bootstrap/lib/stdlib/ebin/gen_fsm.beam +++ b/bootstrap/lib/stdlib/ebin/gen_fsm.beam diff --git a/bootstrap/lib/stdlib/ebin/gen_server.beam b/bootstrap/lib/stdlib/ebin/gen_server.beam Binary files differindex 7698131678..3fa7d89db4 100644 --- a/bootstrap/lib/stdlib/ebin/gen_server.beam +++ b/bootstrap/lib/stdlib/ebin/gen_server.beam diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam Binary files differindex 98c8f6d42e..837027e93a 100644 --- a/bootstrap/lib/stdlib/ebin/supervisor.beam +++ b/bootstrap/lib/stdlib/ebin/supervisor.beam diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 2bd7297231..279844adb2 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -546,12 +546,17 @@ $(TTF_DIR)/driver_tab.c: Makefile.in LANG=C $(PERL) utils/make_driver_tab -o $@ $(DRV_OBJS) GENERATE += $(TTF_DIR)/driver_tab.c + + # Preloaded code. # # This list must be consistent with PRE_LOADED_MODULES in # lib/kernel/src/Makefile. ifeq ($(TARGET),win32) -$(TARGET)/beams.rc: $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ +# On windows the preloaded objects are in a resource object. +PRELOAD_OBJ = $(OBJDIR)/beams.$(RES_EXT) +PRELOAD_SRC = $(TARGET)/beams.rc +$(PRELOAD_SRC): $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ $(ERL_TOP)/erts/preloaded/ebin/init.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_file.beam \ @@ -560,9 +565,10 @@ $(TARGET)/beams.rc: $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ $(ERL_TOP)/erts/preloaded/ebin/erl_prim_loader.beam \ $(ERL_TOP)/erts/preloaded/ebin/erlang.beam LANG=C $(PERL) utils/make_preload $(MAKE_PRELOAD_EXTRA) -rc $^ > $@ -GENERATE += $(TARGET)/beams.rc else -$(TARGET)/preload.c: $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ +PRELOAD_OBJ = $(OBJDIR)/preload.o +PRELOAD_SRC = $(TARGET)/preload.c +$(PRELOAD_SRC): $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ $(ERL_TOP)/erts/preloaded/ebin/init.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_file.beam \ @@ -571,7 +577,6 @@ $(TARGET)/preload.c: $(ERL_TOP)/erts/preloaded/ebin/otp_ring0.beam \ $(ERL_TOP)/erts/preloaded/ebin/erl_prim_loader.beam \ $(ERL_TOP)/erts/preloaded/ebin/erlang.beam LANG=C $(PERL) utils/make_preload -old $^ > $@ -GENERATE += $(TARGET)/preload.c endif .PHONY : generate @@ -579,7 +584,8 @@ ifdef VOID_EMULATOR generate: @echo $(VOID_EMULATOR)' - omitted target generate' else -generate: $(TTF_DIR)/GENERATED +generate: $(TTF_DIR)/GENERATED $(PRELOAD_SRC) + $(TTF_DIR)/GENERATED: $(GENERATE) echo $? >$(TTF_DIR)/GENERATED endif @@ -660,7 +666,7 @@ endif # CS_SRC = sys/$(ERLANG_OSTYPE)/erl_child_setup.c -$(BINDIR)/$(CS_EXECUTABLE): $(TTF_DIR)/GENERATED $(CS_SRC) $(ERTS_LIB) +$(BINDIR)/$(CS_EXECUTABLE): $(TTF_DIR)/GENERATED $(PRELOAD_SRC) $(CS_SRC) $(ERTS_LIB) $(CS_PURIFY) $(CC) $(CS_LDFLAGS) -o $(BINDIR)/$(CS_EXECUTABLE) \ $(CS_CFLAGS) $(COMMON_INCLUDES) $(CS_SRC) $(CS_LIBS) @@ -689,16 +695,7 @@ $(ERL_TOP)/lib/%.beam: # Object files # -# On windows the preloaded objects are in a resource object. - -ifeq ($(TARGET),win32) -PRELOAD = $(OBJDIR)/beams.$(RES_EXT) -else -PRELOAD = $(OBJDIR)/preload.o -endif - - -INIT_OBJS = $(OBJDIR)/erl_main.o $(PRELOAD) +INIT_OBJS = $(OBJDIR)/erl_main.o $(PRELOAD_OBJ) EMU_OBJS = \ $(OBJDIR)/beam_emu.o $(OBJDIR)/beam_opcodes.o \ @@ -1035,7 +1032,7 @@ depend: @echo $(VOID_EMULATOR)' - omitted target depend' else depend: $(TTF_DIR)/depend.mk -$(TTF_DIR)/depend.mk: $(TTF_DIR)/GENERATED +$(TTF_DIR)/depend.mk: $(TTF_DIR)/GENERATED $(PRELOAD_SRC) $(DEP_CC) $(DEP_FLAGS) $(BEAM_SRC) \ | $(SED_DEPEND) > $(TTF_DIR)/depend.mk $(DEP_CC) $(DEP_FLAGS) -DLIBSCTP=$(LIBSCTP) $(DRV_COMMON_SRC) \ diff --git a/erts/emulator/beam/erl_thr_progress.h b/erts/emulator/beam/erl_thr_progress.h index 1bbc49993e..a71724b813 100644 --- a/erts/emulator/beam/erl_thr_progress.h +++ b/erts/emulator/beam/erl_thr_progress.h @@ -138,6 +138,7 @@ ERTS_GLB_INLINE ErtsThrPrgrVal erts_thr_prgr_read_acqb__(ERTS_THR_PRGR_ATOMIC *a ERTS_GLB_INLINE ErtsThrPrgrVal erts_thr_prgr_read_mb__(ERTS_THR_PRGR_ATOMIC *atmc); ERTS_GLB_INLINE int erts_thr_progress_is_managed_thread(void); +ERTS_GLB_INLINE ErtsThrPrgrVal erts_thr_progress_current_to_later__(ErtsThrPrgrVal val); ERTS_GLB_INLINE ErtsThrPrgrVal erts_thr_progress_later_than(ErtsThrPrgrVal val); ERTS_GLB_INLINE ErtsThrPrgrVal erts_thr_progress_later(void); ERTS_GLB_INLINE ErtsThrPrgrVal erts_thr_progress_current(void); @@ -218,9 +219,8 @@ erts_thr_progress_is_managed_thread(void) } ERTS_GLB_INLINE ErtsThrPrgrVal -erts_thr_progress_later_than(ErtsThrPrgrVal val) +erts_thr_progress_current_to_later__(ErtsThrPrgrVal val) { - ERTS_THR_MEMORY_BARRIER; if (val == (ERTS_THR_PRGR_VAL_WAITING-((ErtsThrPrgrVal)2))) return ((ErtsThrPrgrVal) 0); else if (val == (ERTS_THR_PRGR_VAL_WAITING-((ErtsThrPrgrVal)1))) @@ -230,10 +230,17 @@ erts_thr_progress_later_than(ErtsThrPrgrVal val) } ERTS_GLB_INLINE ErtsThrPrgrVal +erts_thr_progress_later_than(ErtsThrPrgrVal val) +{ + ERTS_THR_MEMORY_BARRIER; + return erts_thr_progress_current_to_later__(val); +} + +ERTS_GLB_INLINE ErtsThrPrgrVal erts_thr_progress_later(void) { ErtsThrPrgrVal val = erts_thr_prgr_read_mb__(&erts_thr_prgr__.current); - return erts_thr_progress_later_than(val); + return erts_thr_progress_current_to_later__(val); } ERTS_GLB_INLINE ErtsThrPrgrVal diff --git a/erts/lib_src/common/ethr_atomics.c b/erts/lib_src/common/ethr_atomics.c index d093873841..c52166a7ec 100644 --- a/erts/lib_src/common/ethr_atomics.c +++ b/erts/lib_src/common/ethr_atomics.c @@ -563,7 +563,9 @@ int ethr_dw_atomic_cmpxchg(ethr_dw_atomic_t *var, ethr_dw_sint_t *val, ethr_dw_s int ethr_dw_atomic_cmpxchg_ddrb(ethr_dw_atomic_t *var, ethr_dw_sint_t *val, ethr_dw_sint_t *old_val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_DOUBLE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_dw_atomic_cmpxchg_ddrb__(var, val, old_val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_dw_atomic_cmpxchg(var, val, old_val); #else return ethr_dw_atomic_cmpxchg_rb(var, val, old_val); @@ -786,7 +788,9 @@ void ethr_dw_atomic_set(ethr_dw_atomic_t *var, ethr_dw_sint_t *val) void ethr_dw_atomic_set_ddrb(ethr_dw_atomic_t *var, ethr_dw_sint_t *val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_DOUBLE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_dw_atomic_set_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_dw_atomic_set(var, val); #else ethr_dw_atomic_set_rb(var, val); @@ -949,7 +953,9 @@ void ethr_dw_atomic_read(ethr_dw_atomic_t *var, ethr_dw_sint_t *val) void ethr_dw_atomic_read_ddrb(ethr_dw_atomic_t *var, ethr_dw_sint_t *val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_DOUBLE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_dw_atomic_read_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_dw_atomic_read(var, val); #else ethr_dw_atomic_read_rb(var, val); @@ -1109,7 +1115,9 @@ void ethr_dw_atomic_init(ethr_dw_atomic_t *var, ethr_dw_sint_t *val) void ethr_dw_atomic_init_ddrb(ethr_dw_atomic_t *var, ethr_dw_sint_t *val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_DOUBLE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_dw_atomic_init_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_dw_atomic_init(var, val); #else ethr_dw_atomic_init_rb(var, val); @@ -1278,7 +1286,9 @@ ethr_sint_t ethr_atomic_cmpxchg(ethr_atomic_t *var, ethr_sint_t val, ethr_sint_t ethr_sint_t ethr_atomic_cmpxchg_ddrb(ethr_atomic_t *var, ethr_sint_t val, ethr_sint_t old_val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_cmpxchg_ddrb__(var, val, old_val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_cmpxchg(var, val, old_val); #else return ethr_atomic_cmpxchg_rb(var, val, old_val); @@ -1398,7 +1408,9 @@ ethr_sint_t ethr_atomic_xchg(ethr_atomic_t *var, ethr_sint_t val) ethr_sint_t ethr_atomic_xchg_ddrb(ethr_atomic_t *var, ethr_sint_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_xchg_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_xchg(var, val); #else return ethr_atomic_xchg_rb(var, val); @@ -1512,7 +1524,9 @@ void ethr_atomic_set(ethr_atomic_t *var, ethr_sint_t val) void ethr_atomic_set_ddrb(ethr_atomic_t *var, ethr_sint_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_atomic_set_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic_set(var, val); #else ethr_atomic_set_rb(var, val); @@ -1620,7 +1634,9 @@ void ethr_atomic_init(ethr_atomic_t *var, ethr_sint_t val) void ethr_atomic_init_ddrb(ethr_atomic_t *var, ethr_sint_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_atomic_init_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic_init(var, val); #else ethr_atomic_init_rb(var, val); @@ -1725,7 +1741,9 @@ ethr_sint_t ethr_atomic_add_read(ethr_atomic_t *var, ethr_sint_t val) ethr_sint_t ethr_atomic_add_read_ddrb(ethr_atomic_t *var, ethr_sint_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_add_read_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_add_read(var, val); #else return ethr_atomic_add_read_rb(var, val); @@ -1840,7 +1858,9 @@ ethr_sint_t ethr_atomic_read(ethr_atomic_t *var) ethr_sint_t ethr_atomic_read_ddrb(ethr_atomic_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_read_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_read(var); #else return ethr_atomic_read_rb(var); @@ -1954,7 +1974,9 @@ ethr_sint_t ethr_atomic_inc_read(ethr_atomic_t *var) ethr_sint_t ethr_atomic_inc_read_ddrb(ethr_atomic_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_inc_read_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_inc_read(var); #else return ethr_atomic_inc_read_rb(var); @@ -2069,7 +2091,9 @@ ethr_sint_t ethr_atomic_dec_read(ethr_atomic_t *var) ethr_sint_t ethr_atomic_dec_read_ddrb(ethr_atomic_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_dec_read_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_dec_read(var); #else return ethr_atomic_dec_read_rb(var); @@ -2183,7 +2207,9 @@ void ethr_atomic_add(ethr_atomic_t *var, ethr_sint_t val) void ethr_atomic_add_ddrb(ethr_atomic_t *var, ethr_sint_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_atomic_add_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic_add(var, val); #else ethr_atomic_add_rb(var, val); @@ -2292,7 +2318,9 @@ void ethr_atomic_inc(ethr_atomic_t *var) void ethr_atomic_inc_ddrb(ethr_atomic_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_atomic_inc_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic_inc(var); #else ethr_atomic_inc_rb(var); @@ -2401,7 +2429,9 @@ void ethr_atomic_dec(ethr_atomic_t *var) void ethr_atomic_dec_ddrb(ethr_atomic_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + ethr_atomic_dec_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic_dec(var); #else ethr_atomic_dec_rb(var); @@ -2511,7 +2541,9 @@ ethr_sint_t ethr_atomic_read_band(ethr_atomic_t *var, ethr_sint_t val) ethr_sint_t ethr_atomic_read_band_ddrb(ethr_atomic_t *var, ethr_sint_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_read_band_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_read_band(var, val); #else return ethr_atomic_read_band_rb(var, val); @@ -2626,7 +2658,9 @@ ethr_sint_t ethr_atomic_read_bor(ethr_atomic_t *var, ethr_sint_t val) ethr_sint_t ethr_atomic_read_bor_ddrb(ethr_atomic_t *var, ethr_sint_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_WORD_SZ_NATIVE_ATOMIC_OPS) + return ethr_atomic_read_bor_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic_read_bor(var, val); #else return ethr_atomic_read_bor_rb(var, val); @@ -2761,7 +2795,9 @@ ethr_sint32_t ethr_atomic32_cmpxchg(ethr_atomic32_t *var, ethr_sint32_t val, eth ethr_sint32_t ethr_atomic32_cmpxchg_ddrb(ethr_atomic32_t *var, ethr_sint32_t val, ethr_sint32_t old_val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_cmpxchg_ddrb__(var, val, old_val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_cmpxchg(var, val, old_val); #else return ethr_atomic32_cmpxchg_rb(var, val, old_val); @@ -2858,7 +2894,9 @@ ethr_sint32_t ethr_atomic32_xchg(ethr_atomic32_t *var, ethr_sint32_t val) ethr_sint32_t ethr_atomic32_xchg_ddrb(ethr_atomic32_t *var, ethr_sint32_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_xchg_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_xchg(var, val); #else return ethr_atomic32_xchg_rb(var, val); @@ -2954,7 +2992,9 @@ void ethr_atomic32_set(ethr_atomic32_t *var, ethr_sint32_t val) void ethr_atomic32_set_ddrb(ethr_atomic32_t *var, ethr_sint32_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + ethr_atomic32_set_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic32_set(var, val); #else ethr_atomic32_set_rb(var, val); @@ -3044,7 +3084,9 @@ void ethr_atomic32_init(ethr_atomic32_t *var, ethr_sint32_t val) void ethr_atomic32_init_ddrb(ethr_atomic32_t *var, ethr_sint32_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + ethr_atomic32_init_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic32_init(var, val); #else ethr_atomic32_init_rb(var, val); @@ -3131,7 +3173,9 @@ ethr_sint32_t ethr_atomic32_add_read(ethr_atomic32_t *var, ethr_sint32_t val) ethr_sint32_t ethr_atomic32_add_read_ddrb(ethr_atomic32_t *var, ethr_sint32_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_add_read_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_add_read(var, val); #else return ethr_atomic32_add_read_rb(var, val); @@ -3228,7 +3272,9 @@ ethr_sint32_t ethr_atomic32_read(ethr_atomic32_t *var) ethr_sint32_t ethr_atomic32_read_ddrb(ethr_atomic32_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_read_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_read(var); #else return ethr_atomic32_read_rb(var); @@ -3325,7 +3371,9 @@ ethr_sint32_t ethr_atomic32_inc_read(ethr_atomic32_t *var) ethr_sint32_t ethr_atomic32_inc_read_ddrb(ethr_atomic32_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_inc_read_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_inc_read(var); #else return ethr_atomic32_inc_read_rb(var); @@ -3422,7 +3470,9 @@ ethr_sint32_t ethr_atomic32_dec_read(ethr_atomic32_t *var) ethr_sint32_t ethr_atomic32_dec_read_ddrb(ethr_atomic32_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_dec_read_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_dec_read(var); #else return ethr_atomic32_dec_read_rb(var); @@ -3518,7 +3568,9 @@ void ethr_atomic32_add(ethr_atomic32_t *var, ethr_sint32_t val) void ethr_atomic32_add_ddrb(ethr_atomic32_t *var, ethr_sint32_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + ethr_atomic32_add_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic32_add(var, val); #else ethr_atomic32_add_rb(var, val); @@ -3609,7 +3661,9 @@ void ethr_atomic32_inc(ethr_atomic32_t *var) void ethr_atomic32_inc_ddrb(ethr_atomic32_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + ethr_atomic32_inc_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic32_inc(var); #else ethr_atomic32_inc_rb(var); @@ -3700,7 +3754,9 @@ void ethr_atomic32_dec(ethr_atomic32_t *var) void ethr_atomic32_dec_ddrb(ethr_atomic32_t *var) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + ethr_atomic32_dec_ddrb__(var); +#elif defined(ETHR_ORDERED_READ_DEPEND) ethr_atomic32_dec(var); #else ethr_atomic32_dec_rb(var); @@ -3792,7 +3848,9 @@ ethr_sint32_t ethr_atomic32_read_band(ethr_atomic32_t *var, ethr_sint32_t val) ethr_sint32_t ethr_atomic32_read_band_ddrb(ethr_atomic32_t *var, ethr_sint32_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_read_band_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_read_band(var, val); #else return ethr_atomic32_read_band_rb(var, val); @@ -3889,7 +3947,9 @@ ethr_sint32_t ethr_atomic32_read_bor(ethr_atomic32_t *var, ethr_sint32_t val) ethr_sint32_t ethr_atomic32_read_bor_ddrb(ethr_atomic32_t *var, ethr_sint32_t val) { -#ifdef ETHR_ORDERED_READ_DEPEND +#if defined(ETHR_HAVE_32BIT_NATIVE_ATOMIC_OPS) + return ethr_atomic32_read_bor_ddrb__(var, val); +#elif defined(ETHR_ORDERED_READ_DEPEND) return ethr_atomic32_read_bor(var, val); #else return ethr_atomic32_read_bor_rb(var, val); diff --git a/erts/lib_src/utils/make_atomics_api b/erts/lib_src/utils/make_atomics_api index 75e88f8a7e..74736c5a2d 100755 --- a/erts/lib_src/utils/make_atomics_api +++ b/erts/lib_src/utils/make_atomics_api @@ -805,7 +805,7 @@ rtchk_fallback_call(Return, #atomic_context{dw = DW, non_native_barrier(B) -> lists:member(B, ?NON_NATIVE_BARRIERS). -non_native_barrier_impl(AC, Type, Op, B) -> +non_native_barrier_impl(AC, inline_implementation = Type, Op, B) -> [" ", func_header(AC, Type, false, Op, B), " {", @@ -822,11 +822,36 @@ non_native_barrier_impl(AC, Type, Op, B) -> end, "} " + ]; +non_native_barrier_impl(#atomic_context{have_native_atomic_ops = HaveNative} = AC, + implementation = Type, + Op, + B) -> + [" +", func_header(AC, Type, false, Op, B), " +{", + case B of + ddrb -> + [" +#if defined(", HaveNative, ") + ", func_call(AC, Type, Op, B, true), " +#elif defined(ETHR_ORDERED_READ_DEPEND) + ", func_call(AC, symbol_implementation, Op, none, true), " +#else + ", func_call(AC, symbol_implementation, Op, rb, true), " +#endif +" + ] + end, + "} +" ]. func_call(#atomic_context{'ATMC' = ATMC} = AC, inline_implementation, Op, B, RetStatement) -> func_call(AC, Op, ["ETHR_", ATMC, "_FUNC__(", opstr(Op), op_barrier_ext(B), ")"], RetStatement); func_call(#atomic_context{atomic = Atomic} = AC, implementation, Op, B, RetStatement) -> + func_call(AC, Op, [Atomic, "_", opstr(Op), op_barrier_ext(B), "__"], RetStatement); +func_call(#atomic_context{atomic = Atomic} = AC, symbol_implementation, Op, B, RetStatement) -> func_call(AC, Op, [Atomic, "_", opstr(Op), op_barrier_ext(B)], RetStatement). func_call(#atomic_context{dw = DW, arg1 = Arg1, arg2 = Arg2, arg3 = Arg3} = AC, Op, Func, true) -> diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 0a77527b2f..63a8adbc63 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -64,7 +64,7 @@ print/1, print/2, print/3, pal/1, pal/2, pal/3, capture_start/0, capture_stop/0, capture_get/0, capture_get/1, - fail/1, fail/2, comment/1, comment/2, + fail/1, fail/2, comment/1, comment/2, make_priv_dir/0, testcases/2, userdata/2, userdata/3, timetrap/1, get_timetrap_info/0, sleep/1]). @@ -673,6 +673,15 @@ send_html_comment(Comment) -> ct_util:set_testdata({comment,Html}), test_server:comment(Html). +%%%----------------------------------------------------------------- +%%% @spec make_priv_dir() -> ok | {error,Reason} +%%% Reason = term() +%%% @doc If the test has been started with the create_priv_dir +%%% option set to manual_per_tc, in order for the test case to use +%%% the private directory, it must first create it by calling +%%% this function. +make_priv_dir() -> + test_server:make_priv_dir(). %%%----------------------------------------------------------------- %%% @spec get_target_name(Handle) -> {ok,TargetName} | {error,Reason} diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index b2f669fefe..0cd9b5f7cb 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -920,33 +920,48 @@ insert_dir(D,[D1|Ds]) -> insert_dir(D,[]) -> [D]. -make_last_run_index([Name|Rest], Result, TotSucc, TotFail, UserSkip, AutoSkip, - TotNotBuilt, Missing) -> - case last_test(Name) of +make_last_run_index([Name|Rest], Result, TotSucc, TotFail, + UserSkip, AutoSkip, TotNotBuilt, Missing) -> + case get_run_dirs(Name) of false -> %% Silently skip. - make_last_run_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip, - TotNotBuilt, Missing); - LastLogDir -> + make_last_run_index(Rest, Result, TotSucc, TotFail, + UserSkip, AutoSkip, TotNotBuilt, Missing); + LogDirs -> SuiteName = filename:rootname(filename:basename(Name)), - case make_one_index_entry(SuiteName, LastLogDir, "-", false, Missing) of - {Result1,Succ,Fail,USkip,ASkip,NotBuilt} -> - %% for backwards compatibility - AutoSkip1 = case catch AutoSkip+ASkip of - {'EXIT',_} -> undefined; - Res -> Res - end, - make_last_run_index(Rest, [Result|Result1], TotSucc+Succ, - TotFail+Fail, UserSkip+USkip, AutoSkip1, - TotNotBuilt+NotBuilt, Missing); - error -> - make_last_run_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip, - TotNotBuilt, Missing) - end + {Result1,TotSucc1,TotFail1,UserSkip1,AutoSkip1,TotNotBuilt1} = + make_last_run_index1(SuiteName, LogDirs, Result, + TotSucc, TotFail, + UserSkip, AutoSkip, + TotNotBuilt, Missing), + make_last_run_index(Rest, Result1, TotSucc1, TotFail1, + UserSkip1, AutoSkip1, + TotNotBuilt1, Missing) end; + make_last_run_index([], Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt, _) -> {ok, [Result|total_row(TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt, false)], {TotSucc,TotFail,UserSkip,AutoSkip,TotNotBuilt}}. + +make_last_run_index1(SuiteName, [LogDir | LogDirs], Result, TotSucc, TotFail, + UserSkip, AutoSkip, TotNotBuilt, Missing) -> + case make_one_index_entry(SuiteName, LogDir, "-", false, Missing) of + {Result1,Succ,Fail,USkip,ASkip,NotBuilt} -> + %% for backwards compatibility + AutoSkip1 = case catch AutoSkip+ASkip of + {'EXIT',_} -> undefined; + Res -> Res + end, + make_last_run_index1(SuiteName, LogDirs, [Result|Result1], TotSucc+Succ, + TotFail+Fail, UserSkip+USkip, AutoSkip1, + TotNotBuilt+NotBuilt, Missing); + error -> + make_last_run_index1(SuiteName, LogDirs, Result, TotSucc, TotFail, + UserSkip, AutoSkip, TotNotBuilt, Missing) + end; +make_last_run_index1(_, [], Result, TotSucc, TotFail, + UserSkip, AutoSkip, TotNotBuilt, _) -> + {Result,TotSucc,TotFail,UserSkip,AutoSkip,TotNotBuilt}. make_one_index_entry(SuiteName, LogDir, Label, All, Missing) -> case count_cases(LogDir) of @@ -1698,8 +1713,8 @@ make_all_suites_index(NewTestData = {_TestName,DirName}) -> sort_logdirs([Dir|Dirs],Groups) -> TestName = filename:rootname(filename:basename(Dir)), case filelib:wildcard(filename:join(Dir,"run.*")) of - [RunDir] -> - Groups1 = insert_test(TestName,{filename:basename(RunDir),RunDir},Groups), + RunDirs = [_|_] -> + Groups1 = sort_logdirs1(TestName,RunDirs,Groups), sort_logdirs(Dirs,Groups1); _ -> % ignore missing run directory sort_logdirs(Dirs,Groups) @@ -1707,6 +1722,12 @@ sort_logdirs([Dir|Dirs],Groups) -> sort_logdirs([],Groups) -> lists:keysort(1,sort_each_group(Groups)). +sort_logdirs1(TestName,[RunDir|RunDirs],Groups) -> + Groups1 = insert_test(TestName,{filename:basename(RunDir),RunDir},Groups), + sort_logdirs1(TestName,RunDirs,Groups1); +sort_logdirs1(_,[],Groups) -> + Groups. + insert_test(Test,IxDir,[{Test,IxDirs}|Groups]) -> [{Test,[IxDir|IxDirs]}|Groups]; insert_test(Test,IxDir,[]) -> @@ -1998,21 +2019,17 @@ notify_and_unlock_file(File) -> end. %%%----------------------------------------------------------------- -%%% @spec last_test(Dir) -> string() | false +%%% @spec get_run_dirs(Dir) -> [string()] | false %%% %%% @doc %%% -last_test(Dir) -> - last_test(filelib:wildcard(filename:join(Dir, "run.[1-2]*")), false). - -last_test([Run|Rest], false) -> - last_test(Rest, Run); -last_test([Run|Rest], Latest) when Run > Latest -> - last_test(Rest, Run); -last_test([_|Rest], Latest) -> - last_test(Rest, Latest); -last_test([], Latest) -> - Latest. +get_run_dirs(Dir) -> + case filelib:wildcard(filename:join(Dir, "run.[1-2]*")) of + [] -> + false; + RunDirs -> + lists:sort(RunDirs) + end. %%%----------------------------------------------------------------- %%% @spec xhtml(HTML, XHTML) -> HTML | XHTML diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl index be3c485b75..e6eb135ae8 100644 --- a/lib/common_test/src/ct_repeat.erl +++ b/lib/common_test/src/ct_repeat.erl @@ -116,7 +116,7 @@ spawn_tester(script,Ctrl,Args) -> spawn_tester(func,Ctrl,Opts) -> Tester = fun() -> - case catch ct_run:run_test1(Opts) of + case catch ct_run:run_test2(Opts) of {'EXIT',Reason} -> exit(Reason); Result -> diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 05b10bca32..666eb3c988 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -37,7 +37,7 @@ %% Misc internal functions --export([variables_file_name/1,script_start1/2,run_test1/1]). +-export([variables_file_name/1,script_start1/2,run_test2/1]). -include("ct_event.hrl"). -include("ct_util.hrl"). @@ -63,6 +63,7 @@ stylesheet, multiply_timetraps = 1, scale_timetraps = false, + create_priv_dir, testspecs = [], tests}). @@ -178,6 +179,10 @@ script_start1(Parent, Args) -> fun([CT]) -> list_to_atom(CT); ([]) -> true end, false, Args), + CreatePrivDir = get_start_opt(create_priv_dir, + fun([PD]) -> list_to_atom(PD); + ([]) -> auto_per_tc + end, Args), EvHandlers = event_handler_args2opts(Args), CTHooks = ct_hooks_args2opts(Args), EnableBuiltinHooks = get_start_opt(enable_builtin_hooks, @@ -255,7 +260,8 @@ script_start1(Parent, Args) -> silent_connections = SilentConns, stylesheet = Stylesheet, multiply_timetraps = MultTT, - scale_timetraps = ScaleTT}, + scale_timetraps = ScaleTT, + create_priv_dir = CreatePrivDir}, %% check if log files should be refreshed or go on to run tests... Result = run_or_refresh(StartOpts, Args), @@ -322,12 +328,21 @@ script_start2(StartOpts = #opts{vts = undefined, Cover = choose_val(StartOpts#opts.cover, SpecStartOpts#opts.cover), - MultTT = choose_val(StartOpts#opts.multiply_timetraps, - SpecStartOpts#opts.multiply_timetraps), - ScaleTT = choose_val(StartOpts#opts.scale_timetraps, - SpecStartOpts#opts.scale_timetraps), - AllEvHs = merge_vals([StartOpts#opts.event_handlers, - SpecStartOpts#opts.event_handlers]), + MultTT = + choose_val(StartOpts#opts.multiply_timetraps, + SpecStartOpts#opts.multiply_timetraps), + ScaleTT = + choose_val(StartOpts#opts.scale_timetraps, + SpecStartOpts#opts.scale_timetraps), + + CreatePrivDir = + choose_val(StartOpts#opts.create_priv_dir, + SpecStartOpts#opts.create_priv_dir), + + AllEvHs = + merge_vals([StartOpts#opts.event_handlers, + SpecStartOpts#opts.event_handlers]), + AllCTHooks = merge_vals( [StartOpts#opts.ct_hooks, SpecStartOpts#opts.ct_hooks]), @@ -354,7 +369,8 @@ script_start2(StartOpts = #opts{vts = undefined, EnableBuiltinHooks, include = AllInclude, multiply_timetraps = MultTT, - scale_timetraps = ScaleTT}} + scale_timetraps = ScaleTT, + create_priv_dir = CreatePrivDir}} end; _ -> {undefined,StartOpts} @@ -567,6 +583,7 @@ script_usage() -> "\n\t[-no_auto_compile]" "\n\t[-multiply_timetraps N]" "\n\t[-scale_timetraps]" + "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" "\n\t[-basic_html]\n\n"), io:format("Run tests from command line:\n\n" "\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |" @@ -586,6 +603,7 @@ script_usage() -> "\n\t[-no_auto_compile]" "\n\t[-multiply_timetraps N]" "\n\t[-scale_timetraps]" + "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" "\n\t[-basic_html]" "\n\t[-repeat N [-force_stop]] |" "\n\t[-duration HHMMSS [-force_stop]] |" @@ -606,6 +624,7 @@ script_usage() -> "\n\t[-no_auto_compile]" "\n\t[-multiply_timetraps N]" "\n\t[-scale_timetraps]" + "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" "\n\t[-basic_html]" "\n\t[-repeat N [-force_stop]] |" "\n\t[-duration HHMMSS [-force_stop]] |" @@ -782,6 +801,9 @@ run_test2(StartOpts) -> MultiplyTT = get_start_opt(multiply_timetraps, value, 1, StartOpts), ScaleTT = get_start_opt(scale_timetraps, value, false, StartOpts), + %% create unique priv dir names + CreatePrivDir = get_start_opt(create_priv_dir, value, StartOpts), + %% auto compile & include files Include = case proplists:get_value(auto_compile, StartOpts) of @@ -842,7 +864,8 @@ run_test2(StartOpts) -> silent_connections = SilentConns, stylesheet = Stylesheet, multiply_timetraps = MultiplyTT, - scale_timetraps = ScaleTT}, + scale_timetraps = ScaleTT, + create_priv_dir = CreatePrivDir}, %% test specification case proplists:get_value(spec, StartOpts) of @@ -889,6 +912,8 @@ run_spec_file(Relaxed, SpecOpts#opts.multiply_timetraps), ScaleTT = choose_val(Opts#opts.scale_timetraps, SpecOpts#opts.scale_timetraps), + CreatePrivDir = choose_val(Opts#opts.create_priv_dir, + SpecOpts#opts.create_priv_dir), AllEvHs = merge_vals([Opts#opts.event_handlers, SpecOpts#opts.event_handlers]), AllInclude = merge_vals([Opts#opts.include, @@ -912,6 +937,7 @@ run_spec_file(Relaxed, testspecs = AbsSpecs, multiply_timetraps = MultTT, scale_timetraps = ScaleTT, + create_priv_dir = CreatePrivDir, ct_hooks = AllCTHooks, enable_builtin_hooks = EnableBuiltinHooks }, @@ -1170,7 +1196,8 @@ get_data_for_node(#testspec{label = Labels, enable_builtin_hooks = EnableBuiltinHooks, include = Incl, multiply_timetraps = MTs, - scale_timetraps = STs}, Node) -> + scale_timetraps = STs, + create_priv_dir = PDs}, Node) -> Label = proplists:get_value(Node, Labels), Profile = proplists:get_value(Node, Profiles), LogDir = case proplists:get_value(Node, LogDirs) of @@ -1184,6 +1211,7 @@ get_data_for_node(#testspec{label = Labels, Cover = proplists:get_value(Node, CoverFs), MT = proplists:get_value(Node, MTs), ST = proplists:get_value(Node, STs), + CreatePrivDir = proplists:get_value(Node, PDs), ConfigFiles = [{?ct_config_txt,F} || {N,F} <- Cfgs, N==Node] ++ [CBF || {N,CBF} <- UsrCfgs, N==Node], EvHandlers = [{H,A} || {N,H,A} <- EvHs, N==Node], @@ -1200,7 +1228,8 @@ get_data_for_node(#testspec{label = Labels, enable_builtin_hooks = EnableBuiltinHooks, include = Include, multiply_timetraps = MT, - scale_timetraps = ST}. + scale_timetraps = ST, + create_priv_dir = CreatePrivDir}. refresh_logs(LogDir) -> {ok,Cwd} = file:get_cwd(), @@ -1746,25 +1775,31 @@ set_group_leader_same_as_shell() -> false end. -check_and_add([{TestDir0,M,_} | Tests], Added) -> +check_and_add([{TestDir0,M,_} | Tests], Added, PA) -> case locate_test_dir(TestDir0, M) of {ok,TestDir} -> case lists:member(TestDir, Added) of true -> - check_and_add(Tests, Added); + check_and_add(Tests, Added, PA); false -> - true = code:add_patha(TestDir), - check_and_add(Tests, [TestDir|Added]) + case lists:member(rm_trailing_slash(TestDir), + code:get_path()) of + false -> + true = code:add_patha(TestDir), + check_and_add(Tests, [TestDir|Added], [TestDir|PA]); + true -> + check_and_add(Tests, [TestDir|Added], PA) + end end; {error,_} -> {error,{invalid_directory,TestDir0}} end; -check_and_add([], _) -> - ok. +check_and_add([], _, PA) -> + {ok,PA}. do_run_test(Tests, Skip, Opts) -> - case check_and_add(Tests, []) of - ok -> + case check_and_add(Tests, [], []) of + {ok,AddedToPath} -> ct_util:set_testdata({stats,{0,0,{0,0}}}), ct_util:set_testdata({cover,undefined}), test_server_ctrl:start_link(local), @@ -1842,6 +1877,8 @@ do_run_test(Tests, Skip, Opts) -> test_server_ctrl:multiply_timetraps(Opts#opts.multiply_timetraps), test_server_ctrl:scale_timetraps(Opts#opts.scale_timetraps), + test_server_ctrl:create_priv_dir(choose_val(Opts#opts.create_priv_dir, + auto_per_run)), ct_event:notify(#event{name=start_info, node=node(), data={NoOfTests,NoOfSuites,NoOfCases}}), @@ -1858,7 +1895,9 @@ do_run_test(Tests, Skip, Opts) -> end, lists:foreach(fun(Suite) -> maybe_cleanup_interpret(Suite, Opts#opts.step) - end, CleanUp); + end, CleanUp), + [code:del_path(Dir) || Dir <- AddedToPath], + ok; Error -> Error end. @@ -2347,31 +2386,38 @@ event_handler_init_args2opts([]) -> %% relative dirs "post run_test erl_args" is not kept! rel_to_abs(CtArgs) -> {PA,PZ} = get_pa_pz(CtArgs, [], []), - io:format(user, "~n", []), [begin - code:del_path(filename:basename(D)), - Abs = filename:absname(D), - code:add_pathz(Abs), - if D /= Abs -> + Dir = rm_trailing_slash(D), + Abs = make_abs(Dir), + if Dir /= Abs -> + code:del_path(Dir), + code:del_path(Abs), io:format(user, "Converting ~p to ~p and re-inserting " "with add_pathz/1~n", - [D, Abs]); + [Dir, Abs]); true -> - ok - end + code:del_path(Dir) + end, + code:add_pathz(Abs) end || D <- PZ], [begin - code:del_path(filename:basename(D)), - Abs = filename:absname(D), - code:add_patha(Abs), - if D /= Abs -> + Dir = rm_trailing_slash(D), + Abs = make_abs(Dir), + if Dir /= Abs -> + code:del_path(Dir), + code:del_path(Abs), io:format(user, "Converting ~p to ~p and re-inserting " "with add_patha/1~n", - [D, Abs]); - true ->ok - end + [Dir, Abs]); + true -> + code:del_path(Dir) + end, + code:add_patha(Abs) end || D <- PA], - io:format(user, "~n", []). + io:format(user, "~n", []). + +rm_trailing_slash(Dir) -> + filename:join(filename:split(Dir)). get_pa_pz([{pa,Dirs} | Args], PA, PZ) -> get_pa_pz(Args, PA ++ Dirs, PZ); @@ -2382,6 +2428,19 @@ get_pa_pz([_ | Args], PA, PZ) -> get_pa_pz([], PA, PZ) -> {PA,PZ}. +make_abs(RelDir) -> + Tokens = filename:split(filename:absname(RelDir)), + filename:join(lists:reverse(make_abs1(Tokens, []))). + +make_abs1([".."|Dirs], [_Dir|Path]) -> + make_abs1(Dirs, Path); +make_abs1(["."|Dirs], Path) -> + make_abs1(Dirs, Path); +make_abs1([Dir|Dirs], Path) -> + make_abs1(Dirs, [Dir|Path]); +make_abs1([], Path) -> + Path. + %% This function translates ct:run_test/1 start options %% to ct_run start arguments (on the init arguments format) - %% this is useful mainly for testing the ct_run start functions. @@ -2419,6 +2478,10 @@ opts2args(EnvStartOpts) -> [{scale_timetraps,[]}]; ({scale_timetraps,false}) -> []; + ({create_priv_dir,auto_per_run}) -> + []; + ({create_priv_dir,PD}) when is_atom(PD) -> + [{create_priv_dir,[atom_to_list(PD)]}]; ({force_stop,true}) -> [{force_stop,[]}]; ({force_stop,false}) -> diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index b68cbd3aa1..5b197c0c81 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -568,6 +568,21 @@ add_tests([{scale_timetraps,Node,ST}|Ts],Spec) -> add_tests([{scale_timetraps,ST}|Ts],Spec) -> add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec); +%% --- create_priv_dir --- +add_tests([{create_priv_dir,all_nodes,PD}|Ts],Spec) -> + Tests = lists:map(fun(N) -> {create_priv_dir,N,PD} end, list_nodes(Spec)), + add_tests(Tests++Ts,Spec); +add_tests([{create_priv_dir,Nodes,PD}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,create_priv_dir,[PD],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); +add_tests([{create_priv_dir,Node,PD}|Ts],Spec) -> + PDs = Spec#testspec.create_priv_dir, + PDs1 = [{ref2node(Node,Spec#testspec.nodes),PD} | + lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,PDs)], + add_tests(Ts,Spec#testspec{create_priv_dir=PDs1}); +add_tests([{create_priv_dir,PD}|Ts],Spec) -> + add_tests([{create_priv_dir,all_nodes,PD}|Ts],Spec); + %% --- config --- add_tests([{config,all_nodes,Files}|Ts],Spec) -> Tests = lists:map(fun(N) -> {config,N,Files} end, list_nodes(Spec)), @@ -1158,7 +1173,8 @@ valid_terms() -> {skip_groups,6}, {skip_groups,7}, {skip_cases,5}, - {skip_cases,6} + {skip_cases,6}, + {create_priv_dir,2} ]. %% this function "guesses" if the user has misspelled a term name diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index bde832811a..082599a9c6 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -43,9 +43,10 @@ include=[], multiply_timetraps=[], scale_timetraps=[], + create_priv_dir=[], alias=[], tests=[], - merge_tests = true }). + merge_tests=true}). -record(cover, {app=none, level=details, diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 284612b8f7..332c444145 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -29,6 +29,7 @@ MODULES= \ ct_test_support_eh \ ct_userconfig_callback \ ct_smoke_test_SUITE \ + ct_priv_dir_SUITE \ ct_event_handler_SUITE \ ct_config_info_SUITE \ ct_groups_test_1_SUITE \ diff --git a/lib/common_test/test/ct_priv_dir_SUITE.erl b/lib/common_test/test/ct_priv_dir_SUITE.erl new file mode 100644 index 0000000000..f6942d59bf --- /dev/null +++ b/lib/common_test/test/ct_priv_dir_SUITE.erl @@ -0,0 +1,277 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2011. 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% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_priv_dir_SUITE +%%% +%%% Description: +%%% Test that it works to use the create_priv_dir option. +%%% +%%%------------------------------------------------------------------- +-module(ct_priv_dir_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config1 = ct_test_support:init_per_suite(Config), + Config1. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). + +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [ + default, + auto_per_run, + auto_per_tc, + manual_per_tc, + spec_default, + spec_auto_per_run, + spec_auto_per_run, + spec_manual_per_tc + ]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% +default(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join(DataDir, "priv_dir_SUITE"), + {Opts,ERPid} = setup([{suite,Suite},{testcase,default}, + {label,default}], Config), + ok = execute(default, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% +auto_per_run(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join(DataDir, "priv_dir_SUITE"), + {Opts,ERPid} = setup([{suite,Suite},{testcase,default}, + {label,auto_per_run}, + {create_priv_dir,auto_per_run}], Config), + ok = execute(auto_per_run, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% +auto_per_tc(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join(DataDir, "priv_dir_SUITE"), + {Opts,ERPid} = setup([{suite,Suite},{testcase,auto_per_tc}, + {label,auto_per_tc}, + {create_priv_dir,auto_per_tc}], Config), + ok = execute(auto_per_tc, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% +manual_per_tc(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join(DataDir, "priv_dir_SUITE"), + {Opts,ERPid} = setup([{suite,Suite},{testcase,manual_per_tc}, + {label,manual_per_tc}, + {create_priv_dir,manual_per_tc}], Config), + ok = execute(manual_per_tc, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% +spec_default(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Spec = filename:join(DataDir, "default.spec"), + {Opts,ERPid} = setup([{spec,Spec}, + {label,spec_default}], Config), + ok = execute(spec_default, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% +spec_auto_per_run(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Spec = filename:join(DataDir, "auto_per_run.spec"), + {Opts,ERPid} = setup([{spec,Spec}, + {label,spec_auto_per_run}], Config), + ok = execute(spec_auto_per_run, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% +spec_auto_per_tc(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Spec = filename:join(DataDir, "auto_per_tc.spec"), + {Opts,ERPid} = setup([{spec,Spec}, + {label,spec_auto_per_tc}], Config), + ok = execute(spec_auto_per_tc, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% +spec_manual_per_tc(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Spec = filename:join(DataDir, "manual_per_tc.spec"), + {Opts,ERPid} = setup([{spec,Spec}, + {label,spec_manual_per_tc}], Config), + ok = execute(spec_manual_per_tc, Opts, ERPid, Config). + + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +setup(Test, Config) -> + Opts0 = ct_test_support:get_opts(Config), + Level = ?config(trace_level, Config), + EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test], + ERPid = ct_test_support:start_event_receiver(Config), + {Opts,ERPid}. + +execute(Name, Opts, ERPid, Config) -> + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(Name, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(Name), + ct_test_support:verify_events(TestEvents, Events, Config). + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). + + +test_events(DEF) when DEF == default ; DEF == auto_per_run -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{priv_dir_SUITE,init_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{priv_dir_SUITE,default}}, + {?eh,tc_done,{priv_dir_SUITE,default,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{priv_dir_SUITE,end_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]}]; + +test_events(auto_per_tc) -> + [{?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{priv_dir_SUITE,init_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{priv_dir_SUITE,auto_per_tc}}, + {?eh,tc_done,{priv_dir_SUITE,auto_per_tc,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{priv_dir_SUITE,end_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]}]; + +test_events(manual_per_tc) -> + [{?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{priv_dir_SUITE,init_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{priv_dir_SUITE,manual_per_tc}}, + {?eh,tc_done,{priv_dir_SUITE,manual_per_tc,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{priv_dir_SUITE,end_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]}]; + +test_events(SPECDEF) when SPECDEF == spec_default ; + SPECDEF == spec_auto_per_run -> + [{?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{priv_dir_SUITE,init_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{priv_dir_SUITE,default}}, + {?eh,tc_done,{priv_dir_SUITE,default,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{priv_dir_SUITE,end_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]}]; + +test_events(spec_auto_per_tc) -> + [{?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{priv_dir_SUITE,init_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{priv_dir_SUITE,auto_per_tc}}, + {?eh,tc_done,{priv_dir_SUITE,auto_per_tc,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{priv_dir_SUITE,end_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]}]; + +test_events(spec_manual_per_tc) -> + [{?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{priv_dir_SUITE,init_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{priv_dir_SUITE,manual_per_tc}}, + {?eh,tc_done,{priv_dir_SUITE,manual_per_tc,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{priv_dir_SUITE,end_per_suite}}, + {?eh,tc_done,{priv_dir_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]}]. + diff --git a/lib/common_test/test/ct_priv_dir_SUITE_data/auto_per_run.spec b/lib/common_test/test/ct_priv_dir_SUITE_data/auto_per_run.spec new file mode 100644 index 0000000000..4dde0ed1f4 --- /dev/null +++ b/lib/common_test/test/ct_priv_dir_SUITE_data/auto_per_run.spec @@ -0,0 +1,5 @@ +{create_priv_dir, auto_per_run}. + +{alias, curr, "./"}. + +{cases, curr, priv_dir_SUITE, default}.
\ No newline at end of file diff --git a/lib/common_test/test/ct_priv_dir_SUITE_data/auto_per_tc.spec b/lib/common_test/test/ct_priv_dir_SUITE_data/auto_per_tc.spec new file mode 100644 index 0000000000..c265500865 --- /dev/null +++ b/lib/common_test/test/ct_priv_dir_SUITE_data/auto_per_tc.spec @@ -0,0 +1,5 @@ +{create_priv_dir, auto_per_tc}. + +{alias, curr, "./"}. + +{cases, curr, priv_dir_SUITE, auto_per_tc}.
\ No newline at end of file diff --git a/lib/common_test/test/ct_priv_dir_SUITE_data/default.spec b/lib/common_test/test/ct_priv_dir_SUITE_data/default.spec new file mode 100644 index 0000000000..2f053e792f --- /dev/null +++ b/lib/common_test/test/ct_priv_dir_SUITE_data/default.spec @@ -0,0 +1,3 @@ +{alias, curr, "./"}. + +{cases, curr, priv_dir_SUITE, default}.
\ No newline at end of file diff --git a/lib/common_test/test/ct_priv_dir_SUITE_data/manual_per_tc.spec b/lib/common_test/test/ct_priv_dir_SUITE_data/manual_per_tc.spec new file mode 100644 index 0000000000..4f98734d5f --- /dev/null +++ b/lib/common_test/test/ct_priv_dir_SUITE_data/manual_per_tc.spec @@ -0,0 +1,5 @@ +{create_priv_dir, manual_per_tc}. + +{alias, curr, "./"}. + +{cases, curr, priv_dir_SUITE, manual_per_tc}.
\ No newline at end of file diff --git a/lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl b/lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl new file mode 100644 index 0000000000..423cb2999b --- /dev/null +++ b/lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl @@ -0,0 +1,127 @@ +%%%------------------------------------------------------------------- +%%% @author Peter Andersson <[email protected]> +%%% @copyright (C) 2012, Peter Andersson +%%% @doc +%%% +%%% @end +%%% Created : 23 Jan 2012 by Peter Andersson <[email protected]> +%%%------------------------------------------------------------------- +-module(priv_dir_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% @spec suite() -> Info +%% Info = [tuple()] +%% @end +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,30}}]. + +%%-------------------------------------------------------------------- +%% @spec init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} | {fail,Reason} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec 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 +%% @end +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% @spec all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +all() -> + []. + +default(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + "log_private" = filename:basename(PrivDir), + {ok,_} = file:list_dir(PrivDir). + +auto_per_tc(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + ["log_private",_] = string:tokens(filename:basename(PrivDir), "."), + {ok,_} = file:list_dir(PrivDir). + +manual_per_tc(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + ["log_private",_] = string:tokens(filename:basename(PrivDir), "."), + {error,_} = file:list_dir(PrivDir), + ok = ct:make_priv_dir(), + {ok,_} = file:list_dir(PrivDir). + diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index 5b155398dc..4e67639805 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -150,14 +150,26 @@ guard(Expr, Sub) -> opt_guard_try(#c_seq{arg=Arg,body=Body0}=Seq) -> Body = opt_guard_try(Body0), case {Arg,Body} of - {#c_call{},#c_literal{val=false}} -> - %% We have sequence consisting of a call (evaluted - %% for a possible exception only), followed by 'false'. - %% Since the sequence is inside a try block that will + {#c_call{module=#c_literal{val=Mod}, + name=#c_literal{val=Name}, + args=Args},#c_literal{val=false}} -> + %% We have sequence consisting of a call (evaluated + %% for a possible exception and/or side effect only), + %% followed by 'false'. + %% Since the sequence is inside a try block that will %% default to 'false' if any exception occurs, not %% evalutating the call will not change the behaviour - %% of the guard. - Body; + %% provided that the call has no side effects. + case erl_bifs:is_pure(Mod, Name, length(Args)) of + false -> + %% Not a pure BIF (meaning that this is not + %% a guard and that we must keep the call). + Seq#c_seq{body=Body}; + true -> + %% The BIF has no side effects, so it can + %% be safely removed. + Body + end; {_,_} -> Seq#c_seq{body=Body} end; @@ -1747,36 +1759,26 @@ opt_bool_clauses([_|_], _, _) -> %% end. NewVar -> %% erlang:error(badarg) %% end. -%% -%% We add the extra match-all clause at the end only if Expr is -%% not guaranteed to evaluate to a boolean. opt_bool_not(#c_case{arg=Arg,clauses=Cs0}=Case0) -> case Arg of #c_call{anno=Anno,module=#c_literal{val=erlang}, name=#c_literal{val='not'}, args=[Expr]} -> - Cs = opt_bool_not(Anno, Expr, Cs0), + Cs = [opt_bool_not_invert(C) || C <- Cs0] ++ + [#c_clause{anno=[compiler_generated], + pats=[#c_var{name=cor_variable}], + guard=#c_literal{val=true}, + body=#c_call{anno=Anno, + module=#c_literal{val=erlang}, + name=#c_literal{val=error}, + args=[#c_literal{val=badarg}]}}], Case = Case0#c_case{arg=Expr,clauses=Cs}, opt_bool_not(Case); _ -> opt_bool_case_redundant(Case0) end. -opt_bool_not(Anno, Expr, Cs) -> - Tail = case is_bool_expr(Expr) of - false -> - [#c_clause{anno=[compiler_generated], - pats=[#c_var{name=cor_variable}], - guard=#c_literal{val=true}, - body=#c_call{anno=Anno, - module=#c_literal{val=erlang}, - name=#c_literal{val=error}, - args=[#c_literal{val=badarg}]}}]; - true -> [] - end, - [opt_bool_not_invert(C) || C <- Cs] ++ Tail. - opt_bool_not_invert(#c_clause{pats=[#c_literal{val=Bool}]}=C) -> C#c_clause{pats=[#c_literal{val=not Bool}]}. @@ -2065,32 +2067,7 @@ opt_case_in_let_2(V, Arg0, (_) -> false end, Es), %Only variables in tuple false = core_lib:is_var_used(V, B), %Built tuple must not be used. Arg1 = tuple_to_values(Arg0, length(Es)), %Might fail. - #c_let{vars=Es,arg=Arg1,body=B}; -opt_case_in_let_2(_, Arg, Cs) -> - %% simplify_bool_case(Case0) -> Case - %% Remove unecessary cases like - %% - %% case BoolExpr of - %% true -> true; - %% false -> false; - %% .... - %% end - %% - %% where BoolExpr is an expression that can only return true - %% or false (or throw an exception). - - true = is_bool_case(Cs) andalso is_bool_expr(Arg), - Arg. - -is_bool_case([A,B|_]) -> - (is_bool_clause(true, A) andalso is_bool_clause(false, B)) - orelse (is_bool_clause(false, A) andalso is_bool_clause(true, B)). - -is_bool_clause(Bool, #c_clause{pats=[#c_literal{val=Bool}], - guard=#c_literal{val=true}, - body=#c_literal{val=Bool}}) -> - true; -is_bool_clause(_, _) -> false. + #c_let{vars=Es,arg=Arg1,body=B}. %% is_simple_case_arg(Expr) -> true|false %% Determine whether the Expr is simple enough to be worth @@ -2612,14 +2589,14 @@ bsm_maybe_ctx_to_binary(V, B) -> body=B} end. -previous_ctx_to_binary(V, #c_seq{arg=#c_primop{name=Name,args=As}}) -> - case {Name,As} of - {#c_literal{val=bs_context_to_binary},[#c_var{name=V}]} -> +previous_ctx_to_binary(V, Core) -> + case Core of + #c_seq{arg=#c_primop{name=#c_literal{val=bs_context_to_binary}, + args=[#c_var{name=V}]}} -> true; - {_,_} -> + _ -> false - end; -previous_ctx_to_binary(_, _) -> false. + end. %% bsm_leftmost(Cs) -> none | ArgumentNumber %% Find the leftmost argument that does binary matching. Return @@ -2764,22 +2741,20 @@ add_bin_opt_info(Core, Term) -> end. add_warning(Core, Term) -> - Anno = core_lib:get_anno(Core), - case lists:member(compiler_generated, Anno) of - true -> ok; + case is_compiler_generated(Core) of + true -> + ok; false -> - case get_line(Anno) of - Line when Line >= 0 -> %Must be positive. - File = get_file(Anno), - Key = {?MODULE,warnings}, - case get(Key) of - [{File,[{Line,?MODULE,Term}]}|_] -> - ok; %We already have + Anno = core_lib:get_anno(Core), + Line = get_line(Anno), + File = get_file(Anno), + Key = {?MODULE,warnings}, + case get(Key) of + [{File,[{Line,?MODULE,Term}]}|_] -> + ok; %We already have %an identical warning. - Ws -> - put(Key, [{File,[{Line,?MODULE,Term}]}|Ws]) - end; - _ -> ok %Compiler-generated code. + Ws -> + put(Key, [{File,[{Line,?MODULE,Term}]}|Ws]) end end. @@ -2793,14 +2768,7 @@ get_file([]) -> "no_file". % should not happen is_compiler_generated(Core) -> Anno = core_lib:get_anno(Core), - case lists:member(compiler_generated, Anno) of - true -> true; - false -> - case get_line(Anno) of - Line when Line >= 0 -> false; - _ -> true - end - end. + member(compiler_generated, Anno). get_warnings() -> ordsets:from_list((erase({?MODULE,warnings}))). diff --git a/lib/compiler/src/v3_codegen.erl b/lib/compiler/src/v3_codegen.erl index 6623485609..36d35a8122 100644 --- a/lib/compiler/src/v3_codegen.erl +++ b/lib/compiler/src/v3_codegen.erl @@ -1423,20 +1423,7 @@ set_cg([{var,R}], Con, Le, Vdb, Bef, St) -> Other -> [{move,Other,Ret}] end, - {Ais,clear_dead(Int, Le#l.i, Vdb),St}; -set_cg([], {binary,Segs}, Le, Vdb, Bef, St) -> - Fail = {f,St#cg.bfail}, - Target = find_scratch_reg(Bef#sr.reg), - Temp = find_scratch_reg(put_reg(Target, Bef#sr.reg)), - PutCode = cg_bin_put(Segs, Fail, Bef), - MaxRegs = max_reg(Bef#sr.reg), - Code = cg_binary(PutCode, Target, Temp, Fail, MaxRegs, Le#l.a), - Aft = clear_dead(Bef, Le#l.i, Vdb), - {Code,Aft,St}; -set_cg([], _, Le, Vdb, Bef, St) -> - %% This should have been stripped by compiler, just cleanup. - {[],clear_dead(Bef, Le#l.i, Vdb), St}. - + {Ais,clear_dead(Int, Le#l.i, Vdb),St}. %%% %%% Code generation for constructing binaries. @@ -2067,7 +2054,7 @@ line_1(_, 0) -> %% Missing line number or line number 0. {line,[]}; line_1(Name, Line) -> - {line,[{location,Name,abs(Line)}]}. + {line,[{location,Name,Line}]}. find_loc([Line|T], File, _) when is_integer(Line) -> find_loc(T, File, Line); diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 6885405ae0..ad0a8a7654 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -2085,7 +2085,12 @@ bitstr_vars(Segs, Vs) -> lineno_anno(L, St) -> {line, Line} = erl_parse:get_attribute(L, line), - [Line] ++ St#core.file. + if + Line < 0 -> + [-Line] ++ St#core.file ++ [compiler_generated]; + true -> + [Line] ++ St#core.file + end. get_ianno(Ce) -> case get_anno(Ce) of diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl index f2eaa37617..abe94ad991 100644 --- a/lib/compiler/src/v3_kernel.erl +++ b/lib/compiler/src/v3_kernel.erl @@ -88,8 +88,6 @@ -include("core_parse.hrl"). -include("v3_kernel.hrl"). --define(EXPENSIVE_BINARY_LIMIT, 256). - %% These are not defined in v3_kernel.hrl. get_kanno(Kthing) -> element(2, Kthing). set_kanno(Kthing, Anno) -> setelement(2, Kthing, Anno). @@ -120,7 +118,6 @@ copy_anno(Kdst, Ksrc) -> funs=[], %Fun functions free=[], %Free variables ws=[] :: [warning()], %Warnings. - lit, %Constant pool for literals. guard_refc=0}). %> 0 means in guard -spec module(cerl:c_module(), [compile:option()]) -> @@ -129,7 +126,7 @@ copy_anno(Kdst, Ksrc) -> module(#c_module{anno=A,name=M,exports=Es,attrs=As,defs=Fs}, _Options) -> Kas = attributes(As), Kes = map(fun (#c_var{name={_,_}=Fname}) -> Fname end, Es), - St0 = #kern{lit=dict:new()}, + St0 = #kern{}, {Kfs,St} = mapfoldl(fun function/2, St0, Fs), {ok,#k_mdef{anno=A,name=M#c_literal.val,exports=Kes,attributes=Kas, body=Kfs ++ St#kern.funs},lists:sort(St#kern.ws)}. @@ -250,26 +247,20 @@ expr(#c_var{anno=A,name={_Name,Arity}}=Fname, Sub, St) -> expr(Fun, Sub, St); expr(#c_var{anno=A,name=V}, Sub, St) -> {#k_var{anno=A,name=get_vsub(V, Sub)},[],St}; -expr(#c_literal{}=Lit, Sub, St) -> - Core = handle_literal(Lit), - expr(Core, Sub, St); -expr(#k_literal{val=Val0}=Klit, _Sub, #kern{lit=Literals0}=St) -> - %% Share identical literals to save some space and time during compilation. - case dict:find(Val0, Literals0) of - {ok,Val} -> - {Klit#k_literal{val=Val},[],St}; - error -> - Literals = dict:store(Val0, Val0, Literals0), - {Klit,[],St#kern{lit=Literals}} - end; -expr(#k_nil{}=V, _Sub, St) -> - {V,[],St}; -expr(#k_int{}=V, _Sub, St) -> - {V,[],St}; -expr(#k_float{}=V, _Sub, St) -> - {V,[],St}; -expr(#k_atom{}=V, _Sub, St) -> - {V,[],St}; +expr(#c_literal{anno=A,val=V}, _Sub, St) -> + Klit = case V of + [] -> + #k_nil{anno=A}; + V when is_integer(V) -> + #k_int{anno=A,val=V}; + V when is_float(V) -> + #k_float{anno=A,val=V}; + V when is_atom(V) -> + #k_atom{anno=A,val=V}; + _ -> + #k_literal{anno=A,val=V} + end, + {Klit,[],St}; expr(#c_cons{anno=A,hd=Ch,tl=Ct}, Sub, St0) -> %% Do cons in two steps, first the expressions left to right, then %% any remaining literals right to left. @@ -610,7 +601,6 @@ is_atomic(#k_int{}) -> true; is_atomic(#k_float{}) -> true; is_atomic(#k_atom{}) -> true; %%is_atomic(#k_char{}) -> true; %No characters -%%is_atomic(#k_string{}) -> true; is_atomic(#k_nil{}) -> true; is_atomic(#k_var{}) -> true; is_atomic(_) -> false. @@ -919,9 +909,8 @@ match_guard_1([#iclause{anno=A,osub=Osub,guard=G,body=B}|Cs0], Def0, St0) -> true -> %% The true clause body becomes the default. {Kb,Pb,St1} = body(B, Osub, St0), - Line = get_line(A), - St2 = maybe_add_warning(Cs0, Line, St1), - St = maybe_add_warning(Def0, Line, St2), + St2 = maybe_add_warning(Cs0, A, St1), + St = maybe_add_warning(Def0, A, St2), {[],pre_seq(Pb, Kb),St}; false -> {Kg,St1} = guard(G, Osub, St0), @@ -932,15 +921,18 @@ match_guard_1([#iclause{anno=A,osub=Osub,guard=G,body=B}|Cs0], Def0, St0) -> end; match_guard_1([], Def, St) -> {[],Def,St}. -maybe_add_warning([C|_], Line, St) -> - maybe_add_warning(C, Line, St); -maybe_add_warning([], _Line, St) -> St; -maybe_add_warning(fail, _Line, St) -> St; -maybe_add_warning(Ke, MatchLine, St) -> - case get_kanno(Ke) of - [compiler_generated|_] -> St; - Anno -> +maybe_add_warning([C|_], MatchAnno, St) -> + maybe_add_warning(C, MatchAnno, St); +maybe_add_warning([], _MatchAnno, St) -> St; +maybe_add_warning(fail, _MatchAnno, St) -> St; +maybe_add_warning(Ke, MatchAnno, St) -> + case is_compiler_generated(Ke) of + true -> + St; + false -> + Anno = get_kanno(Ke), Line = get_line(Anno), + MatchLine = get_line(MatchAnno), Warn = case MatchLine of none -> nomatch_shadow; _ -> {nomatch_shadow,MatchLine} @@ -1122,7 +1114,6 @@ select_bin_int([#iclause{pats=[#k_bin_seg{anno=A,type=integer, end, select_assert_match_possible(Bits, Val, Fl), P = #k_bin_int{anno=A,size=Sz,unit=U,flags=Fl,val=Val,next=N}, - select_assert_match_possible(Bits, Val, Fl), case member(native, Fl) of true -> throw(not_possible); false -> ok @@ -1264,8 +1255,6 @@ match_clause([U|Us], [C|_]=Cs0, Def, St0) -> sub_size_var(#k_bin_seg{size=#k_var{name=Name}=Kvar}=BinSeg, [#iclause{isub=Sub}|_]) -> BinSeg#k_bin_seg{size=Kvar#k_var{name=get_vsub(Name, Sub)}}; -sub_size_var(#k_bin_int{size=#k_var{name=Name}=Kvar}=BinSeg, [#iclause{isub=Sub}|_]) -> - BinSeg#k_bin_int{size=Kvar#k_var{name=get_vsub(Name, Sub)}}; sub_size_var(K, _) -> K. get_con([C|_]) -> arg_arg(clause_arg(C)). %Get the constructor @@ -1383,7 +1372,6 @@ arg_con(Arg) -> #k_tuple{} -> k_tuple; #k_binary{} -> k_binary; #k_bin_end{} -> k_bin_end; - #k_bin_int{} -> k_bin_int; #k_bin_seg{} -> k_bin_seg; #k_var{} -> k_var end. @@ -1394,15 +1382,9 @@ arg_val(Arg) -> #k_int{val=I} -> I; #k_float{val=F} -> F; #k_atom{val=A} -> A; - #k_nil{} -> 0; - #k_cons{} -> 2; #k_tuple{es=Es} -> length(Es); #k_bin_seg{size=S,unit=U,type=T,flags=Fs} -> - {set_kanno(S, []),U,T,Fs}; - #k_bin_int{} -> - 0; - #k_bin_end{} -> 0; - #k_binary{} -> 0 + {set_kanno(S, []),U,T,Fs} end. %% ubody_used_vars(Expr, State) -> [UsedVar] @@ -1432,14 +1414,12 @@ ubody(#ivalues{anno=A,args=As}, return, St) -> {#k_return{anno=#k{us=Au,ns=[],a=A},args=As},Au,St}; ubody(#ivalues{anno=A,args=As}, {break,_Vbs}, St) -> Au = lit_list_vars(As), - if St#kern.guard_refc > 0 -> + case is_in_guard(St) of + true -> {#k_guard_break{anno=#k{us=Au,ns=[],a=A},args=As},Au,St}; - true -> + false -> {#k_break{anno=#k{us=Au,ns=[],a=A},args=As},Au,St} end; -ubody(#ivalues{anno=A,args=As}, {guard_break,_Vbs}, St) -> - Au = lit_list_vars(As), - {#k_guard_break{anno=#k{us=Au,ns=[],a=A},args=As},Au,St}; ubody(E, return, St0) -> %% Enterable expressions need no trailing return. case is_enter_expr(E) of @@ -1456,12 +1436,7 @@ ubody(E, {break,_Rs} = Break, St0) -> false -> {Ea,Pa,St1} = force_atomic(E, St0), ubody(pre_seq(Pa, #ivalues{args=[Ea]}), Break, St1) - end; -ubody(E, {guard_break,_Rs} = GuardBreak, St0) -> - %%ok = io:fwrite("ubody ~w:~p~n", [?LINE,{E,Br}]), - %% Exiting expressions need no trailing break. - {Ea,Pa,St1} = force_atomic(E, St0), - ubody(pre_seq(Pa, #ivalues{args=[Ea]}), GuardBreak, St1). + end. iletrec_funs(#iletrec{defs=Fs}, St0) -> %% Use union of all free variables. @@ -1513,64 +1488,21 @@ is_enter_expr(#k_receive{}) -> true; is_enter_expr(#k_receive_next{}) -> true; is_enter_expr(_) -> false. -%% uguard(Expr, State) -> {Expr,[UsedVar],State}. -%% Tag the guard sequence with its used variables. - -uguard(#k_try{anno=A,arg=B0,vars=[#k_var{name=X}],body=#k_var{name=X}, - handler=#k_atom{val=false}}=Try, St0) -> - {B1,Bu,St1} = uguard(B0, St0), - {Try#k_try{anno=#k{us=Bu,ns=[],a=A},arg=B1},Bu,St1}; -uguard(T, St) -> - %%ok = io:fwrite("~w: ~p~n", [?LINE,T]), - uguard_test(T, St). - -%% uguard_test(Expr, State) -> {Test,[UsedVar],State}. -%% At this stage tests are just expressions which don't return any -%% values. - -uguard_test(T, St) -> uguard_expr(T, [], St). +%% uexpr(Expr, Break, State) -> {Expr,[UsedVar],State}. +%% Tag an expression with its used variables. +%% Break = return | {break,[RetVar]}. -uguard_expr(#iset{anno=A,vars=Vs,arg=E0,body=B0}, Rs, St0) -> - Ns = lit_list_vars(Vs), - {E1,Eu,St1} = uguard_expr(E0, Vs, St0), - {B1,Bu,St2} = uguard_expr(B0, Rs, St1), - Used = union(Eu, subtract(Bu, Ns)), - {#k_seq{anno=#k{us=Used,ns=Ns,a=A},arg=E1,body=B1},Used,St2}; -uguard_expr(#k_try{anno=A,arg=B0,vars=[#k_var{name=X}],body=#k_var{name=X}, - handler=#k_atom{val=false}}=Try, Rs, St0) -> - {B1,Bu,St1} = uguard_expr(B0, Rs, St0), - {Try#k_try{anno=#k{us=Bu,ns=lit_list_vars(Rs),a=A},arg=B1,ret=Rs}, - Bu,St1}; -uguard_expr(#k_test{anno=A,op=Op,args=As}=Test, Rs, St) -> +uexpr(#k_test{anno=A,op=Op,args=As}=Test, {break,Rs}, St) -> [] = Rs, %Sanity check Used = union(op_vars(Op), lit_list_vars(As)), {Test#k_test{anno=#k{us=Used,ns=lit_list_vars(Rs),a=A}}, Used,St}; -uguard_expr(#k_bif{anno=A,op=Op,args=As}=Bif, Rs, St) -> - Used = union(op_vars(Op), lit_list_vars(As)), - {Bif#k_bif{anno=#k{us=Used,ns=lit_list_vars(Rs),a=A},ret=Rs}, - Used,St}; -uguard_expr(#ivalues{anno=A,args=As}, Rs, St) -> - Sets = foldr2(fun (V, Arg, Rhs) -> - #iset{anno=A,vars=[V],arg=Arg,body=Rhs} - end, #k_atom{val=true}, Rs, As), - uguard_expr(Sets, [], St); -uguard_expr(#k_match{anno=A,vars=Vs,body=B0}, Rs, St0) -> - %% Experimental support for andalso/orelse in guards. - Br = {guard_break,Rs}, - {B1,Bu,St1} = umatch(B0, Br, St0), - {#k_guard_match{anno=#k{us=Bu,ns=lit_list_vars(Rs),a=A}, - vars=Vs,body=B1,ret=Rs},Bu,St1}; -uguard_expr(Lit, Rs, St) -> - %% Transform literals to puts here. - Used = lit_vars(Lit), - {#k_put{anno=#k{us=Used,ns=lit_list_vars(Rs),a=get_kanno(Lit)}, - arg=Lit,ret=Rs},Used,St}. - -%% uexpr(Expr, Break, State) -> {Expr,[UsedVar],State}. -%% Tag an expression with its used variables. -%% Break = return | {break,[RetVar]}. - +uexpr(#iset{anno=A,vars=Vs,arg=E0,body=B0}, {break,_}=Br, St0) -> + Ns = lit_list_vars(Vs), + {E1,Eu,St1} = uexpr(E0, {break,Vs}, St0), + {B1,Bu,St2} = uexpr(B0, Br, St1), + Used = union(Eu, subtract(Bu, Ns)), + {#k_seq{anno=#k{us=Used,ns=Ns,a=A},arg=E1,body=B1},Used,St2}; uexpr(#k_call{anno=A,op=#k_local{name=F,arity=Ar}=Op,args=As0}=Call, Br, St) -> Free = get_free(F, Ar, St), As1 = As0 ++ Free, %Add free variables LAST! @@ -1602,10 +1534,11 @@ uexpr(#k_match{anno=A,vars=Vs0,body=B0}, Br, St0) -> Vs = handle_reuse_annos(Vs0, St0), Rs = break_rets(Br), {B1,Bu,St1} = umatch(B0, Br, St0), - if St0#kern.guard_refc > 0 -> + case is_in_guard(St1) of + true -> {#k_guard_match{anno=#k{us=Bu,ns=lit_list_vars(Rs),a=A}, vars=Vs,body=B1,ret=Rs},Bu,St1}; - true -> + false -> {#k_match{anno=#k{us=Bu,ns=lit_list_vars(Rs),a=A}, vars=Vs,body=B1,ret=Rs},Bu,St1} end; @@ -1622,24 +1555,27 @@ uexpr(#k_receive_accept{anno=A}, _, St) -> {#k_receive_accept{anno=#k{us=[],ns=[],a=A}},[],St}; uexpr(#k_receive_next{anno=A}, _, St) -> {#k_receive_next{anno=#k{us=[],ns=[],a=A}},[],St}; -uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0}, - {break,Rs0}, St0) -> - {Avs,St1} = new_vars(length(Vs), St0), %Need dummy names here - {A1,Au,St2} = ubody(A0, {break,Avs}, St1), %Must break to clean up here! - {B1,Bu,St3} = ubody(B0, {break,Rs0}, St2), - {H1,Hu,St4} = ubody(H0, {break,Rs0}, St3), - %% Guarantee ONE return variable. - NumNew = if - Rs0 =:= [] -> 1; - true -> 0 - end, - {Ns,St5} = new_vars(NumNew, St4), - Rs1 = Rs0 ++ Ns, - Used = union([Au,subtract(Bu, lit_list_vars(Vs)), - subtract(Hu, lit_list_vars(Evs))]), - {#k_try{anno=#k{us=Used,ns=lit_list_vars(Rs1),a=A}, - arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1,ret=Rs1}, - Used,St5}; +uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0}=Try, + {break,Rs0}=Br, St0) -> + case is_in_guard(St0) of + true -> + {[#k_var{name=X}],#k_var{name=X}} = {Vs,B0}, %Assertion. + #k_atom{val=false} = H0, %Assertion. + {A1,Bu,St1} = uexpr(A0, Br, St0), + {Try#k_try{anno=#k{us=Bu,ns=lit_list_vars(Rs0),a=A}, + arg=A1,ret=Rs0},Bu,St1}; + false -> + {Avs,St1} = new_vars(length(Vs), St0), + {A1,Au,St2} = ubody(A0, {break,Avs}, St1), + {B1,Bu,St3} = ubody(B0, Br, St2), + {H1,Hu,St4} = ubody(H0, Br, St3), + {Rs1,St5} = ensure_return_vars(Rs0, St4), + Used = union([Au,subtract(Bu, lit_list_vars(Vs)), + subtract(Hu, lit_list_vars(Evs))]), + {#k_try{anno=#k{us=Used,ns=lit_list_vars(Rs1),a=A}, + arg=A1,vars=Vs,body=B1,evars=Evs,handler=H1,ret=Rs1}, + Used,St5} + end; uexpr(#k_try{anno=A,arg=A0,vars=Vs,body=B0,evars=Evs,handler=H0}, return, St0) -> {Avs,St1} = new_vars(length(Vs), St0), %Need dummy names here @@ -1685,12 +1621,13 @@ uexpr(#ifun{anno=A,vars=Vs,body=B0}, {break,Rs}, St0) -> #k_int{val=Index},#k_int{val=Uniq}|Fvs], ret=Rs}, Free,add_local_function(Fun, St)}; -uexpr(Lit, {break,Rs}, St) -> +uexpr(Lit, {break,Rs0}, St0) -> %% Transform literals to puts here. %%ok = io:fwrite("uexpr ~w:~p~n", [?LINE,Lit]), Used = lit_vars(Lit), + {Rs,St1} = ensure_return_vars(Rs0, St0), {#k_put{anno=#k{us=Used,ns=lit_list_vars(Rs),a=get_kanno(Lit)}, - arg=Lit,ret=Rs},Used,St}. + arg=Lit,ret=Rs},Used,St1}. add_local_function(_, #kern{funs=ignore}=St) -> St; add_local_function(F, #kern{funs=Funs}=St) -> St#kern{funs=[F|Funs]}. @@ -1747,6 +1684,11 @@ bif_returns(#k_internal{name=N,arity=Ar}, Rs, St0) -> {Ns,St1} = new_vars(bif_vals(N, Ar) - length(Rs), St0), {Rs ++ Ns,St1}. +%% ensure_return_vars([Ret], State) -> {[Ret],State}. + +ensure_return_vars([], St) -> new_vars(1, St); +ensure_return_vars([_]=Rs, St) -> {Rs,St}. + %% umatch(Match, Break, State) -> {Match,[UsedVar],State}. %% Tag a match expression with its used variables. @@ -1779,7 +1721,8 @@ umatch(#k_guard{anno=A,clauses=Gs0}, Br, St0) -> {#k_guard{anno=#k{us=Gus,ns=[],a=A},clauses=Gs1},Gus,St1}; umatch(#k_guard_clause{anno=A,guard=G0,body=B0}, Br, St0) -> %%ok = io:fwrite("~w: ~p~n", [?LINE,G0]), - {G1,Gu,St1} = uguard(G0, St0#kern{guard_refc=St0#kern.guard_refc+1}), + {G1,Gu,St1} = uexpr(G0, {break,[]}, + St0#kern{guard_refc=St0#kern.guard_refc+1}), %%ok = io:fwrite("~w: ~p~n", [?LINE,G1]), {B1,Bu,St2} = umatch(B0, Br, St1#kern{guard_refc=St1#kern.guard_refc-1}), Used = union(Gu, Bu), @@ -1827,7 +1770,6 @@ lit_list_vars(Ps) -> pat_vars(#k_var{name=N}) -> {[],[N]}; %%pat_vars(#k_char{}) -> {[],[]}; -%%pat_vars(#k_string{}) -> {[],[]}; pat_vars(#k_literal{}) -> {[],[]}; pat_vars(#k_int{}) -> {[],[]}; pat_vars(#k_float{}) -> {[],[]}; @@ -1854,34 +1796,6 @@ pat_list_vars(Ps) -> {union(Used0, Used),union(New0, New)} end, {[],[]}, Ps). -%% handle_literal(Literal, Anno) -> Kernel -%% Examine the literal. Complex (heap-based) literals such as lists, -%% tuples, and binaries should be kept as literals and put into the constant pool. -%% -%% (If necessary, this function could be extended to go through the literal -%% and convert huge binary literals to bit syntax expressions. We don't do that -%% because v3_core does not produce huge binary literals, and the optimizations in -%% sys_core_fold don't do much optimizations of binaries. IF THAT CHANGE IS MADE, -%% ALSO CHANGE sys_core_dsetel.) - -handle_literal(#c_literal{anno=A,val=V}) -> - case V of - [_|_] -> - #k_literal{anno=A,val=V}; - [] -> - #k_nil{anno=A}; - V when is_tuple(V) -> - #k_literal{anno=A,val=V}; - V when is_bitstring(V) -> - #k_literal{anno=A,val=V}; - V when is_integer(V) -> - #k_int{anno=A,val=V}; - V when is_float(V) -> - #k_float{anno=A,val=V}; - V when is_atom(V) -> - #k_atom{anno=A,val=V} - end. - make_list(Es) -> foldr(fun(E, Acc) -> #c_cons{hd=E,tl=Acc} @@ -1893,6 +1807,11 @@ integers(N, M) when N =< M -> [N|integers(N + 1, M)]; integers(_, _) -> []. +%% is_in_guard(State) -> true|false. + +is_in_guard(#kern{guard_refc=Refc}) -> + Refc > 0. + %%% %%% Handling of errors and warnings. %%% @@ -1913,7 +1832,10 @@ format_error(bad_call) -> add_warning(none, Term, Anno, #kern{ws=Ws}=St) -> File = get_file(Anno), St#kern{ws=[{File,[{?MODULE,Term}]}|Ws]}; -add_warning(Line, Term, Anno, #kern{ws=Ws}=St) when Line >= 0 -> +add_warning(Line, Term, Anno, #kern{ws=Ws}=St) -> File = get_file(Anno), - St#kern{ws=[{File,[{Line,?MODULE,Term}]}|Ws]}; -add_warning(_, _, _, St) -> St. + St#kern{ws=[{File,[{Line,?MODULE,Term}]}|Ws]}. + +is_compiler_generated(Ke) -> + Anno = get_kanno(Ke), + member(compiler_generated, Anno). diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl index 37f2fdcf7e..17904c37da 100644 --- a/lib/compiler/src/v3_kernel.hrl +++ b/lib/compiler/src/v3_kernel.hrl @@ -35,7 +35,6 @@ -record(k_int, {anno=[],val}). -record(k_float, {anno=[],val}). -record(k_atom, {anno=[],val}). --record(k_string, {anno=[],val}). -record(k_nil, {anno=[]}). -record(k_tuple, {anno=[],es}). diff --git a/lib/compiler/src/v3_life.erl b/lib/compiler/src/v3_life.erl index 93f8034230..22f3e3c2d2 100644 --- a/lib/compiler/src/v3_life.erl +++ b/lib/compiler/src/v3_life.erl @@ -112,53 +112,14 @@ guard(#k_try{anno=A,arg=Ts,vars=[#k_var{name=X}],body=#k_var{name=X}, %% Lock variables that are alive before try and used afterwards. %% Don't lock variables that are only used inside the try expression. Pdb0 = vdb_sub(I, I+1, Vdb), - {T,MaxI,Pdb1} = guard_body(Ts, I+1, Pdb0), + {T,MaxI,Pdb1} = body(Ts, I+1, Pdb0), Pdb2 = use_vars(A#k.ns, MaxI+1, Pdb1), %Save "return" values - #l{ke={protected,T,var_list(Rs)},i=I,a=A#k.a,vdb=Pdb2}; -guard(#k_seq{}=G, I, Vdb0) -> - {Es,_,Vdb1} = guard_body(G, I, Vdb0), - #l{ke={block,Es},i=I,vdb=Vdb1,a=[]}; -guard(G, I, Vdb) -> guard_expr(G, I, Vdb). - -%% guard_body(Kbody, I, Vdb) -> {[Expr],MaxI,Vdb}. - -guard_body(#k_seq{arg=Ke,body=Kb}, I, Vdb0) -> - A = get_kanno(Ke), - Vdb1 = use_vars(A#k.us, I, new_vars(A#k.ns, I, Vdb0)), - {Es,MaxI,Vdb2} = guard_body(Kb, I+1, Vdb1), - E = guard_expr(Ke, I, Vdb2), - {[E|Es],MaxI,Vdb2}; -guard_body(Ke, I, Vdb0) -> - A = get_kanno(Ke), - Vdb1 = use_vars(A#k.us, I, new_vars(A#k.ns, I, Vdb0)), - E = guard_expr(Ke, I, Vdb1), - {[E],I,Vdb1}. - -%% guard_expr(Call, I, Vdb) -> Expr - -guard_expr(#k_test{anno=A,op=Op,args=As}, I, _Vdb) -> - #l{ke={test,test_op(Op),atomic_list(As)},i=I,a=A#k.a}; -guard_expr(#k_bif{anno=A,op=Op,args=As,ret=Rs}, I, _Vdb) -> - Name = bif_op(Op), - Ar = length(As), - case is_gc_bif(Name, Ar) of - false -> - #l{ke={bif,Name,atomic_list(As),var_list(Rs)},i=I,a=A#k.a}; - true -> - #l{ke={gc_bif,Name,atomic_list(As),var_list(Rs)},i=I,a=A#k.a} - end; -guard_expr(#k_put{anno=A,arg=Arg,ret=Rs}, I, _Vdb) -> - #l{ke={set,var_list(Rs),literal(Arg, [])},i=I,a=A#k.a}; -guard_expr(#k_guard_match{anno=A,body=Kb,ret=Rs}, I, Vdb) -> - %% Support for andalso/orelse in guards. - %% Work out imported variables which need to be locked. - Mdb = vdb_sub(I, I+1, Vdb), - M = match(Kb, A#k.us, I+1, [], Mdb), - #l{ke={guard_match,M,var_list(Rs)},i=I,vdb=use_vars(A#k.us, I+1, Mdb),a=A#k.a}; -guard_expr(G, I, Vdb) -> guard(G, I, Vdb). + #l{ke={protected,T,var_list(Rs)},i=I,a=A#k.a,vdb=Pdb2}. %% expr(Kexpr, I, Vdb) -> Expr. +expr(#k_test{anno=A,op=Op,args=As}, I, _Vdb) -> + #l{ke={test,test_op(Op),atomic_list(As)},i=I,a=A#k.a}; expr(#k_call{anno=A,op=Op,args=As,ret=Rs}, I, _Vdb) -> #l{ke={call,call_op(Op),atomic_list(As),var_list(Rs)},i=I,a=A#k.a}; expr(#k_enter{anno=A,op=Op,args=As}, I, _Vdb) -> @@ -176,25 +137,11 @@ expr(#k_guard_match{anno=A,body=Kb,ret=Rs}, I, Vdb) -> Mdb = vdb_sub(I, I+1, Vdb), M = match(Kb, A#k.us, I+1, [], Mdb), #l{ke={guard_match,M,var_list(Rs)},i=I,vdb=use_vars(A#k.us, I+1, Mdb),a=A#k.a}; -expr(#k_try{anno=A,arg=Ka,vars=Vs,body=Kb,evars=Evs,handler=Kh,ret=Rs}, I, Vdb) -> - %% Lock variables that are alive before the catch and used afterwards. - %% Don't lock variables that are only used inside the try. - Tdb0 = vdb_sub(I, I+1, Vdb), - %% This is the tricky bit. Lock variables in Arg that are used in - %% the body and handler. Add try tag 'variable'. - Ab = get_kanno(Kb), - Ah = get_kanno(Kh), - Tdb1 = use_vars(Ab#k.us, I+3, use_vars(Ah#k.us, I+3, Tdb0)), - Tdb2 = vdb_sub(I, I+2, Tdb1), - Vnames = fun (Kvar) -> Kvar#k_var.name end, %Get the variable names - {Aes,_,Adb} = body(Ka, I+2, add_var({catch_tag,I+1}, I+1, locked, Tdb2)), - {Bes,_,Bdb} = body(Kb, I+4, new_vars(map(Vnames, Vs), I+3, Tdb2)), - {Hes,_,Hdb} = body(Kh, I+4, new_vars(map(Vnames, Evs), I+3, Tdb2)), - #l{ke={'try',#l{ke={block,Aes},i=I+1,vdb=Adb,a=[]}, - var_list(Vs),#l{ke={block,Bes},i=I+3,vdb=Bdb,a=[]}, - var_list(Evs),#l{ke={block,Hes},i=I+3,vdb=Hdb,a=[]}, - var_list(Rs)}, - i=I,vdb=Tdb1,a=A#k.a}; +expr(#k_try{}=Try, I, Vdb) -> + case is_in_guard() of + false -> body_try(Try, I, Vdb); + true -> guard(Try, I, Vdb) + end; expr(#k_try_enter{anno=A,arg=Ka,vars=Vs,body=Kb,evars=Evs,handler=Kh}, I, Vdb) -> %% Lock variables that are alive before the catch and used afterwards. %% Don't lock variables that are only used inside the try. @@ -243,6 +190,27 @@ expr(#k_guard_break{anno=A,args=As}, I, Vdb) -> expr(#k_return{anno=A,args=As}, I, _Vdb) -> #l{ke={return,atomic_list(As)},i=I,a=A#k.a}. +body_try(#k_try{anno=A,arg=Ka,vars=Vs,body=Kb,evars=Evs,handler=Kh,ret=Rs}, + I, Vdb) -> + %% Lock variables that are alive before the catch and used afterwards. + %% Don't lock variables that are only used inside the try. + Tdb0 = vdb_sub(I, I+1, Vdb), + %% This is the tricky bit. Lock variables in Arg that are used in + %% the body and handler. Add try tag 'variable'. + Ab = get_kanno(Kb), + Ah = get_kanno(Kh), + Tdb1 = use_vars(Ab#k.us, I+3, use_vars(Ah#k.us, I+3, Tdb0)), + Tdb2 = vdb_sub(I, I+2, Tdb1), + Vnames = fun (Kvar) -> Kvar#k_var.name end, %Get the variable names + {Aes,_,Adb} = body(Ka, I+2, add_var({catch_tag,I+1}, I+1, locked, Tdb2)), + {Bes,_,Bdb} = body(Kb, I+4, new_vars(map(Vnames, Vs), I+3, Tdb2)), + {Hes,_,Hdb} = body(Kh, I+4, new_vars(map(Vnames, Evs), I+3, Tdb2)), + #l{ke={'try',#l{ke={block,Aes},i=I+1,vdb=Adb,a=[]}, + var_list(Vs),#l{ke={block,Bes},i=I+3,vdb=Bdb,a=[]}, + var_list(Evs),#l{ke={block,Hes},i=I+3,vdb=Hdb,a=[]}, + var_list(Rs)}, + i=I,vdb=Tdb1,a=A#k.a}. + %% call_op(Op) -> Op. %% bif_op(Op) -> Op. %% test_op(Op) -> Op. @@ -373,7 +341,6 @@ atomic(#k_int{val=I}) -> {integer,I}; atomic(#k_float{val=F}) -> {float,F}; atomic(#k_atom{val=N}) -> {atom,N}; %%atomic(#k_char{val=C}) -> {char,C}; -%%atomic(#k_string{val=S}) -> {string,S}; atomic(#k_nil{}) -> nil. atomic_list(Ks) -> [atomic(K) || K <- Ks]. @@ -535,3 +502,7 @@ vdb_sub(Min, Max, Vdb) -> true -> Vd end || {V,F,L}=Vd <- Vdb, F < Min, L >= Min ]. +%% is_in_guard() -> true|false. + +is_in_guard() -> + get(guard_refc) > 0. diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl index 874e02803d..fd1539e7fd 100644 --- a/lib/compiler/test/core_SUITE.erl +++ b/lib/compiler/test/core_SUITE.erl @@ -22,7 +22,7 @@ init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2, dehydrated_itracer/1,nested_tries/1, - make_effect_seq/1,eval_is_boolean/1, + seq_in_guard/1,make_effect_seq/1,eval_is_boolean/1, unsafe_case/1,nomatch_shadow/1,reversed_annos/1]). -include_lib("test_server/include/test_server.hrl"). @@ -43,7 +43,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> test_lib:recompile(?MODULE), - [dehydrated_itracer,nested_tries,make_effect_seq, + [dehydrated_itracer,nested_tries,seq_in_guard,make_effect_seq, eval_is_boolean,unsafe_case,nomatch_shadow,reversed_annos]. groups() -> @@ -64,6 +64,7 @@ end_per_group(_GroupName, Config) -> ?comp(dehydrated_itracer). ?comp(nested_tries). +?comp(seq_in_guard). ?comp(make_effect_seq). ?comp(eval_is_boolean). ?comp(unsafe_case). diff --git a/lib/compiler/test/core_SUITE_data/seq_in_guard.core b/lib/compiler/test/core_SUITE_data/seq_in_guard.core new file mode 100644 index 0000000000..44686a7187 --- /dev/null +++ b/lib/compiler/test/core_SUITE_data/seq_in_guard.core @@ -0,0 +1,66 @@ +module 'seq_in_guard' ['seq_in_guard'/0, + 't'/1] + attributes [] +'seq_in_guard'/0 = + %% Line 4 + fun () -> + case <> of + <> when 'true' -> + let <_cor0> = + catch + %% Line 5 + apply 't'/1 + ({}) + in %% Line 5 + case _cor0 of + <{'EXIT',{'function_clause',_cor4}}> when 'true' -> + let <_cor2> = + catch + %% Line 6 + apply 't'/1 + ('atom') + in %% Line 6 + case _cor2 of + <{'EXIT',{'function_clause',_cor5}}> when 'true' -> + %% Line 7 + apply 't'/1 + ({'a','b'}) + ( <_cor3> when 'true' -> + primop 'match_fail' + ({'badmatch',_cor3}) + -| ['compiler_generated'] ) + end + ( <_cor1> when 'true' -> + primop 'match_fail' + ({'badmatch',_cor1}) + -| ['compiler_generated'] ) + end + ( <> when 'true' -> + ( primop 'match_fail' + ({'function_clause'}) + -| [{'function_name',{'seq_in_guard',0}}] ) + -| ['compiler_generated'] ) + end +'t'/1 = + %% Line 9 + fun (_cor0) -> + case _cor0 of + <X> + when try + do + call 'erlang':'element' + (2, X) + 'true' + of <Try> -> + Try + catch <T,R> -> + 'false' -> + %% Line 10 + 'ok' + ( <_cor3> when 'true' -> + ( primop 'match_fail' + ({'function_clause',_cor3}) + -| [{'function_name',{'t',1}}] ) + -| ['compiler_generated'] ) + end +end diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index fb5ec88c9f..54bd52947e 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -21,7 +21,7 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 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,coverage/1]). + eq/1,nested_call_in_case/1,guard_try_catch/1,coverage/1]). -export([foo/0,foo/1,foo/2,foo/3]). @@ -32,7 +32,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> test_lib:recompile(?MODULE), [t_element, setelement, t_length, append, t_apply, bifs, - eq, nested_call_in_case, coverage]. + eq, nested_call_in_case, guard_try_catch, coverage]. groups() -> []. @@ -207,6 +207,23 @@ nested_call_in_case(Config) when is_list(Config) -> ?line {'EXIT',_} = (catch Mod:a(not_a_list, 42)), ok. +guard_try_catch(_Config) -> + false = do_guard_try_catch(key, value), + value = get(key), + ok. + +do_guard_try_catch(K, V) -> + %% This try...catch block looks like a guard. + %% Make sure that it is not optimized like a guard + %% (the put/2 call must not be optimized away). + try + put(K, V), + false + catch + _:_ -> + false + end. + coverage(Config) when is_list(Config) -> ?line {'EXIT',{{case_clause,{a,b,c}},_}} = (catch cover_will_match_list_type({a,b,c})), diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index 458f3a4c81..5be870d78f 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -170,33 +170,32 @@ analysis_start(Parent, Analysis) -> rcv_and_send_ext_types(Parent), NonExports = sets:subtract(sets:from_list(AllNodes), Exports), NonExportsList = sets:to_list(NonExports), - Plt3 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList), - Plt4 = dialyzer_plt:delete_contract_list(Plt3, NonExportsList), + Plt2 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList), send_codeserver_plt(Parent, CServer, State3#analysis_state.plt), - send_analysis_done(Parent, Plt4, State3#analysis_state.doc_plt). + send_analysis_done(Parent, Plt2, State3#analysis_state.doc_plt). analyze_callgraph(Callgraph, State) -> Codeserver = State#analysis_state.codeserver, - Plt = dialyzer_plt:insert_callbacks(State#analysis_state.plt, Codeserver), Parent = State#analysis_state.parent, - case State#analysis_state.analysis_type of - plt_build -> - Callgraph1 = dialyzer_callgraph:finalize(Callgraph), - NewPlt = dialyzer_succ_typings:analyze_callgraph(Callgraph1, Plt, - Codeserver, Parent), - dialyzer_callgraph:delete(Callgraph1), - State#analysis_state{plt = NewPlt}; - succ_typings -> - NoWarn = State#analysis_state.no_warn_unused, - DocPlt = State#analysis_state.doc_plt, - Callgraph1 = dialyzer_callgraph:finalize(Callgraph), - {Warnings, NewPlt, NewDocPlt} = - dialyzer_succ_typings:get_warnings(Callgraph1, Plt, DocPlt, - Codeserver, NoWarn, Parent), - dialyzer_callgraph:delete(Callgraph1), - send_warnings(State#analysis_state.parent, Warnings), - State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt} - end. + DocPlt = State#analysis_state.doc_plt, + Plt = dialyzer_plt:insert_callbacks(State#analysis_state.plt, Codeserver), + Callgraph1 = dialyzer_callgraph:finalize(Callgraph), + {NewPlt, NewDocPlt} = + case State#analysis_state.analysis_type of + plt_build -> + {dialyzer_succ_typings:analyze_callgraph(Callgraph1, Plt, + Codeserver, Parent), + DocPlt}; + succ_typings -> + NoWarn = State#analysis_state.no_warn_unused, + {Warnings, NewPlt0, NewDocPlt0} = + dialyzer_succ_typings:get_warnings(Callgraph1, Plt, DocPlt, + Codeserver, NoWarn, Parent), + send_warnings(State#analysis_state.parent, Warnings), + {NewPlt0, NewDocPlt0} + end, + dialyzer_callgraph:delete(Callgraph1), + State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt}. %%-------------------------------------------------------------------- %% Build the callgraph and fill the codeserver. @@ -282,10 +281,10 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, if ExtCalls1 =:= [] -> {[], []}; true -> ModuleSet = sets:from_list(Modules), - lists:partition(fun({_From, {M, _F, _A}}) -> - sets:is_element(M, ModuleSet) orelse - dialyzer_plt:contains_module(InitPlt, M) - end, ExtCalls1) + PltModuleSet = dialyzer_plt:all_modules(InitPlt), + AllModules = sets:union(ModuleSet, PltModuleSet), + Pred = fun({_From, {M, _F, _A}}) -> sets:is_element(M, AllModules) end, + lists:partition(Pred, ExtCalls1) end, NonLocalCalls = dialyzer_callgraph:non_local_calls(Callgraph1), BadCalls2 = [Call || Call = {_From, To} <- NonLocalCalls, @@ -366,9 +365,11 @@ abs_get_nowarn(Abs, M) -> false -> [{M, F, A} || {function, _, F, A, _} <- Abs]; % all functions true -> - [{M, F, A} || - {nowarn_unused_function, FAs} <- Opts, - {F, A} <- lists:flatten([FAs])] + OnLoad = + lists:flatten([{M, F, A} || {attribute, _, on_load, {F, A}} <- Abs]), + OnLoad ++ [{M, F, A} || + {nowarn_unused_function, FAs} <- Opts, + {F, A} <- lists:flatten([FAs])] end. get_exported_types_from_core(Core) -> diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl index e89c08df7d..fdaa8b663c 100644 --- a/lib/dialyzer/src/dialyzer_behaviours.erl +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -72,9 +72,11 @@ check_callbacks(Module, Attrs, Plt, Codeserver) -> %%-------------------------------------------------------------------- get_behaviours(Attrs) -> - BehaviourListsAndLine = [{cerl:concrete(L2), hd(cerl:get_ann(L2))} || - {L1, L2} <- Attrs, cerl:is_literal(L1), - cerl:is_literal(L2), cerl:concrete(L1) =:= 'behaviour'], + BehaviourListsAndLine = + [{cerl:concrete(L2), hd(cerl:get_ann(L2))} || + {L1, L2} <- Attrs, cerl:is_literal(L1), + cerl:is_literal(L2), cerl:concrete(L1) =:= 'behaviour' orelse + cerl:concrete(L1) =:= 'behavior'], Behaviours = lists:append([Behs || {Behs,_} <- BehaviourListsAndLine]), BehLines = [{B,L} || {L1,L} <- BehaviourListsAndLine, B <- L1], {Behaviours, BehLines}. @@ -239,12 +241,12 @@ translatable_behaviours(Tree) -> get_behaviour_apis(Behaviours) -> get_behaviour_apis(Behaviours, []). --spec translate_behaviour_api_call(dialyzer_races:mfa_or_funlbl(), +-spec translate_behaviour_api_call(dialyzer_callgraph:mfa_or_funlbl(), [erl_types:erl_type()], [dialyzer_races:core_vars()], module(), behaviour_api_dict()) -> - {dialyzer_races:mfa_or_funlbl(), + {dialyzer_callgraph:mfa_or_funlbl(), [erl_types:erl_type()], [dialyzer_races:core_vars()]} | 'plain_call'. diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl index d3de5aaf45..4767c02f77 100644 --- a/lib/dialyzer/src/dialyzer_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_callgraph.erl @@ -59,7 +59,7 @@ put_named_tables/2, put_public_tables/2, put_behaviour_api_calls/2, get_behaviour_api_calls/1]). --export_type([callgraph/0]). +-export_type([callgraph/0, mfa_or_funlbl/0]). -include("dialyzer.hrl"). @@ -177,14 +177,14 @@ is_escaping(Label, #callgraph{esc = Esc}) when is_integer(Label) -> add_edges([], CG) -> CG; -add_edges(Edges, #callgraph{digraph = Callgraph} = CG) -> - CG#callgraph{digraph = digraph_add_edges(Edges, Callgraph)}. +add_edges(Edges, #callgraph{digraph = Digraph} = CG) -> + CG#callgraph{digraph = digraph_add_edges(Edges, Digraph)}. -spec add_edges([callgraph_edge()], [mfa_or_funlbl()], callgraph()) -> callgraph(). add_edges(Edges, MFAs, #callgraph{digraph = DG} = CG) -> - DG1 = digraph_confirm_vertices(MFAs, DG), - add_edges(Edges, CG#callgraph{digraph = DG1}). + DG = digraph_confirm_vertices(MFAs, DG), + add_edges(Edges, CG). -spec take_scc(callgraph()) -> 'none' | {'ok', scc(), callgraph()}. @@ -196,8 +196,8 @@ take_scc(#callgraph{postorder = []}) -> -spec remove_external(callgraph()) -> {callgraph(), [tuple()]}. remove_external(#callgraph{digraph = DG} = CG) -> - {NewDG, External} = digraph_remove_external(DG), - {CG#callgraph{digraph = NewDG}, External}. + {DG, External} = digraph_remove_external(DG), + {CG, External}. -spec non_local_calls(callgraph()) -> mfa_calls(). @@ -241,30 +241,30 @@ modules(#callgraph{digraph = DG}) -> -spec module_postorder(callgraph()) -> [[module()]]. module_postorder(#callgraph{digraph = DG}) -> + {MDG, _Nodes} = get_module_digraph_and_nodes(DG), + MDG1 = digraph_utils:condensation(MDG), + PostOrder = digraph_utils:postorder(MDG1), + PostOrder1 = sort_sccs_internally(PostOrder, MDG), + digraph:delete(MDG1), + digraph_delete(MDG), + PostOrder1. + +get_module_digraph_and_nodes(DG) -> Edges = digraph_edges(DG), Nodes = ordsets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]), MDG = digraph:new(), - MDG1 = digraph_confirm_vertices(Nodes, MDG), - MDG2 = create_module_digraph(Edges, MDG1), - MDG3 = digraph_utils:condensation(MDG2), - PostOrder = digraph_utils:postorder(MDG3), - PostOrder1 = sort_sccs_internally(PostOrder, MDG2), - digraph:delete(MDG2), - digraph_delete(MDG3), - PostOrder1. + MDG = digraph_confirm_vertices(Nodes, MDG), + MDG = create_module_digraph(Edges, MDG), + {MDG, Nodes}. %% The module deps of a module are modules that depend on the module -spec module_deps(callgraph()) -> dict(). module_deps(#callgraph{digraph = DG}) -> - Edges = digraph_edges(DG), - Nodes = ordsets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]), - MDG = digraph:new(), - MDG1 = digraph_confirm_vertices(Nodes, MDG), - MDG2 = create_module_digraph(Edges, MDG1), - Deps = [{N, ordsets:from_list(digraph:in_neighbours(MDG2, N))} + {MDG, Nodes} = get_module_digraph_and_nodes(DG), + Deps = [{N, ordsets:from_list(digraph:in_neighbours(MDG, N))} || N <- Nodes], - digraph_delete(MDG2), + digraph_delete(MDG), dict:from_list(Deps). -spec strip_module_deps(dict(), set()) -> dict(). diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index 13ca65e4dd..f1e87affbd 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -292,15 +292,11 @@ table__loop(Cached, Map) -> {NewCached, Ans} = case Cached of {M, Tree} -> - [Val] = [VarFun || {Var, _Fun} = VarFun <- cerl:module_defs(Tree), - cerl:fname_id(Var) =:= F, - cerl:fname_arity(Var) =:= A], + Val = find_fun(F, A, Tree), {Cached, Val}; _ -> Tree = fetch_and_expand(M, Map), - [Val] = [VarFun || {Var, _Fun} = VarFun <- cerl:module_defs(Tree), - cerl:fname_id(Var) =:= F, - cerl:fname_arity(Var) =:= A], + Val = find_fun(F, A, Tree), {{M, Tree}, Val} end, Pid ! {self(), MFA, Ans}, @@ -329,3 +325,12 @@ fetch_and_expand(Mod, Map) -> Msg = "found no module named '" ++ S ++ "' in the analyzed files", exit({error, Msg}) end. + +find_fun(F, A, Tree) -> + Pred = + fun({Var, _Fun}) -> + (cerl:fname_id(Var) =/= F) orelse + (cerl:fname_arity(Var) =/= A) + end, + [Val|_] = lists:dropwhile(Pred, cerl:module_defs(Tree)), + Val. diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 6008dba080..aba13278ff 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -67,7 +67,6 @@ %%-define(DEBUG, true). %%-define(DEBUG_PP, true). -%%-define(DEBUG_TIME, true). %%-define(DOT, true). -ifdef(DEBUG). @@ -77,9 +76,6 @@ -define(debug(S_, L_), ok). -endif. -%%-define(debug1(S_, L_), io:format(S_, L_)). -%%-define(debug1(S_, L_), ok). - %%-------------------------------------------------------------------- -define(no_arg, no_arg). @@ -276,6 +272,7 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> debug_pp(Tree, false), Module = cerl:atom_val(cerl:module_name(Tree)), RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph), + RaceCode = dialyzer_callgraph:get_race_code(Callgraph), BehaviourTranslations = case RaceDetection of true -> dialyzer_behaviours:translatable_behaviours(Tree); @@ -286,9 +283,6 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> TopFun, Plt, Module, Records, BehaviourTranslations), State1 = state__race_analysis(not GetWarnings, State), State2 = analyze_loop(State1), - RaceCode = dialyzer_callgraph:get_race_code(Callgraph), - Callgraph1 = State2#state.callgraph, - RaceCode1 = dialyzer_callgraph:get_race_code(Callgraph1), case GetWarnings of true -> State3 = state__set_warning_mode(State2), @@ -313,6 +307,8 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> St#state{callgraph = Callgraph3} end; false -> + Callgraph1 = State2#state.callgraph, + RaceCode1 = dialyzer_callgraph:get_race_code(Callgraph1), state__restore_race_code( dict:merge(fun (_K, V1, _V2) -> V1 end, RaceCode, RaceCode1), State2) @@ -320,27 +316,26 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> analyze_loop(#state{callgraph = Callgraph, races = Races} = State) -> case state__get_work(State) of - none -> state__clean_not_called(State); - {Fun, NewState} -> - ArgTypes = state__get_args(Fun, NewState), - case any_none(ArgTypes) of + none -> State; + {Fun, NewState1} -> + {ArgTypes, IsCalled} = state__get_args_and_status(Fun, NewState1), + case not IsCalled of true -> - ?debug("Not handling1 ~w: ~s\n", + ?debug("Not handling (not called) ~w: ~s\n", [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), - analyze_loop(NewState); + analyze_loop(NewState1); false -> - case state__fun_env(Fun, NewState) of + case state__fun_env(Fun, NewState1) of none -> - ?debug("Not handling2 ~w: ~s\n", + ?debug("Not handling (no env) ~w: ~s\n", [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), - analyze_loop(NewState); + analyze_loop(NewState1); Map -> ?debug("Handling fun ~p: ~s\n", [state__lookup_name(get_label(Fun), State), - t_to_string(state__fun_type(Fun, NewState))]), - NewState1 = state__mark_fun_as_handled(NewState, Fun), + t_to_string(state__fun_type(Fun, NewState1))]), Vars = cerl:fun_vars(Fun), Map1 = enter_type_lists(Vars, ArgTypes, Map), Body = cerl:fun_body(Fun), @@ -2892,28 +2887,22 @@ state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) -> TreeMap = build_tree_map(Tree), Funs = dict:fetch_keys(TreeMap), FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques), - Work = init_work([get_label(Tree)]), - Env = dict:store(top, map__new(), dict:new()), + ExportedFuns = + [Fun || Fun <- Funs--[top], dialyzer_callgraph:is_escaping(Fun, Callgraph)], + Work = init_work(ExportedFuns), + Env = lists:foldl(fun(Fun, Env) -> dict:store(Fun, map__new(), Env) end, + dict:new(), Funs), #state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques, plt = Plt, races = dialyzer_races:new(), records = Records, warning_mode = false, warnings = [], work = Work, tree_map = TreeMap, module = Module, behaviour_api_dict = BehaviourTranslations}. -state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) -> - Fun = get_label(Fun0), - case dict:find(Fun, FunTab) of - {ok, {not_handled, Entry}} -> - State#state{fun_tab = dict:store(Fun, Entry, FunTab)}; - {ok, {_, _}} -> - State - end. - state__warning_mode(#state{warning_mode = WM}) -> WM. state__set_warning_mode(#state{tree_map = TreeMap, fun_tab = FunTab, races = Races} = State) -> - ?debug("Starting warning pass\n", []), + ?debug("==========\nStarting warning pass\n==========\n", []), Funs = dict:fetch_keys(TreeMap), State#state{work = init_work([top|Funs--[top]]), fun_tab = FunTab, warning_mode = true, @@ -2985,7 +2974,7 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, {NotCalled, Ret} = case dict:fetch(get_label(Fun), FunTab) of {not_handled, {_Args0, Ret0}} -> {true, Ret0}; - {Args0, Ret0} -> {any_none(Args0), Ret0} + {_Args0, Ret0} -> {false, Ret0} end, case NotCalled of true -> @@ -3079,11 +3068,11 @@ state__lookup_record(Tag, Arity, #state{records = Records}) -> error end. -state__get_args(Tree, #state{fun_tab = FunTab}) -> +state__get_args_and_status(Tree, #state{fun_tab = FunTab}) -> Fun = get_label(Tree), case dict:find(Fun, FunTab) of - {ok, {not_handled, {ArgTypes, _}}} -> ArgTypes; - {ok, {ArgTypes, _}} -> ArgTypes + {ok, {not_handled, {ArgTypes, _}}} -> {ArgTypes, false}; + {ok, {ArgTypes, _}} -> {ArgTypes, true} end. build_tree_map(Tree) -> @@ -3099,7 +3088,7 @@ build_tree_map(Tree) -> cerl_trees:fold(Fun, dict:new(), Tree). init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> - NewDict = dict:store(top, {not_handled, {[], t_none()}}, Dict), + NewDict = dict:store(top, {[], t_none()}, Dict), init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> Arity = cerl:fun_arity(dict:fetch(Fun, TreeMap)), @@ -3115,9 +3104,9 @@ init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> false -> {Args, t_unit()} end end; - false -> {lists:duplicate(Arity, t_none()), t_unit()} + false -> {not_handled, {lists:duplicate(Arity, t_none()), t_unit()}} end, - NewDict = dict:store(Fun, {not_handled, FunEntry}, Dict), + NewDict = dict:store(Fun, FunEntry, Dict), init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt, _Opaques) -> Dict. @@ -3141,7 +3130,8 @@ state__clean_not_called(#state{fun_tab = FunTab} = State) -> end, FunTab), State#state{fun_tab = NewFunTab}. -state__all_fun_types(#state{fun_tab = FunTab}) -> +state__all_fun_types(State) -> + #state{fun_tab = FunTab} = state__clean_not_called(State), Tab1 = dict:erase(top, FunTab), dict:map(fun(_Fun, {Args, Ret}) -> t_fun(Args, Ret)end, Tab1). diff --git a/lib/dialyzer/src/dialyzer_gui.erl b/lib/dialyzer/src/dialyzer_gui.erl index f60194e01f..bac659548f 100644 --- a/lib/dialyzer/src/dialyzer_gui.erl +++ b/lib/dialyzer/src/dialyzer_gui.erl @@ -511,6 +511,16 @@ gui_loop(#gui_state{add_all = AddAll, add_file = AddFile, add_rec = AddRec, [ExtCalls]), free_editor(State, "Analysis done", Msg), gui_loop(State); + {BackendPid, ext_types, ExtTypes} -> + Map = fun({M,F,A}) -> io_lib:format("~p:~p/~p",[M,F,A]) end, + ExtTypeString = string:join(lists:map(Map, ExtTypes), "\n"), + Msg = io_lib:format("The following remote types are being used " + "but information about them is not available.\n" + "The analysis might get more precise by including " + "the modules containing these types and making sure " + "that they are exported:\n~s\n", [ExtTypeString]), + free_editor(State, "Analysis done", Msg), + gui_loop(State); {BackendPid, log, LogMsg} -> update_editor(Log, LogMsg), gui_loop(State); diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl index e711c15ea7..9ff32bd8b1 100644 --- a/lib/dialyzer/src/dialyzer_gui_wx.erl +++ b/lib/dialyzer/src/dialyzer_gui_wx.erl @@ -503,6 +503,16 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, [ExtCalls]), free_editor(State,"Analysis Done", Msg), gui_loop(State); + {BackendPid, ext_types, ExtTypes} -> + Map = fun({M,F,A}) -> io_lib:format("~p:~p/~p",[M,F,A]) end, + ExtTypeString = string:join(lists:map(Map, ExtTypes), "\n"), + Msg = io_lib:format("The following remote types are being used " + "but information about them is not available.\n" + "The analysis might get more precise by including " + "the modules containing these types and making sure " + "that they are exported:\n~s\n", [ExtTypeString]), + free_editor(State, "Analysis done", Msg), + gui_loop(State); {BackendPid, log, LogMsg} -> update_editor(Log, LogMsg), gui_loop(State); diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index 206c43e4e2..74892d1668 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -31,8 +31,7 @@ -export([check_plt/3, compute_md5_from_files/1, contains_mfa/2, - contains_module/2, - delete_contract_list/2, + all_modules/1, delete_list/2, delete_module/2, included_files/1, @@ -153,10 +152,8 @@ lookup_contract(#plt{contracts = Contracts}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> table_lookup(Contracts, MFA). --spec lookup_callbacks(plt(), module()) -> [{mfa(), - {{Filename::string(), - Line::pos_integer()}, - #contract{}}}]. +-spec lookup_callbacks(plt(), module()) -> + [{mfa(), {{Filename::string(), Line::pos_integer()}, #contract{}}}]. lookup_callbacks(#plt{callbacks = Callbacks}, Mod) when is_atom(Mod) -> FunModFilter = @@ -166,18 +163,6 @@ lookup_callbacks(#plt{callbacks = Callbacks}, Mod) when is_atom(Mod) -> ModCallbacks = dict:filter(FunModFilter, Callbacks), dict:to_list(ModCallbacks). --spec delete_contract_list(plt(), [mfa()]) -> plt(). - -delete_contract_list(#plt{contracts = Contracts, - callbacks = Callbacks} = PLT, List) -> - PLT#plt{contracts = table_delete_list(Contracts, List), - callbacks = table_delete_list(Callbacks, List)}. - -%% -spec insert(plt(), mfa() | integer(), {_, _}) -> plt(). -%% -%% insert(#plt{info = Info} = PLT, Id, Types) -> -%% PLT#plt{info = table_insert(Info, Id, Types)}. - -type ret_args_types() :: {erl_types:erl_type(), [erl_types:erl_type()]}. -spec insert_list(plt(), [{mfa() | integer(), ret_args_types()}]) -> plt(). @@ -220,10 +205,10 @@ get_exported_types(#plt{exported_types = ExpTypes}) -> lookup_module(#plt{info = Info}, M) when is_atom(M) -> table_lookup_module(Info, M). --spec contains_module(plt(), atom()) -> boolean(). +-spec all_modules(plt()) -> set(). -contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) -> - table_contains_module(Info, M) orelse table_contains_module(Cs, M). +all_modules(#plt{info = Info, contracts = Cs}) -> + sets:union(table_all_modules(Info), table_all_modules(Cs)). -spec contains_mfa(plt(), mfa()) -> boolean(). @@ -623,10 +608,12 @@ table_lookup_module(Plt, Mod) -> false -> {value, List} end. -table_contains_module(Plt, Mod) -> - dict:fold(fun({M, _F, _A}, _Val, _Acc) when M =:= Mod -> true; - (_, _, Acc) -> Acc - end, false, Plt). +table_all_modules(Plt) -> + Fold = + fun({M, _F, _A}, _Val, Acc) -> sets:add_element(M, Acc); + (_, _, Acc) -> Acc + end, + dict:fold(Fold, sets:new(), Plt). table_merge([H|T]) -> table_merge(T, H). diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl index ee9d5e88a3..b9594f5301 100644 --- a/lib/dialyzer/src/dialyzer_races.erl +++ b/lib/dialyzer/src/dialyzer_races.erl @@ -39,7 +39,7 @@ let_tag_new/2, new/0, put_curr_fun/3, put_fun_args/2, put_race_analysis/2, put_race_list/3]). --export_type([races/0, mfa_or_funlbl/0, core_vars/0]). +-export_type([races/0, core_vars/0]). -include("dialyzer.hrl"). @@ -66,8 +66,6 @@ %%% %%% =========================================================================== --type mfa_or_funlbl() :: label() | mfa(). - -type label_type() :: label() | [label()] | {label()} | ?no_label. -type args() :: [label_type() | [string()]]. -type core_vars() :: cerl:cerl() | ?no_arg | ?bypassed. @@ -94,7 +92,7 @@ guard :: cerl:cerl()}). -record(end_case, {clauses :: [#end_clause{}]}). -record(curr_fun, {status :: 'in' | 'out', - mfa :: mfa_or_funlbl(), + mfa :: dialyzer_callgraph:mfa_or_funlbl(), label :: label(), def_vars :: [core_vars()], arg_types :: [erl_types:erl_type()], @@ -107,8 +105,8 @@ state :: _, %% XXX: recursive file_line :: file_line(), var_map :: dict()}). --record(fun_call, {caller :: mfa_or_funlbl(), - callee :: mfa_or_funlbl(), +-record(fun_call, {caller :: dialyzer_callgraph:mfa_or_funlbl(), + callee :: dialyzer_callgraph:mfa_or_funlbl(), arg_types :: [erl_types:erl_type()], vars :: [core_vars()]}). -record(let_tag, {var :: var_to_map1(), @@ -130,10 +128,10 @@ vars :: [core_vars()], file_line :: file_line(), index :: non_neg_integer(), - fun_mfa :: mfa_or_funlbl(), + fun_mfa :: dialyzer_callgraph:mfa_or_funlbl(), fun_label :: label()}). --record(races, {curr_fun :: mfa_or_funlbl(), +-record(races, {curr_fun :: dialyzer_callgraph:mfa_or_funlbl(), curr_fun_label :: label(), curr_fun_args = 'empty' :: core_args(), new_table = 'no_t' :: table(), @@ -158,7 +156,8 @@ %%% %%% =========================================================================== --spec store_race_call(mfa_or_funlbl(), [erl_types:erl_type()], [core_vars()], +-spec store_race_call(dialyzer_callgraph:mfa_or_funlbl(), + [erl_types:erl_type()], [core_vars()], file_line(), dialyzer_dataflow:state()) -> dialyzer_dataflow:state(). @@ -2405,7 +2404,7 @@ end_case_new(Clauses) -> end_clause_new(Arg, Pats, Guard) -> #end_clause{arg = Arg, pats = Pats, guard = Guard}. --spec get_curr_fun(races()) -> mfa_or_funlbl(). +-spec get_curr_fun(races()) -> dialyzer_callgraph:mfa_or_funlbl(). get_curr_fun(#races{curr_fun = CurrFun}) -> CurrFun. @@ -2444,7 +2443,7 @@ let_tag_new(Var, Arg) -> new() -> #races{}. --spec put_curr_fun(mfa_or_funlbl(), label(), races()) -> +-spec put_curr_fun(dialyzer_callgraph:mfa_or_funlbl(), label(), races()) -> races(). put_curr_fun(CurrFun, CurrFunLabel, Races) -> diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl index 4d86bb34a7..de4c8a32a3 100644 --- a/lib/dialyzer/src/dialyzer_succ_typings.erl +++ b/lib/dialyzer/src/dialyzer_succ_typings.erl @@ -204,7 +204,7 @@ refine_one_module(M, State) -> #st{callgraph = Callgraph, codeserver = CodeServer, plt = PLT} = State, ModCode = dialyzer_codeserver:lookup_mod_code(M, CodeServer), AllFuns = collect_fun_info([ModCode]), - FunTypes = get_fun_types_from_plt(AllFuns, State), + FunTypes = get_fun_types_from_plt(AllFuns, Callgraph, PLT), Records = dialyzer_codeserver:lookup_mod_records(M, CodeServer), {NewFunTypes, RaceCode, PublicTables, NamedTables} = dialyzer_dataflow:get_fun_types(ModCode, PLT, Callgraph, Records), @@ -321,7 +321,9 @@ find_succ_typings(#st{callgraph = Callgraph, parent = Parent} = State, end end. -analyze_scc(SCC, #st{codeserver = Codeserver} = State) -> +analyze_scc(SCC, #st{codeserver = Codeserver, + callgraph = Callgraph, + plt = Plt} = State) -> SCC_Info = [{MFA, dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver), dialyzer_codeserver:lookup_mod_records(M, Codeserver)} @@ -330,23 +332,19 @@ analyze_scc(SCC, #st{codeserver = Codeserver} = State) -> || {_, _, _} = MFA <- SCC], Contracts2 = [{MFA, Contract} || {MFA, {ok, Contract}} <- Contracts1], Contracts3 = orddict:from_list(Contracts2), + NextLabel = dialyzer_codeserver:get_next_core_label(Codeserver), {SuccTypes, PltContracts, NotFixpoint} = - find_succ_types_for_scc(SCC_Info, Contracts3, State), + find_succ_types_for_scc(SCC_Info, Contracts3, NextLabel, Callgraph, Plt), State1 = insert_into_plt(SuccTypes, State), ContrPlt = dialyzer_plt:insert_contract_list(State1#st.plt, PltContracts), {State1#st{plt = ContrPlt}, NotFixpoint}. -find_succ_types_for_scc(SCC_Info, Contracts, - #st{codeserver = Codeserver, - callgraph = Callgraph, plt = Plt} = State) -> +find_succ_types_for_scc(SCC_Info, Contracts, NextLabel, Callgraph, Plt) -> %% Assume that the PLT contains the current propagated types AllFuns = collect_fun_info([Fun || {_MFA, {_Var, Fun}, _Rec} <- SCC_Info]), - PropTypes = get_fun_types_from_plt(AllFuns, State), - MFAs = [MFA || {MFA, {_Var, _Fun}, _Rec} <- SCC_Info], - NextLabel = dialyzer_codeserver:get_next_core_label(Codeserver), - Plt1 = dialyzer_plt:delete_contract_list(Plt, MFAs), + PropTypes = get_fun_types_from_plt(AllFuns, Callgraph, Plt), FunTypes = dialyzer_typesig:analyze_scc(SCC_Info, NextLabel, - Callgraph, Plt1, PropTypes), + Callgraph, Plt, PropTypes), AllFunSet = sets:from_list([X || {X, _} <- AllFuns]), FilteredFunTypes = dict:filter(fun(X, _) -> sets:is_element(X, AllFunSet) @@ -372,13 +370,13 @@ find_succ_types_for_scc(SCC_Info, Contracts, ordsets:from_list([Fun || {Fun, _Arity} <- AllFuns])} end. -get_fun_types_from_plt(FunList, State) -> - get_fun_types_from_plt(FunList, State, dict:new()). +get_fun_types_from_plt(FunList, Callgraph, Plt) -> + get_fun_types_from_plt(FunList, Callgraph, Plt, dict:new()). -get_fun_types_from_plt([{FunLabel, Arity}|Left], State, Map) -> - Type = lookup_fun_type(FunLabel, Arity, State), - get_fun_types_from_plt(Left, State, dict:store(FunLabel, Type, Map)); -get_fun_types_from_plt([], _State, Map) -> +get_fun_types_from_plt([{FunLabel, Arity}|Left], Callgraph, Plt, Map) -> + Type = lookup_fun_type(FunLabel, Arity, Callgraph, Plt), + get_fun_types_from_plt(Left, Callgraph, Plt, dict:store(FunLabel, Type, Map)); +get_fun_types_from_plt([], _Callgraph, _Plt, Map) -> Map. collect_fun_info(Trees) -> @@ -396,7 +394,7 @@ collect_fun_info([Tree|Trees], List) -> collect_fun_info([], List) -> List. -lookup_fun_type(Label, Arity, #st{callgraph = Callgraph, plt = Plt}) -> +lookup_fun_type(Label, Arity, Callgraph, Plt) -> ID = lookup_name(Label, Callgraph), case dialyzer_plt:lookup(Plt, ID) of none -> erl_types:t_fun(Arity, erl_types:t_any()); diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index 4268814859..04ff8e4941 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -91,22 +91,24 @@ -type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, dict()}]. -type typesig_funmap() :: [{type_var(), type_var()}]. %% Orddict --record(state, {callgraph :: dialyzer_callgraph:callgraph(), - cs = [] :: [constr()], - cmap = dict:new() :: dict(), - fun_map = [] :: typesig_funmap(), - fun_arities = dict:new() :: dict(), - in_match = false :: boolean(), - in_guard = false :: boolean(), - module :: module(), - name_map = dict:new() :: dict(), - next_label :: label(), - non_self_recs = [] :: [label()], - plt :: dialyzer_plt:plt(), - prop_types = dict:new() :: dict(), - records = dict:new() :: dict(), - opaques = [] :: [erl_types:erl_type()], - scc = [] :: [type_var()]}). +-record(state, {callgraph :: dialyzer_callgraph:callgraph(), + cs = [] :: [constr()], + cmap = dict:new() :: dict(), + fun_map = [] :: typesig_funmap(), + fun_arities = dict:new() :: dict(), + in_match = false :: boolean(), + in_guard = false :: boolean(), + module :: module(), + name_map = dict:new() :: dict(), + next_label :: label(), + self_recs :: [label()], + plt :: dialyzer_plt:plt(), + prop_types = dict:new() :: dict(), + records = dict:new() :: dict(), + opaques = [] :: [erl_types:erl_type()], + scc = [] :: [type_var()], + mfas = [] :: [dialyzer_callgraph:mfa_or_funlbl()] + }). %%----------------------------------------------------------------------------- @@ -448,7 +450,8 @@ traverse(Tree, DefinedVars, State) -> %% Check if a record is constructed. _ -> Arity = length(Fields), - case state__lookup_record(State2, cerl:atom_val(Tag), Arity) of + Records = State2#state.records, + case lookup_record(Records, cerl:atom_val(Tag), Arity) of error -> {State2, TupleType}; {ok, RecType} -> State3 = state__store_conj(TupleType, sub, RecType, State2), @@ -646,8 +649,14 @@ get_plt_constr(MFA, Dst, ArgVars, State) -> PltRes = dialyzer_plt:lookup(Plt, MFA), Opaques = State#state.opaques, Module = State#state.module, + SCCMFAs = State#state.mfas, {FunModule, _, _} = MFA, - case dialyzer_plt:lookup_contract(Plt, MFA) of + Contract = + case lists:member(MFA, SCCMFAs) of + true -> none; + false -> dialyzer_plt:lookup_contract(Plt, MFA) + end, + case Contract of none -> case PltRes of none -> State; @@ -1246,6 +1255,8 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) -> mk_constraint(Var, sub, ArgV)]); get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> %% TODO: Revise this to make it precise for Tag and Arity. + Records = State#state.records, + AllOpaques = State#state.opaques, ArgFun = fun(Map) -> case t_is_atom(true, lookup_type(Dst, Map)) of @@ -1262,10 +1273,8 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> GenRecord = t_tuple([TagType|AnyElems]), case t_atom_vals(TagType) of [TagVal] -> - case state__lookup_record(State, TagVal, - ArityVal - 1) of + case lookup_record(Records, TagVal, ArityVal - 1) of {ok, Type} -> - AllOpaques = State#state.opaques, case t_opaque_match_record(Type, AllOpaques) of [Opaque] -> Opaque; _ -> Type @@ -1287,7 +1296,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> DstFun = fun(Map) -> [TmpVar, TmpTag, TmpArity] = TmpArgTypes = lookup_type_list(Args, Map), TmpArgTypes2 = - case lists:member(TmpVar, State#state.opaques) of + case lists:member(TmpVar, AllOpaques) of true -> case t_is_integer(TmpArity) of true -> @@ -1297,7 +1306,8 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) -> true -> case t_atom_vals(TmpTag) of [TmpTagVal] -> - case state__lookup_record(State, TmpTagVal, TmpArityVal - 1) of + case lookup_record(Records, TmpTagVal, + TmpArityVal - 1) of {ok, TmpType} -> case t_is_none(t_inf(TmpType, TmpVar, opaque)) of true -> TmpArgTypes; @@ -1526,9 +1536,10 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, case t_is_none(GenType) of true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error); false -> + Opaques = State#state.opaques, Fun = fun(Map) -> [I, T] = ATs = lookup_type_list(Args, Map), - ATs2 = case lists:member(T, State#state.opaques) of + ATs2 = case lists:member(T, Opaques) of true -> [I, erl_types:t_opaque_structure(T)]; false -> ATs end, @@ -1546,7 +1557,7 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, end; get_bif_constr({M, F, A} = _BIF, Dst, Args, State) -> GenType = erl_bif_types:type(M, F, A), - Opaques = State#state.opaques, + Opaques = State#state.opaques, case t_is_none(GenType) of true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error); false -> @@ -1612,11 +1623,12 @@ get_bif_test_constr(Dst, Arg, Type, State) -> end end, ArgV = ?mk_fun_var(ArgFun, [Dst]), + Opaques = State#state.opaques, DstFun = fun(Map) -> ArgType = lookup_type(Arg, Map), case t_is_none(t_inf(ArgType, Type)) of true -> - case lists:member(ArgType, State#state.opaques) of + case lists:member(ArgType, Opaques) of true -> OpaqueStruct = erl_types:t_opaque_structure(ArgType), case t_is_none(t_inf(OpaqueStruct, Type)) of @@ -1743,33 +1755,29 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps}, true -> solve_self_recursive(Cs, Map, MapDict, Id, t_none(), State); false -> solve_ref_or_list(Cs, Map, MapDict, State) end, - case Res of - {error, NewMapDict} -> - ?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]), - Arity = state__fun_arity(Id, State), - FunType = - case state__prop_domain(t_var_name(Id), State) of - error -> t_fun(Arity, t_none()); - {ok, Dom} -> t_fun(Dom, t_none()) - end, - NewMap1 = enter_type(Id, FunType, Map), - NewMap2 = - case state__get_rec_var(Id, State) of - {ok, Var} -> enter_type(Var, FunType, NewMap1); - error -> NewMap1 - end, - {ok, dict:store(Id, NewMap2, NewMapDict), NewMap2}; - {ok, NewMapDict, NewMap} -> - ?debug("Done solving fun: ~p\n", [debug_lookup_name(Id)]), - FunType = lookup_type(Id, NewMap), - NewMap1 = enter_type(Id, FunType, Map), - NewMap2 = - case state__get_rec_var(Id, State) of - {ok, Var} -> enter_type(Var, FunType, NewMap1); - error -> NewMap1 - end, - {ok, dict:store(Id, NewMap2, NewMapDict), NewMap2} - end + {NewMapDict, FunType} = + case Res of + {error, NewMapDict0} -> + ?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]), + Arity = state__fun_arity(Id, State), + FunType0 = + case state__prop_domain(t_var_name(Id), State) of + error -> t_fun(Arity, t_none()); + {ok, Dom} -> t_fun(Dom, t_none()) + end, + {NewMapDict0, FunType0}; + {ok, NewMapDict0, NewMap} -> + ?debug("Done solving fun: ~p\n", [debug_lookup_name(Id)]), + FunType0 = lookup_type(Id, NewMap), + {NewMapDict0, FunType0} + end, + NewMap1 = enter_type(Id, FunType, Map), + NewMap2 = + case state__get_rec_var(Id, State) of + {ok, Var} -> enter_type(Var, FunType, NewMap1); + error -> NewMap1 + end, + {ok, dict:store(Id, NewMap2, NewMapDict), NewMap2} end; solve_ref_or_list(#constraint_list{type=Type, list = Cs, deps = Deps, id = Id}, Map, MapDict, State) -> @@ -2078,10 +2086,15 @@ mk_var_no_lit_list(List) -> %% ============================================================================ new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes) -> - NameMap = dict:from_list([{MFA, Var} || {MFA, {Var, _Fun}, _Rec} <- SCC0]), + List = [{MFA, Var} || {MFA, {Var, _Fun}, _Rec} <- SCC0], + NameMap = dict:from_list(List), + MFAs = [MFA || {MFA, _Var} <- List], SCC = [mk_var(Fun) || {_MFA, {_Var, Fun}, _Rec} <- SCC0], + SelfRecs = [F || F <- SCC, + dialyzer_callgraph:is_self_rec(t_var_name(F), CallGraph)], #state{callgraph = CallGraph, name_map = NameMap, next_label = NextLabel, - prop_types = PropTypes, plt = Plt, scc = ordsets:from_list(SCC)}. + prop_types = PropTypes, plt = Plt, scc = ordsets:from_list(SCC), + mfas = MFAs, self_recs = ordsets:from_list(SelfRecs)}. state__set_rec_dict(State, RecDict) -> State#state{records = RecDict}. @@ -2091,15 +2104,6 @@ state__set_opaques(#state{records = RecDict} = State, {M, _F, _A}) -> erl_types:module_builtin_opaques(M) ++ t_opaque_from_records(RecDict), State#state{opaques = Opaques, module = M}. -state__lookup_record(#state{records = Records}, Tag, Arity) -> - case erl_types:lookup_record(Tag, Arity, Records) of - {ok, Fields} -> - {ok, t_tuple([t_from_term(Tag)| - [FieldType || {_FieldName, FieldType} <- Fields]])}; - error -> - error - end. - state__set_in_match(State, Bool) -> State#state{in_match = Bool}. @@ -2268,14 +2272,12 @@ state__get_cs(Var, #state{cmap = Dict}) -> %% The functions here will not be treated as self recursive. %% These functions will need to be handled as such manually. -state__mark_as_non_self_rec(SCC, #state{non_self_recs = NS} = State) -> - State#state{non_self_recs = ordsets:union(NS, ordsets:from_list(SCC))}. +state__mark_as_non_self_rec(SCC, #state{self_recs = SelfRecs} = State) -> + %% TODO: Check if the result is always empty and just set it to [] if so. + State#state{self_recs = ordsets:subtract(SelfRecs, ordsets:from_list(SCC))}. -state__is_self_rec(Fun, #state{callgraph = CallGraph, non_self_recs = NS}) -> - case ordsets:is_element(Fun, NS) of - true -> false; - false -> dialyzer_callgraph:is_self_rec(t_var_name(Fun), CallGraph) - end. +state__is_self_rec(Fun, #state{self_recs = SelfRecs}) -> + ordsets:is_element(Fun, SelfRecs). state__store_funs(Vars0, Funs0, #state{fun_map = Map} = State) -> debug_make_name_map(Vars0, Funs0), @@ -2689,6 +2691,15 @@ find_constraint(Tuple, [#constraint_list{list = List}|Cs]) -> find_constraint(Tuple, [_|Cs]) -> find_constraint(Tuple, Cs). +lookup_record(Records, Tag, Arity) -> + case erl_types:lookup_record(Tag, Arity, Records) of + {ok, Fields} -> + {ok, t_tuple([t_from_term(Tag)| + [FieldType || {_FieldName, FieldType} <- Fields]])}; + error -> + error + end. + %% ============================================================================ %% %% Pretty printer and debug facilities. diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile index 47deb17f1d..6a1abce943 100644 --- a/lib/dialyzer/test/Makefile +++ b/lib/dialyzer/test/Makefile @@ -30,5 +30,5 @@ release_tests_spec: $(INSTALL_DATA) $(AUXILIARY_FILES) $(RELSYSDIR) @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) cd $(RELSYSDIR);\ - erl -make;\ + erlc dialyzer_common.erl file_utils.erl;\ erl -noshell -run dialyzer_common create_all_suites -s erlang halt diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour index a38e662ccf..8cecabccaa 100644 --- a/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour +++ b/lib/dialyzer/test/behaviour_SUITE_data/results/sample_behaviour @@ -1,9 +1,9 @@ -sample_callback_wrong.erl:15: The inferred return type of sample_callback_2/0 (42) has nothing in common with atom(), which is the expected return type for the callback of sample_behaviour behaviour -sample_callback_wrong.erl:16: The inferred return type of sample_callback_3/0 ('fair') has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of sample_behaviour behaviour -sample_callback_wrong.erl:17: The inferred return type of sample_callback_4/1 ('fail') has nothing in common with 'ok', which is the expected return type for the callback of sample_behaviour behaviour -sample_callback_wrong.erl:19: The inferred return type of sample_callback_5/1 (string()) has nothing in common with 'fail' | 'ok', which is the expected return type for the callback of sample_behaviour behaviour -sample_callback_wrong.erl:19: The inferred type for the 1st argument of sample_callback_5/1 (atom()) is not a supertype of 1..255, which is expected type for this argument in the callback of the sample_behaviour behaviour -sample_callback_wrong.erl:21: The inferred return type of sample_callback_6/3 ({'okk',number()}) has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of sample_behaviour behaviour -sample_callback_wrong.erl:21: The inferred type for the 3rd argument of sample_callback_6/3 (atom()) is not a supertype of string(), which is expected type for this argument in the callback of the sample_behaviour behaviour -sample_callback_wrong.erl:3: Undefined callback function sample_callback_1/0 (behaviour 'sample_behaviour') +sample_callback_wrong.erl:16: The inferred return type of sample_callback_2/0 (42) has nothing in common with atom(), which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:17: The inferred return type of sample_callback_3/0 ('fair') has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:18: The inferred return type of sample_callback_4/1 ('fail') has nothing in common with 'ok', which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:20: The inferred return type of sample_callback_5/1 (string()) has nothing in common with 'fail' | 'ok', which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:20: The inferred type for the 1st argument of sample_callback_5/1 (atom()) is not a supertype of 1..255, which is expected type for this argument in the callback of the sample_behaviour behaviour +sample_callback_wrong.erl:22: The inferred return type of sample_callback_6/3 ({'okk',number()}) has nothing in common with 'fail' | {'ok',1..255}, which is the expected return type for the callback of sample_behaviour behaviour +sample_callback_wrong.erl:22: The inferred type for the 3rd argument of sample_callback_6/3 (atom()) is not a supertype of string(), which is expected type for this argument in the callback of the sample_behaviour behaviour +sample_callback_wrong.erl:4: Undefined callback function sample_callback_1/0 (behaviour 'sample_behaviour') diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/custom_sup.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/custom_sup.erl new file mode 100644 index 0000000000..8ec84d798f --- /dev/null +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/custom_sup.erl @@ -0,0 +1,37 @@ +%%% Dialyzer was giving a warning with this input because of a bug in the +%%% substitution of remote types in specs. Remote types in the first element of +%%% a tuple would not update the tuple's tag set and we could end up with a +%%% non-normalized representation. +%%% +%%% Reported by Damian DobroczyĆski on 29/02/2012 + +-module(custom_sup). + +-behavior(supervisor). + +-export([init/1]). + +-spec init(atom()) -> + {ok, {{supervisor:strategy(), non_neg_integer(), non_neg_integer()}, + [supervisor:child_spec()]}} | ignore. + +init(StorageName) -> + Strategy = {one_for_all, 100, 1}, + %% get application-wide storage parameters + case application:get_env(storage) of + undefined -> + ignore; + {ok, Storage} -> + BackendId = proplists:get_value(backend, Storage), + BackendArgs = proplists:get_value(args, Storage), + if + (BackendId =:= undefined) orelse (BackendArgs =:= undefined) -> + ignore; + true -> + {ok, {Strategy, + [{id1, {a_module, start_link, []}, + permanent, 5000, worker, [a_module]}, + {id2, {another_module, start_link, []}, + permanent, 5000, worker, [another_module]}]}} + end + end. diff --git a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl index 02a063fab7..430494c48c 100644 --- a/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl +++ b/lib/dialyzer/test/behaviour_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl @@ -1,6 +1,7 @@ -module(sample_callback_wrong). --behaviour(sample_behaviour). +%% This attribute uses the american spelling of 'behaviour'. +-behavior(sample_behaviour). -export([ % sample_callback_1/0, diff --git a/lib/dialyzer/test/dialyzer_common.erl b/lib/dialyzer/test/dialyzer_common.erl index 51766a4604..d2b1026c06 100644 --- a/lib/dialyzer/test/dialyzer_common.erl +++ b/lib/dialyzer/test/dialyzer_common.erl @@ -216,10 +216,13 @@ get_suites(Dir) -> end. suffix(String, Suffix) -> - Index = string:rstr(String, Suffix), - case string:substr(String, Index) =:= Suffix of - true -> {yes, string:sub_string(String,1,Index-1)}; - false -> no + case string:rstr(String, Suffix) of + 0 -> no; + Index -> + case string:substr(String, Index) =:= Suffix of + true -> {yes, string:sub_string(String,1,Index-1)}; + false -> no + end end. -spec create_suite(string()) -> 'ok'. diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques b/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques new file mode 100644 index 0000000000..18ece8820c --- /dev/null +++ b/lib/dialyzer/test/opaque_SUITE_data/results/multiple_wrong_opaques @@ -0,0 +1,2 @@ + +multiple_wrong_opaques.erl:5: Invalid type specification for function multiple_wrong_opaques:weird/1. The success typing is ('gazonk') -> 42 diff --git a/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl b/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl new file mode 100644 index 0000000000..9e695cec1d --- /dev/null +++ b/lib/dialyzer/test/opaque_SUITE_data/src/multiple_wrong_opaques.erl @@ -0,0 +1,8 @@ +-module(multiple_wrong_opaques). + +-export([weird/1]). + +-spec weird(dict() | gb_tree()) -> 42. + +weird(gazonk) -> 42. + diff --git a/lib/dialyzer/test/options1_SUITE_data/results/compiler b/lib/dialyzer/test/options1_SUITE_data/results/compiler index e82087ae86..6399e3e36b 100644 --- a/lib/dialyzer/test/options1_SUITE_data/results/compiler +++ b/lib/dialyzer/test/options1_SUITE_data/results/compiler @@ -20,6 +20,7 @@ cerl_inline.erl:2333: The pattern 'true' can never match the type 'false' cerl_inline.erl:2355: The pattern 'true' can never match the type 'false' cerl_inline.erl:238: The pattern 'true' can never match the type 'false' cerl_inline.erl:2436: Function filename/1 will never be called +cerl_inline.erl:244: Function counter_stats/0 will never be called cerl_inline.erl:2700: The pattern 'true' can never match the type 'false' cerl_inline.erl:2730: The pattern <{F, L, D}, Vs> can never match the type <[1..255,...],[any()]> cerl_inline.erl:2738: The pattern <{F, L, D}, Vs> can never match the type <[1..255,...],[any()]> diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/inets b/lib/dialyzer/test/r9c_SUITE_data/results/inets index 24cb39e52b..773525eb7f 100644 --- a/lib/dialyzer/test/r9c_SUITE_data/results/inets +++ b/lib/dialyzer/test/r9c_SUITE_data/results/inets @@ -8,6 +8,7 @@ http_lib.erl:424: The variable _ can never match since previous clauses complete http_lib.erl:438: The variable _ can never match since previous clauses completely covered the type any() http_lib.erl:99: Function getHeaderValue/2 will never be called httpc_handler.erl:660: Function exit_session_ok/2 has no local return +httpc_handler.erl:676: Function format_time/0 will never be called httpc_manager.erl:145: The pattern {ErrorReply, State2} can never match the type {{'ok',number()},number(),#state{reqid::number()}} httpc_manager.erl:160: The pattern {ErrorReply, State2} can never match the type {{'ok',number()},number(),#state{reqid::number()}} httpc_manager.erl:478: The pattern {'error', Reason} can never match the type 'ok' | {number(),#session{clientclose::boolean(),pipeline::[],quelength::1}} diff --git a/lib/dialyzer/test/small_SUITE_data/results/cerl_hipeify b/lib/dialyzer/test/small_SUITE_data/results/cerl_hipeify index 87bf6f309f..06dc0d63ee 100644 --- a/lib/dialyzer/test/small_SUITE_data/results/cerl_hipeify +++ b/lib/dialyzer/test/small_SUITE_data/results/cerl_hipeify @@ -1,4 +1,4 @@ cerl_hipeify.erl:370: Function will never be called -cerl_hipeify.erl:370: Guard test fun((none()) -> none()) =:= F::{_,_,_} | {_,_,_,_} | {_,_,_,_,_} | {_,_,_,_,_,_} | {_,_,_,_,_,_,_} can never succeed +cerl_hipeify.erl:370: Guard test fun((none()) -> no_return()) =:= F::{_,_,_} | {_,_,_,_} | {_,_,_,_,_} | {_,_,_,_,_,_} | {_,_,_,_,_,_,_} can never succeed cerl_hipeify.erl:641: Function env__new_function_name/2 will never be called diff --git a/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 b/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 index 7e9972ad98..142e4b2c37 100644 --- a/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 +++ b/lib/dialyzer/test/small_SUITE_data/results/inf_loop2 @@ -1,4 +1,4 @@ inf_loop2.erl:18: Function test/0 has no local return inf_loop2.erl:19: The call lists:reverse('gazonk') will never return since it differs in the 1st argument from the success typing arguments: ([any()]) -inf_loop2.erl:22: Function loop/0 has no local return +inf_loop2.erl:22: Function loop/0 will never be called diff --git a/lib/dialyzer/test/small_SUITE_data/results/no_local_return b/lib/dialyzer/test/small_SUITE_data/results/no_local_return new file mode 100644 index 0000000000..6ca1ed51d8 --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/results/no_local_return @@ -0,0 +1,3 @@ + +no_local_return.erl:11: Function bar/1 will never be called +no_local_return.erl:8: Function foo/0 will never be called diff --git a/lib/dialyzer/test/small_SUITE_data/src/no_local_return.erl b/lib/dialyzer/test/small_SUITE_data/src/no_local_return.erl new file mode 100644 index 0000000000..4e1a0b015a --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/no_local_return.erl @@ -0,0 +1,12 @@ +-module(no_local_return). + +%% NOTE: No function is exported. Dialyzer produced a bogus +%% 'Function foo/0 has no local return' warning +%% when in fact typer was finding correct return values for both +%% these functions. + +foo() -> + bar(42). + +bar(X) -> + lists:duplicate(X, gazonk). diff --git a/lib/dialyzer/test/small_SUITE_data/src/on_load.erl b/lib/dialyzer/test/small_SUITE_data/src/on_load.erl new file mode 100644 index 0000000000..16533a9caa --- /dev/null +++ b/lib/dialyzer/test/small_SUITE_data/src/on_load.erl @@ -0,0 +1,11 @@ +%%% This is to ensure that "on_load" functions are never reported as unused. + +-module(on_load). + +-export([foo/0]). + +-on_load(bar/0). + +foo() -> ok. + +bar() -> ok. diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index 620fed365e..65b9a057de 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -424,12 +424,7 @@ t_has_opaque_subtype(T) -> -spec t_opaque_structure(erl_type()) -> erl_type(). t_opaque_structure(?opaque(Elements)) -> - case ordsets:size(Elements) of - 1 -> - [#opaque{struct = Struct}] = ordsets:to_list(Elements), - Struct; - _ -> throw({error, "Unexpected multiple opaque types"}) - end. + t_sup([Struct || #opaque{struct = Struct} <- ordsets:to_list(Elements)]). -spec t_opaque_module(erl_type()) -> module(). @@ -688,9 +683,9 @@ t_solve_remote(?opaque(Set), ET, R, C) -> {NewList, RR} = opaques_solve_remote(List, ET, R, C), {?opaque(ordsets:from_list(NewList)), RR}; t_solve_remote(?tuple(?any, _, _) = T, _ET, _R, _C) -> {T, []}; -t_solve_remote(?tuple(Types, Arity, Tag), ET, R, C) -> +t_solve_remote(?tuple(Types, _Arity, _Tag), ET, R, C) -> {RL, RR} = list_solve_remote(Types, ET, R, C), - {?tuple(RL, Arity, Tag), RR}; + {t_tuple(RL), RR}; t_solve_remote(?tuple_set(Set), ET, R, C) -> {NewSet, RR} = tuples_solve_remote(Set, ET, R, C), {?tuple_set(NewSet), RR}; diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index b38e51292a..d7ce432786 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -44,7 +44,7 @@ -export([start_node/3, stop_node/1, wait_for_node/1, is_release_available/1]). -export([app_test/1, app_test/2]). -export([is_native/1]). --export([comment/1]). +-export([comment/1, make_priv_dir/0]). -export([os_type/0]). -export([run_on_shielded_node/2]). -export([is_cover/0,is_debug/0,is_commercial/0]). @@ -754,6 +754,25 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment, CurrConf) -> {set_curr_conf,From,NewCurrConf} -> From ! {self(),set_curr_conf,ok}, run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,NewCurrConf); + {make_priv_dir,From} when CurrConf == undefined -> + From ! {self(),make_priv_dir,{error,no_priv_dir_in_config}}; + {make_priv_dir,From} -> + Result = + case proplists:get_value(priv_dir, element(2, CurrConf)) of + undefined -> + {error,no_priv_dir_in_config}; + PrivDir -> + case file:make_dir(PrivDir) of + ok -> + ok; + {error, eexist} -> + ok; + MkDirError -> + {error,{MkDirError,PrivDir}} + end + end, + From ! {self(),make_priv_dir,Result}, + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {'EXIT',Pid,{Ref,Time,Value,Loc,Opts}} -> RetVal = {Time/1000000,Value,mod_loc(Loc),Opts,Comment}, run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment,undefined); @@ -1188,6 +1207,9 @@ run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit, exit({Ref,Time,Value,Loc,Opts}). run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> + %% save current state in controller loop + sync_send(group_leader(),set_curr_conf,{{Mod,Func},hd(Args)}, + 5000, fun() -> exit(no_answer_from_group_leader) end), case RunInit of run_init -> put(test_server_init_or_end_conf,{init_per_testcase,Func}), @@ -1246,8 +1268,8 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> %% call user callback function if defined EndConf1 = user_callback(TCCallback, Mod, Func, 'end', EndConf), %% update current state in controller loop - sync_send(group_leader(),set_curr_conf,EndConf1, - 5000, fun() -> exit(no_answer_from_group_leader) end), + sync_send(group_leader(),set_curr_conf,EndConf1, 5000, + fun() -> exit(no_answer_from_group_leader) end), {FWReturn1,TSReturn1,EndConf2} = case end_per_testcase(Mod, Func, EndConf1) of SaveCfg1={save_config,_} -> @@ -2587,11 +2609,23 @@ read_comment() -> MsgLooper = group_leader(), MsgLooper ! {read_comment,self()}, receive - {MsgLooper,read_comment,Comment} -> - Comment + {MsgLooper,read_comment,Comment} -> Comment + after + 5000 -> "" + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% make_priv_dir() -> ok +%% +%% Order test server to create the private directory +%% for the current test case. +make_priv_dir() -> + MsgLooper = group_leader(), + group_leader() ! {make_priv_dir,self()}, + receive + {MsgLooper,make_priv_dir,Result} -> Result after - 5000 -> - "" + 5000 -> error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 4b649c3ec5..50720a45c8 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -164,6 +164,7 @@ -export([start_get_totals/1, stop_get_totals/0]). -export([get_levels/0, set_levels/3]). -export([multiply_timetraps/1, scale_timetraps/1, get_timetrap_parameters/0]). +-export([create_priv_dir/1]). -export([cover/2, cover/3, cover/7, cross_cover_analyse/1, cross_cover_analyse/2, trc/1, stop_trace/0]). -export([testcase_callback/1]). @@ -219,8 +220,8 @@ -define(user_skip_color, "#FF8000"). -record(state,{jobs=[],levels={1,19,10}, - multiply_timetraps=1,scale_timetraps=true, - finish=false, + multiply_timetraps=1, scale_timetraps=true, + create_priv_dir=auto_per_run, finish=false, target_info, trc=false, cover=false, wait_for_node=[], testcase_callback=undefined, idle_notify=[], get_totals=false, random_seed=undefined}). @@ -506,6 +507,9 @@ scale_timetraps(Bool) -> get_timetrap_parameters() -> controller_call(get_timetrap_parameters). +create_priv_dir(Value) -> + controller_call({create_priv_dir,Value}). + trc(TraceFile) -> controller_call({trace,TraceFile}, 2*?ACCEPT_TIMEOUT). @@ -811,6 +815,7 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) -> [SpecName,{State#state.multiply_timetraps, State#state.scale_timetraps}], LogDir, Name, State#state.levels, + State#state.create_priv_dir, State#state.testcase_callback, ExtraTools1), NewJobs = [{Name,Pid}|State#state.jobs], {reply, ok, State#state{jobs=NewJobs}}; @@ -820,6 +825,7 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) -> [SpecList,{State#state.multiply_timetraps, State#state.scale_timetraps}], LogDir, Name, State#state.levels, + State#state.create_priv_dir, State#state.testcase_callback, ExtraTools1), NewJobs = [{Name,Pid}|State#state.jobs], {reply, ok, State#state{jobs=NewJobs}}; @@ -837,6 +843,7 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) -> {State#state.multiply_timetraps, State#state.scale_timetraps}], LogDir, Name, State#state.levels, + State#state.create_priv_dir, State#state.testcase_callback, ExtraTools1), NewJobs = [{Name,Pid}|State#state.jobs], {reply, ok, State#state{jobs=NewJobs}} @@ -1045,6 +1052,18 @@ handle_call({cover,App,Analyse}, _From, State) -> {reply,ok,State#state{cover={App,Analyse}}}; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% handle_call({create_priv_dir,Value}, _, State) -> ok | {error,Reason} +%% +%% Set create_priv_dir to either auto_per_run (create common priv dir once +%% per test run), manual_per_tc (the priv dir name will be unique for each +%% test case, but the user has to call test_server:make_priv_dir/0 to create +%% it), or auto_per_tc (unique priv dir created automatically for each test +%% case). + +handle_call({create_priv_dir,Value}, _From, State) -> + {reply,ok,State#state{create_priv_dir=Value}}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% handle_call({testcase_callback,{Mod,Func}}, _, State) -> ok | {error,Reason} %% %% Add a callback function that will be called before and after every @@ -1321,7 +1340,7 @@ kill_all_jobs([]) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% spawn_tester(Mod, Func, Args, Dir, Name, Levels, +%% spawn_tester(Mod, Func, Args, Dir, Name, Levels, CreatePrivDir, %% TestCaseCallback, ExtraTools) -> Pid %% Mod = atom() %% Func = atom() @@ -1329,6 +1348,7 @@ kill_all_jobs([]) -> %% Dir = string() %% Name = string() %% Levels = {integer(),integer(),integer()} +%% CreatePrivDir = auto_per_run | manual_per_tc | auto_per_tc %% TestCaseCallback = {CBMod,CBFunc} | undefined %% ExtraTools = [ExtraTool,...] %% ExtraTool = CoverInfo | TraceInfo | RandomSeed @@ -1339,14 +1359,15 @@ kill_all_jobs([]) -> %% When the named function is done executing, a summary of the results %% is printed to the log files. -spawn_tester(Mod, Func, Args, Dir, Name, Levels, TCCallback, ExtraTools) -> +spawn_tester(Mod, Func, Args, Dir, Name, Levels, + CreatePrivDir, TCCallback, ExtraTools) -> spawn_link( fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels, - TCCallback, ExtraTools) + CreatePrivDir, TCCallback, ExtraTools) end). init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, - TCCallback, ExtraTools) -> + CreatePrivDir, TCCallback, ExtraTools) -> process_flag(trap_exit, true), put(test_server_name, Name), put(test_server_dir, Dir), @@ -1357,6 +1378,7 @@ init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, put(test_server_summary_level, SumLev), put(test_server_major_level, MajLev), put(test_server_minor_level, MinLev), + put(test_server_create_priv_dir, CreatePrivDir), put(test_server_random_seed, proplists:get_value(random_seed, ExtraTools)), put(test_server_testcase_callback, TCCallback), %% before first print, read and set logging options @@ -1818,7 +1840,7 @@ do_test_cases(TopCase, SkipCases, Config, TimetrapSpec) -> %% Creates the log directories, the major log file and the html log file. %% The log files are initialized with some header information. %% -%% The name of the log directory will be <Name>.LOGS/run.<Date>/ where +%% The name of the log directory will be <Name>.logs/run.<Date>/ where %% Name is the test suite name and Date is the current date and time. start_log_file() -> @@ -2754,7 +2776,15 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, {skipped,TcSkip}, {failed,TcFail}]}] end, - TSDirs = [{priv_dir,get(test_server_priv_dir)},{data_dir,get_data_dir(Mod)}], + + case get(test_server_create_priv_dir) of + auto_per_run -> % use common priv_dir + TSDirs = [{priv_dir,get(test_server_priv_dir)}, + {data_dir,get_data_dir(Mod)}]; + _ -> + TSDirs = [{data_dir,get_data_dir(Mod)}] + end, + ActualCfg = if not StartConf -> update_config(hd(Config), TSDirs ++ CfgProps); @@ -2916,8 +2946,13 @@ run_test_cases_loop([{conf,_Ref,_Props,_X}=Conf|_Cases0], run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) -> ActualCfg = - update_config(hd(Config), [{priv_dir,get(test_server_priv_dir)}, - {data_dir,get_data_dir(Mod)}]), + case get(test_server_create_priv_dir) of + auto_per_run -> + update_config(hd(Config), [{priv_dir,get(test_server_priv_dir)}, + {data_dir,get_data_dir(Mod)}]); + _ -> + update_config(hd(Config), [{data_dir,get_data_dir(Mod)}]) + end, run_test_cases_loop([{Mod,Case,[ActualCfg]}|Cases], Config, TimetrapData, Mode, Status); @@ -3638,9 +3673,14 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, %% if this runs on a parallel test case process, %% copy the dictionary from the main process do_if_parallel(Main, fun() -> process_flag(trap_exit, true) end, ok), - CopyDict = fun() -> lists:foreach(fun({Key,Val}) -> put(Key, Val) end, State) end, + CopyDict = fun() -> lists:foreach(fun({Key,Val}) -> + put(Key, Val) + end, State) + end, do_if_parallel(Main, CopyDict, ok), - do_if_parallel(Main, fun() -> put(test_server_common_io_handler, {tc,Main}) end, ok), + do_if_parallel(Main, fun() -> + put(test_server_common_io_handler, {tc,Main}) + end, ok), %% if io is being buffered, send start io session message %% (no matter if case runs on parallel or main process) case get(test_server_common_io_handler) of @@ -3660,8 +3700,35 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, MinorBase = filename:basename(MinorName), print(major, "=logfile ~s", [filename:basename(MinorName)]), - Args1 = [[{tc_logfile,MinorName} | proplists:delete(tc_logfile,hd(Args))]], - test_server_sup:framework_call(report, [tc_start,{{?pl2a(Mod),Func},MinorName}]), + UpdatedArgs = + %% maybe create unique private directory for test case or config func + case get(test_server_create_priv_dir) of + auto_per_run -> + update_config(hd(Args), [{tc_logfile,MinorName}]); + PrivDirMode -> + RunDir = filename:dirname(MinorName), + Ext = + if Num == 0 -> + {_,S,Us} = now(), + lists:flatten(io_lib:format(".~w.~w", [S,Us])); + true -> + %% create unique private directory for test case + RunDir = filename:dirname(MinorName), + lists:flatten(io_lib:format(".~w", [Num])) + end, + PrivDir = filename:join(RunDir, ?priv_dir) ++ Ext, + if PrivDirMode == auto_per_tc -> + ok = file:make_dir(PrivDir); + PrivDirMode == manual_per_tc -> + ok + end, + update_config(hd(Args), [{priv_dir,PrivDir++"/"}, + {tc_logfile,MinorName}]) + end, + + test_server_sup:framework_call(report, + [tc_start,{{?pl2a(Mod),Func},MinorName}]), + print_props((RunInit==skip_init), get_props(Mode)), GroupName = case get_name(Mode) of undefined -> ""; @@ -3680,7 +3747,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, do_if_parallel(Main, ok, fun erlang:yield/0), %% run the test case {Result,DetectedFail,ProcsBefore,ProcsAfter} = - run_test_case_apply(Num, Mod, Func, Args1, get_name(Mode), + run_test_case_apply(Num, Mod, Func, [UpdatedArgs], get_name(Mode), RunInit, Where, TimetrapData), {Time,RetVal,Loc,Opts,Comment} = case Result of |