Repository: parapluu/Concuerror Branch: master Commit: c90d601808a0 Files: 1282 Total size: 13.3 MB Directory structure: gitextract_f2h5wf0f/ ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ └── other-issue.md │ ├── codecov.yml │ ├── pull_request_template.md │ ├── scripts/ │ │ ├── covertool_combine │ │ └── covertool_setup │ └── workflows/ │ ├── tests.yml │ └── website.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── bin/ │ └── .gitignore ├── cover/ │ ├── .gitignore │ └── cover-report ├── doc/ │ ├── .gitignore │ ├── Gemfile │ ├── Makefile │ └── overview.edoc ├── docs/ │ ├── .bundle/ │ │ └── config │ ├── .gitignore │ ├── 404.md │ ├── CNAME │ ├── Gemfile │ ├── Makefile │ ├── README.md │ ├── _config.yml │ ├── _config_local.yml │ ├── _data/ │ │ ├── authors.yml │ │ ├── navigation.yml │ │ └── text.yml │ ├── _includes/ │ │ ├── footer-custom.html │ │ ├── head-custom.html │ │ ├── masthead.html │ │ └── yt.html │ ├── _posts/ │ │ ├── 2013-09-03-site.md │ │ ├── 2013-09-23-euc-2013.md │ │ ├── 2014-06-02-poolboy-example.md │ │ ├── 2014-06-03-poolboy-example-errors.md │ │ ├── 2014-08-11-euc-2014.md │ │ ├── 2015-06-10-euc-2015.md │ │ ├── 2017-08-14-euc-2017-talk.md │ │ ├── 2017-08-14-euc-2017-tutorial.md │ │ ├── 2017-10-03-publications-update.md │ │ ├── 2017-10-08-concuerror-eunit.md │ │ ├── 2017-10-14-concuerring-concuerror-ep-1.md │ │ ├── 2017-11-06-Announcing-Mailing-List.md │ │ ├── 2018-07-15-hex.md │ │ ├── 2020-10-10-tips.md │ │ ├── 2020-10-17-code-beam-sf-2019.md │ │ ├── 2020-10-17-functional-conf-2018.md │ │ └── 2020-10-24-basic-tutorial.md │ ├── contact.md │ ├── download.md │ ├── faq.md │ ├── feed.xml │ ├── index.md │ ├── news.md │ ├── publications.md │ ├── search.md │ ├── sitemap.xml │ └── tutorials.md ├── elvis.config ├── priv/ │ ├── concuerror │ ├── generate_option_docfiles │ └── generate_version_hrl ├── rebar.config ├── resources/ │ ├── DPOR_paper_material/ │ │ ├── DPOR_README │ │ ├── foo.erl │ │ ├── foobar.erl │ │ ├── my_test.erl │ │ ├── run_dialyzer.sh │ │ ├── run_filesystem.sh │ │ ├── run_indexer.sh │ │ ├── run_last_zero.sh │ │ ├── run_readers.sh │ │ └── run_rush_hour.sh │ ├── bash_completion/ │ │ └── concuerror │ ├── erlang_questions/ │ │ └── dets_bugs/ │ │ ├── alt_dets_bugs.erl │ │ └── dets_bugs.erl │ ├── flanagan.erl │ ├── how-to-release.md │ ├── old_include/ │ │ ├── gen.hrl │ │ ├── gui.hrl │ │ └── instr.hrl │ ├── old_source/ │ │ ├── LICENSE │ │ ├── concuerror.erl │ │ ├── concuerror_deps.erl │ │ ├── concuerror_error.erl │ │ ├── concuerror_gui.erl │ │ ├── concuerror_instr.erl │ │ ├── concuerror_io_server.erl │ │ ├── concuerror_lid.erl │ │ ├── concuerror_log.erl │ │ ├── concuerror_proc_action.erl │ │ ├── concuerror_rep.erl │ │ ├── concuerror_sched.erl │ │ ├── concuerror_state.erl │ │ ├── concuerror_ticket.erl │ │ └── concuerror_util.erl │ ├── perm_tests/ │ │ ├── run_perm_tests │ │ └── src/ │ │ ├── complete_test_3.erl │ │ ├── conditional_readers_3.erl │ │ ├── depend_4_1.erl │ │ ├── depend_4_2.erl │ │ ├── depend_4_3.erl │ │ ├── depend_6_1.erl │ │ ├── depend_6_2.erl │ │ ├── depend_6_3.erl │ │ ├── depend_6_4.erl │ │ ├── depend_6_5.erl │ │ ├── etsi.erl │ │ ├── readers_2.erl │ │ └── readers_3.erl │ ├── syntax/ │ │ ├── block_after.erl │ │ ├── non_local_pat.erl │ │ ├── rec_uscore.erl │ │ └── strip_attr.erl │ ├── tdd/ │ │ ├── reg_server.erl │ │ ├── reg_server.hrl │ │ └── reg_server_tests.erl │ └── utest/ │ ├── concuerror_error_tests.erl │ ├── concuerror_instr_tests.erl │ ├── concuerror_lid_tests.erl │ ├── concuerror_state_tests.erl │ └── concuerror_ticket_tests.erl ├── src/ │ ├── .gitignore │ ├── concuerror.app.src │ ├── concuerror.erl │ ├── concuerror.hrl │ ├── concuerror_callback.erl │ ├── concuerror_dependencies.erl │ ├── concuerror_estimator.erl │ ├── concuerror_inspect.erl │ ├── concuerror_instrumenter.erl │ ├── concuerror_io_lib.erl │ ├── concuerror_loader.erl │ ├── concuerror_logger.erl │ ├── concuerror_options.erl │ ├── concuerror_scheduler.erl │ └── concuerror_window_average.erl ├── test/ │ ├── README.md │ ├── concuerror_logger_tests.erl │ └── concuerror_options_tests.erl ├── tests/ │ ├── README.md │ ├── differ │ ├── results/ │ │ └── .gitignore │ ├── runtests.py │ ├── scenarios.erl │ ├── suites/ │ │ ├── advanced_tests/ │ │ │ ├── results/ │ │ │ │ ├── gen_server_bug-test_register-inf-dpor.txt │ │ │ │ ├── gen_server_bug-test_without_register-inf-dpor.txt │ │ │ │ ├── manolis-test_2workers_small-inf-dpor.txt │ │ │ │ ├── percept-test-inf-dpor.txt │ │ │ │ ├── preemption-preemption-inf-dpor.txt │ │ │ │ ├── receive_deps-test-inf-dpor.txt │ │ │ │ ├── receive_deps_kill-test-inf-dpor.txt │ │ │ │ ├── receive_pats-test1-inf-dpor.txt │ │ │ │ ├── receive_pats-test2-inf-dpor.txt │ │ │ │ ├── receive_pats-test3-inf-dpor.txt │ │ │ │ ├── receive_pats-test4-inf-dpor.txt │ │ │ │ ├── receive_patterns-test1-inf-optimal.txt │ │ │ │ ├── receive_patterns-test2-inf-optimal.txt │ │ │ │ ├── ring_leader_election-ring_leader_election-inf-dpor.txt │ │ │ │ ├── ring_leader_election_barrier-ring_leader_election_barrier-inf-dpor.txt │ │ │ │ ├── ring_leader_election_symmetric-ring_leader_election_symmetric-inf-dpor.txt │ │ │ │ ├── ring_leader_election_symmetric_buffer-ring_leader_election_symmetric_buffer-inf-dpor.txt │ │ │ │ ├── ring_leader_election_symmetric_buffer_2-ring_leader_election_symmetric_buffer_2-inf-dpor.txt │ │ │ │ ├── shared_ptr-shared_ptr-inf-dpor.txt │ │ │ │ ├── shootout-test1-inf-dpor.txt │ │ │ │ ├── simple_registry-simple_registry-inf-dpor.txt │ │ │ │ ├── source_killer-test-inf-dpor.txt │ │ │ │ ├── source_killer-test-inf-source.txt │ │ │ │ └── vjeko_peer-concuerror_test-inf-dpor.txt │ │ │ └── src/ │ │ │ ├── gen_server_bug.erl │ │ │ ├── indexer_example.erl │ │ │ ├── manolis/ │ │ │ │ ├── good_testcase.txt │ │ │ │ ├── readme.txt │ │ │ │ ├── rush_hour.erl │ │ │ │ ├── search.erl │ │ │ │ └── test.erl │ │ │ ├── percept.erl │ │ │ ├── preemption.erl │ │ │ ├── receive_deps.erl │ │ │ ├── receive_deps_kill.erl │ │ │ ├── receive_pats.erl │ │ │ ├── receive_patterns.erl │ │ │ ├── ring_leader_election.erl │ │ │ ├── ring_leader_election_barrier.erl │ │ │ ├── ring_leader_election_symmetric.erl │ │ │ ├── ring_leader_election_symmetric_buffer.erl │ │ │ ├── ring_leader_election_symmetric_buffer_2.erl │ │ │ ├── send_order.erl │ │ │ ├── shared_ptr.erl │ │ │ ├── shootout/ │ │ │ │ ├── pidigits.erl │ │ │ │ ├── test.erl │ │ │ │ └── thread_ring.erl │ │ │ ├── simple_registry.erl │ │ │ ├── source_killer.erl │ │ │ └── vjeko_peer.erl │ │ ├── basic_tests/ │ │ │ ├── results/ │ │ │ │ ├── ac_tab_insert-test-inf-optimal.txt │ │ │ │ ├── after_test_2-after_test_2-inf-dpor.txt │ │ │ │ ├── after_test_3-after_test_3-inf-dpor.txt │ │ │ │ ├── after_vs_trap_exit-after_vs_trap_exit-inf-dpor.txt │ │ │ │ ├── allow_first_crash-test-inf-dpor.txt │ │ │ │ ├── assert_filter-test-inf-dpor.txt │ │ │ │ ├── bad_dictionary-bad_erase-inf-optimal.txt │ │ │ │ ├── bad_dictionary-bad_get-inf-optimal.txt │ │ │ │ ├── bad_dictionary-bad_put-inf-optimal.txt │ │ │ │ ├── bad_whereis-test-inf-dpor.txt │ │ │ │ ├── code_when_undef-test-inf-optimal.txt │ │ │ │ ├── concuerror_crash-test-inf-dpor.txt │ │ │ │ ├── curious_builtins-test1-inf-dpor.txt │ │ │ │ ├── curious_builtins-test2-inf-dpor.txt │ │ │ │ ├── curious_builtins-test3-inf-dpor.txt │ │ │ │ ├── dead_receive-dead_receive-inf-dpor.txt │ │ │ │ ├── deeper_exit-test-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-child_both-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-child_flush-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-child_info-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-child_none-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-parent_both-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-parent_flush-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-parent_info-inf-dpor.txt │ │ │ │ ├── demonitor_exhaustive-parent_none-inf-dpor.txt │ │ │ │ ├── demonitor_flush-test1-inf-dpor.txt │ │ │ │ ├── demonitor_flush-test2-inf-dpor.txt │ │ │ │ ├── demonitor_flush-test3-inf-dpor.txt │ │ │ │ ├── demonitor_flush-test4-inf-dpor.txt │ │ │ │ ├── demonitor_flush-test5-inf-dpor.txt │ │ │ │ ├── demonitor_flush-test6-inf-dpor.txt │ │ │ │ ├── demonitor_sane_opts-test1-inf-dpor.txt │ │ │ │ ├── demonitor_sane_opts-test2-inf-dpor.txt │ │ │ │ ├── demonitor_sane_opts-test3-inf-dpor.txt │ │ │ │ ├── depth_bound-test-inf-dpor.txt │ │ │ │ ├── erlang_display-test-inf-dpor.txt │ │ │ │ ├── erlang_display_string-test-inf-dpor.txt │ │ │ │ ├── ets_delete-ets_delete-inf-dpor.txt │ │ │ │ ├── ets_delete-ets_delete_bad-inf-dpor.txt │ │ │ │ ├── ets_delete-ets_delete_plain-inf-dpor.txt │ │ │ │ ├── ets_delete_2-ets_delete_2-inf-dpor.txt │ │ │ │ ├── ets_delete_2-ets_delete_2_twice-inf-dpor.txt │ │ │ │ ├── ets_delete_all-delete_all_bad-inf-optimal.txt │ │ │ │ ├── ets_delete_all-delete_all_good-inf-optimal.txt │ │ │ │ ├── ets_delete_all-race_delete_all_read-inf-optimal.txt │ │ │ │ ├── ets_delete_all-race_delete_all_write-inf-optimal.txt │ │ │ │ ├── ets_delete_object-test1-inf-dpor.txt │ │ │ │ ├── ets_delete_object-test2-inf-dpor.txt │ │ │ │ ├── ets_delete_object-test3-inf-dpor.txt │ │ │ │ ├── ets_exceptions-test-inf-dpor.txt │ │ │ │ ├── ets_give_away-give_away_bad-inf-optimal.txt │ │ │ │ ├── ets_give_away-give_away_good-inf-optimal.txt │ │ │ │ ├── ets_global_global-new_delete-inf-optimal.txt │ │ │ │ ├── ets_global_global-new_exit-inf-optimal.txt │ │ │ │ ├── ets_global_global-new_new-inf-optimal.txt │ │ │ │ ├── ets_global_global-new_rename_free-inf-optimal.txt │ │ │ │ ├── ets_global_global-new_rename_taken-inf-optimal.txt │ │ │ │ ├── ets_global_global-rename_delete-inf-optimal.txt │ │ │ │ ├── ets_global_global-rename_exit-inf-optimal.txt │ │ │ │ ├── ets_global_global-rename_rename_free-inf-optimal.txt │ │ │ │ ├── ets_global_global-rename_rename_taken-inf-optimal.txt │ │ │ │ ├── ets_heir-ets_heir-inf-dpor.txt │ │ │ │ ├── ets_heir-test-inf-dpor.txt │ │ │ │ ├── ets_heir-test1-inf-dpor.txt │ │ │ │ ├── ets_heir-test2-inf-dpor.txt │ │ │ │ ├── ets_heir_2-ets_heir_2-inf-dpor.txt │ │ │ │ ├── ets_info-info_2_badarg-inf-optimal.txt │ │ │ │ ├── ets_info-info_bad-inf-optimal.txt │ │ │ │ ├── ets_info-info_badarg-inf-optimal.txt │ │ │ │ ├── ets_info-info_good-inf-optimal.txt │ │ │ │ ├── ets_info-info_system-inf-optimal.txt │ │ │ │ ├── ets_info_details-test-inf-optimal.txt │ │ │ │ ├── ets_insert_new-test-inf-dpor.txt │ │ │ │ ├── ets_insert_objects-ets_insert_objects-inf-dpor.txt │ │ │ │ ├── ets_many_insert_new-ets_many_insert_new-inf-dpor.txt │ │ │ │ ├── ets_new-test-inf-dpor.txt │ │ │ │ ├── ets_new-test-inf.txt │ │ │ │ ├── ets_new_failure-ets_new_failure-inf-dpor.txt │ │ │ │ ├── ets_new_failure_2-ets_new_failure_2-inf-dpor.txt │ │ │ │ ├── ets_new_failure_3-ets_new_failure_3-inf-dpor.txt │ │ │ │ ├── ets_ref_keys-ets_ref_keys-0.txt │ │ │ │ ├── ets_ref_keys-ets_ref_keys-inf-dpor.txt │ │ │ │ ├── ets_rename-rename_bad-inf-optimal.txt │ │ │ │ ├── ets_rename-rename_good-inf-optimal.txt │ │ │ │ ├── ets_rename-rename_public-inf-optimal.txt │ │ │ │ ├── ets_select-match_against_different_keys-inf-dpor.txt │ │ │ │ ├── ets_select-match_against_different_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-match_against_different_tuples-inf-dpor.txt │ │ │ │ ├── ets_select-match_against_matching_keys-inf-dpor.txt │ │ │ │ ├── ets_select-match_against_matching_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-match_against_matching_tuples-inf-dpor.txt │ │ │ │ ├── ets_select-match_object_against_different_keys-inf-dpor.txt │ │ │ │ ├── ets_select-match_object_against_different_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-match_object_against_different_tuples-inf-dpor.txt │ │ │ │ ├── ets_select-match_object_against_matching_keys-inf-dpor.txt │ │ │ │ ├── ets_select-match_object_against_matching_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-match_object_against_matching_tuples-inf-dpor.txt │ │ │ │ ├── ets_select-select_against_different_keys-inf-dpor.txt │ │ │ │ ├── ets_select-select_against_different_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-select_against_different_tuples-inf-dpor.txt │ │ │ │ ├── ets_select-select_against_matching_keys-inf-dpor.txt │ │ │ │ ├── ets_select-select_against_matching_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-select_against_matching_tuples-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_different_any-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_different_keys-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_different_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_different_tuples-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_matching_any-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_matching_keys-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_matching_matchspec-inf-dpor.txt │ │ │ │ ├── ets_select-select_delete_against_matching_tuples-inf-dpor.txt │ │ │ │ ├── ets_unguarded-test-inf-optimal.txt │ │ │ │ ├── ets_unnamed-test1-inf-dpor.txt │ │ │ │ ├── ets_unnamed-test2-inf-dpor.txt │ │ │ │ ├── ets_unnamed-test3-inf-dpor.txt │ │ │ │ ├── ets_update_counter-test-inf-dpor.txt │ │ │ │ ├── ets_update_counter-test1-inf-dpor.txt │ │ │ │ ├── ets_update_element-race_update_element_r-inf-optimal.txt │ │ │ │ ├── ets_update_element-race_update_element_w-inf-optimal.txt │ │ │ │ ├── ets_update_element-update_element_bad-inf-optimal.txt │ │ │ │ ├── ets_update_element-update_element_good-inf-optimal.txt │ │ │ │ ├── ets_whereis-race_whereis_r-inf-optimal.txt │ │ │ │ ├── ets_whereis-race_whereis_w-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_bad-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_badarg-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_good-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_race_del-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_race_exit-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_race_wr-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_race_ww-inf-optimal.txt │ │ │ │ ├── ets_whereis-whereis_system-inf-optimal.txt │ │ │ │ ├── exit-test1-inf-dpor.txt │ │ │ │ ├── exit-test1-inf.txt │ │ │ │ ├── exit-test2-inf-dpor.txt │ │ │ │ ├── exit-test2-inf.txt │ │ │ │ ├── exit-test3-inf-dpor.txt │ │ │ │ ├── exit-test3-inf.txt │ │ │ │ ├── exit-test4-inf-dpor.txt │ │ │ │ ├── exit-test5-inf-dpor.txt │ │ │ │ ├── exit-test6-inf-dpor.txt │ │ │ │ ├── exit_kill-test-inf-dpor.txt │ │ │ │ ├── exit_message_unpredicted-exit_message_unpredicted-inf-dpor.txt │ │ │ │ ├── file_read_file-test-inf-dpor.txt │ │ │ │ ├── fun_fail-test1-inf-dpor.txt │ │ │ │ ├── fun_fail-test2-inf-dpor.txt │ │ │ │ ├── fun_fail-test3-inf-dpor.txt │ │ │ │ ├── group_leader-test-inf-dpor.txt │ │ │ │ ├── group_leader-test1-inf-dpor.txt │ │ │ │ ├── group_leader2-test-inf-dpor.txt │ │ │ │ ├── group_leader3-test-inf-dpor.txt │ │ │ │ ├── halt_error-test1-inf-optimal.txt │ │ │ │ ├── halt_error-test2-inf-optimal.txt │ │ │ │ ├── hibernate-test-inf-optimal.txt │ │ │ │ ├── hopeless_after-hopeless_after-inf-dpor.txt │ │ │ │ ├── i_hate_myself-i_hate_myself-inf-dpor.txt │ │ │ │ ├── ignore_error_1-test-inf-dpor.txt │ │ │ │ ├── ignore_error_2-test-inf-dpor.txt │ │ │ │ ├── ignore_error_3-test-inf-dpor.txt │ │ │ │ ├── immediate_delivery-test1-inf-dpor.txt │ │ │ │ ├── immediate_delivery-test2-inf-dpor.txt │ │ │ │ ├── immediate_delivery-test3-inf-dpor.txt │ │ │ │ ├── init_race_condition-test-inf-dpor.txt │ │ │ │ ├── init_race_condition-test-inf.txt │ │ │ │ ├── inspect_system-test-inf-dpor.txt │ │ │ │ ├── instr_apply-test-inf-dpor.txt │ │ │ │ ├── instr_apply-test-inf.txt │ │ │ │ ├── io_error-test-inf-dpor.txt │ │ │ │ ├── io_format-test-inf-dpor.txt │ │ │ │ ├── io_format_2-test-inf-dpor.txt │ │ │ │ ├── io_format_3-test-inf-dpor.txt │ │ │ │ ├── irrelevant_send-irrelevant_send-inf-dpor.txt │ │ │ │ ├── is_process_alive_1-is_process_alive_1-inf-dpor.txt │ │ │ │ ├── keep_going-test-inf-dpor.txt │ │ │ │ ├── kill_running_waiting-test-inf-dpor.txt │ │ │ │ ├── late_hope-late_hope-inf-dpor.txt │ │ │ │ ├── lid_test-test-inf-dpor.txt │ │ │ │ ├── link_after-link_after-inf-dpor.txt │ │ │ │ ├── load_before_start-test-inf-dpor.txt │ │ │ │ ├── main_and_backup-main_and_backup-inf-dpor.txt │ │ │ │ ├── many_kill-test-inf-dpor.txt │ │ │ │ ├── many_links-many_links-inf-dpor.txt │ │ │ │ ├── many_monitors-many_monitors-inf-dpor.txt │ │ │ │ ├── many_send_to_dead-many_send_to_dead-inf-dpor.txt │ │ │ │ ├── many_send_to_dead_2-many_send_to_dead_2-inf-dpor.txt │ │ │ │ ├── message_queue_length-test-inf-optimal.txt │ │ │ │ ├── messed_deliveries-test-inf-dpor.txt │ │ │ │ ├── module_loaded_dep-test-inf-optimal.txt │ │ │ │ ├── monitor_demonitor-monitor_demonitor-inf-dpor.txt │ │ │ │ ├── monitor_failure-monitor_failure-inf-dpor.txt │ │ │ │ ├── monitor_info-test1-inf-dpor.txt │ │ │ │ ├── monitor_info-test2-inf-dpor.txt │ │ │ │ ├── monitor_missing_messages-monitor_missing_messages-inf-dpor.txt │ │ │ │ ├── monitor_named-test-inf-dpor.txt │ │ │ │ ├── monitor_order-test-inf-optimal.txt │ │ │ │ ├── monotonic_time-test-inf-optimal.txt │ │ │ │ ├── node_names-test1-inf-dpor.txt │ │ │ │ ├── node_names-test2-inf-dpor.txt │ │ │ │ ├── normal_exit-normal_exit-inf-dpor.txt │ │ │ │ ├── normal_shutdown-test-inf-dpor.txt │ │ │ │ ├── normal_tuple_exit-test-inf-dpor.txt │ │ │ │ ├── os_timestamp-test-inf-dpor.txt │ │ │ │ ├── pids_are_unsafe-test-inf-dpor.txt │ │ │ │ ├── process_flag_link-process_flag_link-inf-dpor.txt │ │ │ │ ├── process_info-test1-inf-dpor.txt │ │ │ │ ├── process_info-test2-inf-dpor.txt │ │ │ │ ├── process_info-test3-inf-dpor.txt │ │ │ │ ├── process_info-test_current_function-inf-dpor.txt │ │ │ │ ├── process_info-test_current_function_top-inf-dpor.txt │ │ │ │ ├── process_info-test_message_queue_len-inf-dpor.txt │ │ │ │ ├── process_info-test_messages-inf-dpor.txt │ │ │ │ ├── process_info-test_mql_flush-inf-dpor.txt │ │ │ │ ├── processes-test1-inf-dpor.txt │ │ │ │ ├── processes-test2-inf-dpor.txt │ │ │ │ ├── processes_leader-test-inf-optimal.txt │ │ │ │ ├── racing_after-test-inf-dpor.txt │ │ │ │ ├── receive_after-receive_after-inf-dpor.txt │ │ │ │ ├── receive_and_after-receive_and_after-inf-dpor.txt │ │ │ │ ├── receive_catchall-test1-inf-dpor.txt │ │ │ │ ├── receive_catchall-test2-inf-dpor.txt │ │ │ │ ├── receive_catchall-test3-inf-dpor.txt │ │ │ │ ├── receive_exit-test-inf-dpor.txt │ │ │ │ ├── receive_exit-test1-inf-dpor.txt │ │ │ │ ├── receive_exit-test2-inf-dpor.txt │ │ │ │ ├── receive_exit-test3-inf-dpor.txt │ │ │ │ ├── receive_with_guard-receive_with_guard-inf-dpor.txt │ │ │ │ ├── receive_without_trapping-receive_without_trapping-inf-dpor.txt │ │ │ │ ├── register_link-test-inf-optimal.txt │ │ │ │ ├── registered_1-registered_1-inf-dpor.txt │ │ │ │ ├── registered_2-registered_2-inf-dpor.txt │ │ │ │ ├── registered_send_1-registered_send_1-inf-dpor.txt │ │ │ │ ├── registered_send_2-registered_send_2-inf-dpor.txt │ │ │ │ ├── registered_send_3-registered_send_3-inf-dpor.txt │ │ │ │ ├── replay_sanity-replay_sanity-inf-dpor.txt │ │ │ │ ├── report_blocks-test-inf-dpor.txt │ │ │ │ ├── reuse_raw_pid-test-inf-dpor.txt │ │ │ │ ├── safeops_coverage-test-inf-optimal.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-0-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-0.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-1-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-1.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-2-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-2.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-inf-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_receive_exit-inf.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-0-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-0.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-1-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-1.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-2-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-2.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-3-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-3.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-4-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-4.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-5-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-5.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-6-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-6.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-7-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-7.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-inf-dpor.txt │ │ │ │ ├── sched_tests-test_3_proc_send_receive-inf.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-0-dpor.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-0.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-1-dpor.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-1.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-2-dpor.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-2.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-3-dpor.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-3.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-inf-dpor.txt │ │ │ │ ├── sched_tests-test_after_clause_preemption-inf.txt │ │ │ │ ├── sched_tests-test_halt_0-0-dpor.txt │ │ │ │ ├── sched_tests-test_halt_0-0.txt │ │ │ │ ├── sched_tests-test_halt_0-inf-dpor.txt │ │ │ │ ├── sched_tests-test_halt_0-inf.txt │ │ │ │ ├── sched_tests-test_halt_1-0-dpor.txt │ │ │ │ ├── sched_tests-test_halt_1-0.txt │ │ │ │ ├── sched_tests-test_halt_1-inf-dpor.txt │ │ │ │ ├── sched_tests-test_halt_1-inf.txt │ │ │ │ ├── sched_tests-test_link_receive_exit-0-dpor.txt │ │ │ │ ├── sched_tests-test_link_receive_exit-0.txt │ │ │ │ ├── sched_tests-test_link_receive_exit-1-dpor.txt │ │ │ │ ├── sched_tests-test_link_receive_exit-1.txt │ │ │ │ ├── sched_tests-test_link_receive_exit-inf-dpor.txt │ │ │ │ ├── sched_tests-test_link_receive_exit-inf.txt │ │ │ │ ├── sched_tests-test_link_unlink-0-dpor.txt │ │ │ │ ├── sched_tests-test_link_unlink-0.txt │ │ │ │ ├── sched_tests-test_link_unlink-1-dpor.txt │ │ │ │ ├── sched_tests-test_link_unlink-1.txt │ │ │ │ ├── sched_tests-test_link_unlink-2-dpor.txt │ │ │ │ ├── sched_tests-test_link_unlink-2.txt │ │ │ │ ├── sched_tests-test_link_unlink-3-dpor.txt │ │ │ │ ├── sched_tests-test_link_unlink-3.txt │ │ │ │ ├── sched_tests-test_link_unlink-inf-dpor.txt │ │ │ │ ├── sched_tests-test_link_unlink-inf.txt │ │ │ │ ├── sched_tests-test_monitor_unexisting-0-dpor.txt │ │ │ │ ├── sched_tests-test_monitor_unexisting-0.txt │ │ │ │ ├── sched_tests-test_monitor_unexisting-1-dpor.txt │ │ │ │ ├── sched_tests-test_monitor_unexisting-1.txt │ │ │ │ ├── sched_tests-test_monitor_unexisting-inf-dpor.txt │ │ │ │ ├── sched_tests-test_monitor_unexisting-inf.txt │ │ │ │ ├── sched_tests-test_nested_send_receive_block_twice-0-dpor.txt │ │ │ │ ├── sched_tests-test_nested_send_receive_block_twice-0.txt │ │ │ │ ├── sched_tests-test_nested_send_receive_block_twice-1-dpor.txt │ │ │ │ ├── sched_tests-test_nested_send_receive_block_twice-1.txt │ │ │ │ ├── sched_tests-test_nested_send_receive_block_twice-2-dpor.txt │ │ │ │ ├── sched_tests-test_nested_send_receive_block_twice-2.txt │ │ │ │ ├── sched_tests-test_receive-0-dpor.txt │ │ │ │ ├── sched_tests-test_receive-0.txt │ │ │ │ ├── sched_tests-test_receive-inf-dpor.txt │ │ │ │ ├── sched_tests-test_receive-inf.txt │ │ │ │ ├── sched_tests-test_receive_2-0-dpor.txt │ │ │ │ ├── sched_tests-test_receive_2-0.txt │ │ │ │ ├── sched_tests-test_receive_2-inf-dpor.txt │ │ │ │ ├── sched_tests-test_receive_2-inf.txt │ │ │ │ ├── sched_tests-test_receive_after_block_expr_action-0-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_block_expr_action-0.txt │ │ │ │ ├── sched_tests-test_receive_after_block_expr_action-inf-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_block_expr_action-inf.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_no_patterns-0-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_no_patterns-0.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_no_patterns-inf-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_no_patterns-inf.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_with_pattern-0-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_with_pattern-0.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_with_pattern-inf-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_infinity_with_pattern-inf.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-0-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-0.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-1-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-1.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-2-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-2.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-inf-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_no_patterns-inf.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-0-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-0.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-1-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-1.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-2-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-2.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-3-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-3.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-inf-dpor.txt │ │ │ │ ├── sched_tests-test_receive_after_with_pattern-inf.txt │ │ │ │ ├── sched_tests-test_register_unregister-0-dpor.txt │ │ │ │ ├── sched_tests-test_register_unregister-0.txt │ │ │ │ ├── sched_tests-test_register_unregister-1-dpor.txt │ │ │ │ ├── sched_tests-test_register_unregister-1.txt │ │ │ │ ├── sched_tests-test_register_unregister-2-dpor.txt │ │ │ │ ├── sched_tests-test_register_unregister-2.txt │ │ │ │ ├── sched_tests-test_register_unregister-3-dpor.txt │ │ │ │ ├── sched_tests-test_register_unregister-3.txt │ │ │ │ ├── sched_tests-test_register_unregister-inf-dpor.txt │ │ │ │ ├── sched_tests-test_register_unregister-inf.txt │ │ │ │ ├── sched_tests-test_send-0-dpor.txt │ │ │ │ ├── sched_tests-test_send-0.txt │ │ │ │ ├── sched_tests-test_send-1-dpor.txt │ │ │ │ ├── sched_tests-test_send-1.txt │ │ │ │ ├── sched_tests-test_send-inf-dpor.txt │ │ │ │ ├── sched_tests-test_send-inf.txt │ │ │ │ ├── sched_tests-test_send_2-0-dpor.txt │ │ │ │ ├── sched_tests-test_send_2-0.txt │ │ │ │ ├── sched_tests-test_send_2-1-dpor.txt │ │ │ │ ├── sched_tests-test_send_2-1.txt │ │ │ │ ├── sched_tests-test_send_2-inf-dpor.txt │ │ │ │ ├── sched_tests-test_send_2-inf.txt │ │ │ │ ├── sched_tests-test_send_3-0-dpor.txt │ │ │ │ ├── sched_tests-test_send_3-0.txt │ │ │ │ ├── sched_tests-test_send_3-1-dpor.txt │ │ │ │ ├── sched_tests-test_send_3-1.txt │ │ │ │ ├── sched_tests-test_send_3-inf-dpor.txt │ │ │ │ ├── sched_tests-test_send_3-inf.txt │ │ │ │ ├── sched_tests-test_send_receive-0-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive-0.txt │ │ │ │ ├── sched_tests-test_send_receive-1-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive-1.txt │ │ │ │ ├── sched_tests-test_send_receive-2-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive-2.txt │ │ │ │ ├── sched_tests-test_send_receive-inf-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive-inf.txt │ │ │ │ ├── sched_tests-test_send_receive_2-0-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_2-0.txt │ │ │ │ ├── sched_tests-test_send_receive_2-1-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_2-1.txt │ │ │ │ ├── sched_tests-test_send_receive_2-2-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_2-2.txt │ │ │ │ ├── sched_tests-test_send_receive_2-inf-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_2-inf.txt │ │ │ │ ├── sched_tests-test_send_receive_3-0-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_3-0.txt │ │ │ │ ├── sched_tests-test_send_receive_3-1-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_3-1.txt │ │ │ │ ├── sched_tests-test_send_receive_3-2-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_3-2.txt │ │ │ │ ├── sched_tests-test_send_receive_3-inf-dpor.txt │ │ │ │ ├── sched_tests-test_send_receive_3-inf.txt │ │ │ │ ├── sched_tests-test_spawn-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn-0.txt │ │ │ │ ├── sched_tests-test_spawn-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn-1.txt │ │ │ │ ├── sched_tests-test_spawn-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn-inf.txt │ │ │ │ ├── sched_tests-test_spawn_link_race-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_race-0.txt │ │ │ │ ├── sched_tests-test_spawn_link_race-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_race-1.txt │ │ │ │ ├── sched_tests-test_spawn_link_race-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_race-inf.txt │ │ │ │ ├── sched_tests-test_spawn_link_receive_exit-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_receive_exit-0.txt │ │ │ │ ├── sched_tests-test_spawn_link_receive_exit-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_receive_exit-1.txt │ │ │ │ ├── sched_tests-test_spawn_link_receive_exit-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_receive_exit-inf.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-0.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-1.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-2-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-2.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink-inf.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_2-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_2-0.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_2-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_2-1.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_2-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_2-inf.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_3-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_3-0.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_3-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_3-1.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_3-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_link_unlink_3-inf.txt │ │ │ │ ├── sched_tests-test_spawn_monitor-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor-0.txt │ │ │ │ ├── sched_tests-test_spawn_monitor-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor-inf.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor-0.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor-1.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor-inf.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_2-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_2-0.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_2-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_2-1.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_2-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_2-inf.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_3-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_3-0.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_3-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_3-1.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_3-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_3-inf.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_4-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_4-0.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_4-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_4-1.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_4-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_monitor_demonitor_4-inf.txt │ │ │ │ ├── sched_tests-test_spawn_opt_link_receive_exit-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_opt_link_receive_exit-0.txt │ │ │ │ ├── sched_tests-test_spawn_opt_link_receive_exit-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_opt_link_receive_exit-1.txt │ │ │ │ ├── sched_tests-test_spawn_opt_link_receive_exit-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_opt_link_receive_exit-inf.txt │ │ │ │ ├── sched_tests-test_spawn_opt_monitor-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_opt_monitor-0.txt │ │ │ │ ├── sched_tests-test_spawn_opt_monitor-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_opt_monitor-inf.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-0-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-0.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-1-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-1.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-2-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-2.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-inf-dpor.txt │ │ │ │ ├── sched_tests-test_spawn_register_race-inf.txt │ │ │ │ ├── sched_tests-test_trap_exit_timing-0-dpor.txt │ │ │ │ ├── sched_tests-test_trap_exit_timing-0.txt │ │ │ │ ├── sched_tests-test_trap_exit_timing-1-dpor.txt │ │ │ │ ├── sched_tests-test_trap_exit_timing-1.txt │ │ │ │ ├── sched_tests-test_trap_exit_timing-inf-dpor.txt │ │ │ │ ├── sched_tests-test_trap_exit_timing-inf.txt │ │ │ │ ├── sched_tests-test_var_mod_fun-0-dpor.txt │ │ │ │ ├── sched_tests-test_var_mod_fun-0.txt │ │ │ │ ├── sched_tests-test_var_mod_fun-1-dpor.txt │ │ │ │ ├── sched_tests-test_var_mod_fun-1.txt │ │ │ │ ├── sched_tests-test_var_mod_fun-inf-dpor.txt │ │ │ │ ├── sched_tests-test_var_mod_fun-inf.txt │ │ │ │ ├── sched_tests-test_whereis-0-dpor.txt │ │ │ │ ├── sched_tests-test_whereis-0.txt │ │ │ │ ├── sched_tests-test_whereis-1-dpor.txt │ │ │ │ ├── sched_tests-test_whereis-1.txt │ │ │ │ ├── sched_tests-test_whereis-2-dpor.txt │ │ │ │ ├── sched_tests-test_whereis-2.txt │ │ │ │ ├── sched_tests-test_whereis-inf-dpor.txt │ │ │ │ ├── sched_tests-test_whereis-inf.txt │ │ │ │ ├── self_exits-test1-inf-dpor.txt │ │ │ │ ├── self_exits-test2-inf-dpor.txt │ │ │ │ ├── self_exits-test3-inf-dpor.txt │ │ │ │ ├── self_exits-test4-inf-dpor.txt │ │ │ │ ├── send_after-test1-inf-dpor.txt │ │ │ │ ├── send_after-test11-inf-dpor.txt │ │ │ │ ├── send_after-test12-inf-dpor.txt │ │ │ │ ├── send_after-test13-inf-dpor.txt │ │ │ │ ├── send_after-test14-inf-dpor.txt │ │ │ │ ├── send_after-test15-inf-dpor.txt │ │ │ │ ├── send_after-test16-inf-dpor.txt │ │ │ │ ├── send_after-test17-inf-dpor.txt │ │ │ │ ├── send_after-test18-inf-dpor.txt │ │ │ │ ├── send_after-test19-inf-dpor.txt │ │ │ │ ├── send_after-test1A-inf-dpor.txt │ │ │ │ ├── send_after-test1B-inf-dpor.txt │ │ │ │ ├── send_after-test1C-inf-dpor.txt │ │ │ │ ├── send_after-test1D-inf-dpor.txt │ │ │ │ ├── send_after-test2-inf-dpor.txt │ │ │ │ ├── send_after-test3-inf-dpor.txt │ │ │ │ ├── send_after-test4-inf-dpor.txt │ │ │ │ ├── send_after-test5-inf-dpor.txt │ │ │ │ ├── send_after-test6-inf-dpor.txt │ │ │ │ ├── send_after-test7-inf-dpor.txt │ │ │ │ ├── send_after-test8-inf-dpor.txt │ │ │ │ ├── send_after-test9-inf-dpor.txt │ │ │ │ ├── send_after-testA-inf-dpor.txt │ │ │ │ ├── send_after-testB-inf-dpor.txt │ │ │ │ ├── send_after-testC-inf-dpor.txt │ │ │ │ ├── send_after_trouble-test-inf-dpor.txt │ │ │ │ ├── send_named_vs_send-send_named_vs_send-inf-dpor.txt │ │ │ │ ├── send_receive_dependencies-send_receive_dependencies-inf-dpor.txt │ │ │ │ ├── send_self-test-inf-dpor.txt │ │ │ │ ├── send_self-test1-inf-dpor.txt │ │ │ │ ├── send_self-test2-inf-dpor.txt │ │ │ │ ├── send_self-test3-inf-dpor.txt │ │ │ │ ├── send_using_names-send_using_names-inf-dpor.txt │ │ │ │ ├── send_vs_trap_exit-send_vs_trap_exit-inf-dpor.txt │ │ │ │ ├── signals_vs_messages-test-inf-dpor.txt │ │ │ │ ├── signals_vs_messages-test1-inf-dpor.txt │ │ │ │ ├── simple_ets-simple_ets-inf-dpor.txt │ │ │ │ ├── simple_spawn-simple_spawn-inf-dpor.txt │ │ │ │ ├── sleep_set_block_not_bad-test-inf-persistent.txt │ │ │ │ ├── spawn_and_send-spawn_and_send-inf-dpor.txt │ │ │ │ ├── spawn_bad-test-inf-dpor.txt │ │ │ │ ├── spawn_failure-spawn_failure-inf-dpor.txt │ │ │ │ ├── spawn_monitor_test-spawn_monitor_test-inf-dpor.txt │ │ │ │ ├── spawned_sender_crasher-spawned_sender_crasher-inf-dpor.txt │ │ │ │ ├── spawned_senders-spawned_senders-inf-dpor.txt │ │ │ │ ├── stacktrace_vs_exit-test-inf-optimal.txt │ │ │ │ ├── system_info-test-inf-optimal.txt │ │ │ │ ├── system_instant_delivery-test-inf-optimal.txt │ │ │ │ ├── system_non_instant_delivery-test-inf-optimal.txt │ │ │ │ ├── timers_afters_exits_crashes-both_crash-inf-dpor.txt │ │ │ │ ├── timers_afters_exits_crashes-child_crashes-inf-dpor.txt │ │ │ │ ├── timers_afters_exits_crashes-my_exit_bad-inf-dpor.txt │ │ │ │ ├── timers_afters_exits_crashes-my_exit_ok-inf-dpor.txt │ │ │ │ ├── timers_afters_exits_crashes-my_start_timer-inf-dpor.txt │ │ │ │ ├── timestamps-exit_system_time0-inf-optimal.txt │ │ │ │ ├── timestamps-exit_system_time1-inf-optimal.txt │ │ │ │ ├── timestamps-exit_timestamp-inf-optimal.txt │ │ │ │ ├── timestamps-other_system_time0-inf-optimal.txt │ │ │ │ ├── timestamps-other_system_time1-inf-optimal.txt │ │ │ │ ├── timestamps-other_timestamp-inf-optimal.txt │ │ │ │ ├── trace_the_sender-trace_the_sender-inf-dpor.txt │ │ │ │ ├── transparent_instrumentation-test-inf-dpor.txt │ │ │ │ ├── trigger_the_after-trigger_the_after-inf-dpor.txt │ │ │ │ ├── unregister_deadlocked-test1-inf-dpor.txt │ │ │ │ └── waiting-waiting-inf-dpor.txt │ │ │ └── src/ │ │ │ ├── ac_tab_delete_crash.erl │ │ │ ├── ac_tab_insert.erl │ │ │ ├── after_test_2.erl │ │ │ ├── after_test_3.erl │ │ │ ├── after_vs_trap_exit.erl │ │ │ ├── allow_first_crash.erl │ │ │ ├── application_check.erl │ │ │ ├── assert_filter.erl │ │ │ ├── assert_tip.erl │ │ │ ├── bad_dictionary.erl │ │ │ ├── bad_whereis.erl │ │ │ ├── code_when_undef.erl │ │ │ ├── concuerror_crash.erl │ │ │ ├── curious_builtins.erl │ │ │ ├── dead_receive.erl │ │ │ ├── deeper_exit.erl │ │ │ ├── demonitor_exhaustive.erl │ │ │ ├── demonitor_flush.erl │ │ │ ├── demonitor_sane_opts.erl │ │ │ ├── depth_bound.erl │ │ │ ├── erlang_display.erl │ │ │ ├── erlang_display_string.erl │ │ │ ├── ets_delete.erl │ │ │ ├── ets_delete_2.erl │ │ │ ├── ets_delete_all.erl │ │ │ ├── ets_delete_object.erl │ │ │ ├── ets_exceptions.erl │ │ │ ├── ets_give_away.erl │ │ │ ├── ets_global_global.erl │ │ │ ├── ets_heir.erl │ │ │ ├── ets_heir_2.erl │ │ │ ├── ets_info.erl │ │ │ ├── ets_info_details.erl │ │ │ ├── ets_insert_new.erl │ │ │ ├── ets_insert_objects.erl │ │ │ ├── ets_many_insert_new.erl │ │ │ ├── ets_new.erl │ │ │ ├── ets_new_failure.erl │ │ │ ├── ets_new_failure_2.erl │ │ │ ├── ets_new_failure_3.erl │ │ │ ├── ets_ref_keys.erl │ │ │ ├── ets_rename.erl │ │ │ ├── ets_select.erl │ │ │ ├── ets_unguarded.erl │ │ │ ├── ets_unnamed.erl │ │ │ ├── ets_update_counter.erl │ │ │ ├── ets_update_element.erl │ │ │ ├── ets_whereis.erl │ │ │ ├── exit.erl │ │ │ ├── exit_kill.erl │ │ │ ├── exit_message_unpredicted.erl │ │ │ ├── file_read_file.erl │ │ │ ├── fun_fail.erl │ │ │ ├── group_leader.erl │ │ │ ├── group_leader2.erl │ │ │ ├── group_leader3.erl │ │ │ ├── halt_error.erl │ │ │ ├── halt_support.erl │ │ │ ├── hibernate.erl │ │ │ ├── hopeless_after.erl │ │ │ ├── i_hate_myself.erl │ │ │ ├── ignore_error_1.erl │ │ │ ├── ignore_error_2.erl │ │ │ ├── ignore_error_3.erl │ │ │ ├── immediate_delivery.erl │ │ │ ├── infinite_loop.erl │ │ │ ├── init_race_condition.erl │ │ │ ├── inspect_system.erl │ │ │ ├── instr_apply.erl │ │ │ ├── io_error.erl │ │ │ ├── io_format.erl │ │ │ ├── io_format_2.erl │ │ │ ├── io_format_3.erl │ │ │ ├── irrelevant_send.erl │ │ │ ├── is_process_alive_1.erl │ │ │ ├── keep_going.erl │ │ │ ├── kill_running_waiting.erl │ │ │ ├── late_hope.erl │ │ │ ├── lid_test.erl │ │ │ ├── link_after.erl │ │ │ ├── load_before_start.erl │ │ │ ├── log_all.erl │ │ │ ├── main_and_backup.erl │ │ │ ├── many_kill.erl │ │ │ ├── many_links.erl │ │ │ ├── many_monitors.erl │ │ │ ├── many_send_to_dead.erl │ │ │ ├── many_send_to_dead_2.erl │ │ │ ├── message_queue_length.erl │ │ │ ├── messed_deliveries.erl │ │ │ ├── module_loaded_dep.erl │ │ │ ├── monitor_demonitor.erl │ │ │ ├── monitor_failure.erl │ │ │ ├── monitor_info.erl │ │ │ ├── monitor_missing_messages.erl │ │ │ ├── monitor_named.erl │ │ │ ├── monitor_order.erl │ │ │ ├── monotonic_time.erl │ │ │ ├── node_names.erl │ │ │ ├── nonexistent_module.erl │ │ │ ├── normal_exit.erl │ │ │ ├── normal_shutdown.erl │ │ │ ├── normal_tuple_exit.erl │ │ │ ├── os_timestamp.erl │ │ │ ├── pids_are_unsafe.erl │ │ │ ├── process_flag_link.erl │ │ │ ├── process_info.erl │ │ │ ├── processes.erl │ │ │ ├── processes_leader.erl │ │ │ ├── racing_after.erl │ │ │ ├── receive_after.erl │ │ │ ├── receive_and_after.erl │ │ │ ├── receive_catchall.erl │ │ │ ├── receive_exit.erl │ │ │ ├── receive_loop.erl │ │ │ ├── receive_with_guard.erl │ │ │ ├── receive_without_trapping.erl │ │ │ ├── register_link.erl │ │ │ ├── registered_1.erl │ │ │ ├── registered_2.erl │ │ │ ├── registered_send_1.erl │ │ │ ├── registered_send_2.erl │ │ │ ├── registered_send_3.erl │ │ │ ├── replay_sanity.erl │ │ │ ├── report_blocks.erl │ │ │ ├── reuse_raw_pid.erl │ │ │ ├── safeops_coverage.erl │ │ │ ├── sched_tests.erl │ │ │ ├── self_exits.erl │ │ │ ├── send_after.erl │ │ │ ├── send_after_trouble.erl │ │ │ ├── send_named_vs_send.erl │ │ │ ├── send_receive_dependencies.erl │ │ │ ├── send_self.erl │ │ │ ├── send_using_names.erl │ │ │ ├── send_vs_trap_exit.erl │ │ │ ├── signals_vs_messages.erl │ │ │ ├── simple_ets.erl │ │ │ ├── simple_spawn.erl │ │ │ ├── sleep_set_block_not_bad.erl │ │ │ ├── spawn_and_send.erl │ │ │ ├── spawn_bad.erl │ │ │ ├── spawn_failure.erl │ │ │ ├── spawn_monitor_test.erl │ │ │ ├── spawned_sender_crasher.erl │ │ │ ├── spawned_senders.erl │ │ │ ├── stacktrace_vs_exit.erl │ │ │ ├── system_info.erl │ │ │ ├── system_instant_delivery.erl │ │ │ ├── system_non_instant_delivery.erl │ │ │ ├── timers_afters_exits_crashes.erl │ │ │ ├── timestamps.erl │ │ │ ├── trace_the_sender.erl │ │ │ ├── transparent_instrumentation.erl │ │ │ ├── trigger_the_after.erl │ │ │ ├── unregister_deadlocked.erl │ │ │ ├── unsupported_exit.erl │ │ │ └── waiting.erl │ │ ├── bounding_tests/ │ │ │ ├── results/ │ │ │ │ ├── manolis-test_2workers_small-1-bpor-source.txt │ │ │ │ ├── readers-test-0-dpor.txt │ │ │ │ ├── readers-test-1-dpor.txt │ │ │ │ ├── readers-test-2-dpor.txt │ │ │ │ ├── readers-test-3-dpor.txt │ │ │ │ ├── readers-test-4-dpor.txt │ │ │ │ ├── readers-test-5-dpor.txt │ │ │ │ ├── readers-test-6-dpor.txt │ │ │ │ ├── readers-test-7-dpor.txt │ │ │ │ ├── readers_2-test-0-dpor.txt │ │ │ │ ├── readers_2-test-1-dpor.txt │ │ │ │ ├── readers_2-test-2-dpor.txt │ │ │ │ ├── readers_2-test-3-dpor.txt │ │ │ │ ├── readers_2-test-4-dpor.txt │ │ │ │ ├── readers_2-test-5-dpor.txt │ │ │ │ ├── readers_2-test-6-dpor.txt │ │ │ │ ├── readers_2-test-7-dpor.txt │ │ │ │ ├── readers_compare-test-0-bpor-persistent.txt │ │ │ │ ├── readers_compare-test-0-bpor-source.txt │ │ │ │ ├── readers_compare-test-0-optimal.txt │ │ │ │ ├── readers_compare-test-0-persistent.txt │ │ │ │ ├── readers_compare-test-0-source.txt │ │ │ │ ├── readers_compare-test-1-bpor-persistent.txt │ │ │ │ ├── readers_compare-test-1-bpor-source.txt │ │ │ │ ├── readers_compare-test-1-optimal.txt │ │ │ │ ├── readers_compare-test-1-persistent.txt │ │ │ │ ├── readers_compare-test-1-source.txt │ │ │ │ ├── readers_compare-test-2-bpor-persistent.txt │ │ │ │ ├── readers_compare-test-2-bpor-source.txt │ │ │ │ ├── readers_compare-test-2-optimal.txt │ │ │ │ ├── readers_compare-test-2-persistent.txt │ │ │ │ ├── readers_compare-test-2-source.txt │ │ │ │ ├── readers_compare-test-6-bpor-persistent.txt │ │ │ │ ├── readers_compare-test-6-bpor-source.txt │ │ │ │ ├── readers_compare-test-6-optimal.txt │ │ │ │ ├── readers_compare-test-6-persistent.txt │ │ │ │ ├── readers_compare-test-6-source.txt │ │ │ │ ├── readers_delay-test-0-nodpor.txt │ │ │ │ ├── readers_delay-test-1-nodpor.txt │ │ │ │ ├── readers_delay-test-2-nodpor.txt │ │ │ │ ├── readers_delay-test-3-nodpor.txt │ │ │ │ ├── readers_delay-test-4-nodpor.txt │ │ │ │ ├── readers_delay-test-5-nodpor.txt │ │ │ │ ├── readers_delay-test-6-nodpor.txt │ │ │ │ ├── readers_no_preempt-test-0-bpor-persistent.txt │ │ │ │ ├── readers_no_preempt-test-0-bpor-source.txt │ │ │ │ ├── readers_no_preempt-test-0-optimal.txt │ │ │ │ ├── readers_no_preempt-test-0-persistent.txt │ │ │ │ ├── readers_no_preempt-test-0-source.txt │ │ │ │ ├── readers_no_preempt-test-1-bpor-persistent.txt │ │ │ │ ├── readers_no_preempt-test-1-bpor-source.txt │ │ │ │ ├── readers_no_preempt-test-1-optimal.txt │ │ │ │ ├── readers_no_preempt-test-1-persistent.txt │ │ │ │ ├── readers_no_preempt-test-1-source.txt │ │ │ │ ├── readers_no_preempt-test-2-bpor-persistent.txt │ │ │ │ ├── readers_no_preempt-test-2-bpor-source.txt │ │ │ │ ├── readers_no_preempt-test-2-optimal.txt │ │ │ │ ├── readers_no_preempt-test-2-persistent.txt │ │ │ │ ├── readers_no_preempt-test-2-source.txt │ │ │ │ ├── simple_delay-test-0-dpor.txt │ │ │ │ ├── simple_delay-test-1-dpor.txt │ │ │ │ ├── simple_delay-test-2-dpor.txt │ │ │ │ ├── writers-test-0-dpor.txt │ │ │ │ ├── writers-test-1-dpor.txt │ │ │ │ ├── writers-test-2-dpor.txt │ │ │ │ ├── writers-test-3-dpor.txt │ │ │ │ ├── writers-test-4-dpor.txt │ │ │ │ ├── writers-test-5-dpor.txt │ │ │ │ ├── writers-test-6-dpor.txt │ │ │ │ └── writers-test-7-dpor.txt │ │ │ └── src/ │ │ │ ├── manolis/ │ │ │ │ ├── rush_hour.erl │ │ │ │ ├── search.erl │ │ │ │ └── test.erl │ │ │ ├── readers.erl │ │ │ ├── readers_2.erl │ │ │ ├── readers_compare.erl │ │ │ ├── readers_delay.erl │ │ │ ├── readers_no_preempt.erl │ │ │ ├── simple_delay.erl │ │ │ └── writers.erl │ │ ├── corner_tests/ │ │ │ ├── results/ │ │ │ │ └── deliver_exhaustive-test-inf-nodpor.txt │ │ │ └── src/ │ │ │ └── deliver_exhaustive.erl │ │ ├── dpor_tests/ │ │ │ ├── results/ │ │ │ │ ├── POPL_optimal_example-test-inf-dpor.txt │ │ │ │ ├── POPL_optimal_example-test-inf-source.txt │ │ │ │ ├── after_test-after_test-inf-dpor.txt │ │ │ │ ├── after_test_reduced-after_test_reduced-inf-dpor.txt │ │ │ │ ├── combination_lock_1-combination_lock_1-inf-dpor.txt │ │ │ │ ├── combination_lock_1-combination_lock_1-inf-source.txt │ │ │ │ ├── complete_test_1-complete_test_1-inf-dpor.txt │ │ │ │ ├── complete_test_2-complete_test_2-inf-dpor.txt │ │ │ │ ├── conditional_readers-conditional_readers-inf-dpor.txt │ │ │ │ ├── conditional_readers_2-conditional_readers_2-inf-dpor.txt │ │ │ │ ├── conditional_readers_3-conditional_readers_3-inf-dpor.txt │ │ │ │ ├── depend_1-depend_1-inf-dpor.txt │ │ │ │ ├── depend_2-depend_2-inf-dpor.txt │ │ │ │ ├── depend_3-depend_3-inf-dpor.txt │ │ │ │ ├── depend_4-depend_4-inf-dpor.txt │ │ │ │ ├── depend_4_screen-depend_4_screen-inf-dpor.txt │ │ │ │ ├── depend_5-depend_5-inf-dpor.txt │ │ │ │ ├── depend_6-depend_6-inf-dpor.txt │ │ │ │ ├── depend_6_newest-test-inf-dpor.txt │ │ │ │ ├── depend_6_round-test-inf-dpor.txt │ │ │ │ ├── diff_dep_1-diff_dep_1-inf-dpor.txt │ │ │ │ ├── diff_dep_2-diff_dep_2-inf-dpor.txt │ │ │ │ ├── diff_dep_3-diff_dep_3-inf-dpor.txt │ │ │ │ ├── diff_obs-test-inf-optimal.txt │ │ │ │ ├── difficult_readers-difficult_readers-inf-dpor.txt │ │ │ │ ├── ets_cross-test-inf-dpor.txt │ │ │ │ ├── ets_dependencies-ets_dependencies-inf-dpor.txt │ │ │ │ ├── ets_dependencies_2-ets_dependencies_2-inf-dpor.txt │ │ │ │ ├── ets_dependencies_3-ets_dependencies_3-inf-dpor.txt │ │ │ │ ├── ets_dependencies_deeper-ets_dependencies_deeper-inf-dpor.txt │ │ │ │ ├── ets_dependencies_deeper_2-ets_dependencies_deeper_2-inf-dpor.txt │ │ │ │ ├── ets_dependencies_n-ets_dependencies_n-inf-dpor.txt │ │ │ │ ├── ets_writing_same-ets_writing_same-inf-dpor.txt │ │ │ │ ├── etsi-etsi-inf-dpor.txt │ │ │ │ ├── etsi_2-etsi_2-inf-dpor.txt │ │ │ │ ├── etsi_3-etsi_3-inf-dpor.txt │ │ │ │ ├── etsi_4-etsi_4-inf-dpor.txt │ │ │ │ ├── etsi_5-etsi_5-inf-dpor.txt │ │ │ │ ├── etsi_6-etsi_6-inf-dpor.txt │ │ │ │ ├── etsi_7-etsi_7-inf-dpor.txt │ │ │ │ ├── etsi_8-etsi_8-inf-dpor.txt │ │ │ │ ├── file_system_example-test-inf-dpor.txt │ │ │ │ ├── hairbrush_teeth_1-hairbrush_teeth_1-inf-dpor.txt │ │ │ │ ├── hairbrush_teeth_2-hairbrush_teeth_2-inf-dpor.txt │ │ │ │ ├── hairbrush_teeth_3-hairbrush_teeth_3-inf-dpor.txt │ │ │ │ ├── hairbrush_teeth_4-hairbrush_teeth_4-inf-dpor.txt │ │ │ │ ├── independent_receivers-independent_receivers-inf-dpor.txt │ │ │ │ ├── indifferent_senders-indifferent_senders-inf-dpor.txt │ │ │ │ ├── last_zero-test-inf-dpor.txt │ │ │ │ ├── last_zero-test-inf-source.txt │ │ │ │ ├── long_chain-long_chain-inf-dpor.txt │ │ │ │ ├── many_initials-many_initials-inf-dpor.txt │ │ │ │ ├── many_kill_2-test-inf-dpor.txt │ │ │ │ ├── manywrite-manywrite-inf-dpor.txt │ │ │ │ ├── manywrite_2-manywrite_2-inf-dpor.txt │ │ │ │ ├── messages_1-messages_1-inf-dpor.txt │ │ │ │ ├── messages_2-test-inf-dpor.txt │ │ │ │ ├── no_observers-test-inf-optimal.txt │ │ │ │ ├── not_prerequisite_1-not_prerequisite_1-inf-dpor.txt │ │ │ │ ├── observable_others-dep_obs-inf-optimal.txt │ │ │ │ ├── observable_others-dep_unobs-inf-optimal.txt │ │ │ │ ├── observable_others-indep_obs-inf-optimal.txt │ │ │ │ ├── observable_others-indep_unobs-inf-optimal.txt │ │ │ │ ├── precise_demonitor-demonitor_empty-inf-optimal.txt │ │ │ │ ├── precise_demonitor-demonitor_flush-inf-optimal.txt │ │ │ │ ├── precise_demonitor-demonitor_flush_info-inf-optimal.txt │ │ │ │ ├── precise_demonitor-demonitor_info-inf-optimal.txt │ │ │ │ ├── priorities-priorities-inf-dpor.txt │ │ │ │ ├── process_info-links-inf-optimal.txt │ │ │ │ ├── proxy-proxy-inf-dpor.txt │ │ │ │ ├── proxy2-proxy2-inf-dpor.txt │ │ │ │ ├── readers-test-inf-optimal.txt │ │ │ │ ├── readers-test-inf-persistent.txt │ │ │ │ ├── readers-test-inf-source.txt │ │ │ │ ├── readers_basic-readers_basic-inf-dpor.txt │ │ │ │ ├── readers_rrw-readers_rrw-inf-dpor.txt │ │ │ │ ├── readers_rwr-readers_rwr-inf-dpor.txt │ │ │ │ ├── readers_wrr-readers_wrr-inf-dpor.txt │ │ │ │ ├── receive_order-test1-inf-dpor.txt │ │ │ │ ├── receive_order-test2-inf-dpor.txt │ │ │ │ ├── receive_order-test3-inf-dpor.txt │ │ │ │ ├── register_again-register_again-inf-dpor.txt │ │ │ │ ├── register_again_2-register_again_2-inf-dpor.txt │ │ │ │ ├── same_messages-same_messages-inf-dpor.txt │ │ │ │ ├── same_messages_2-same_messages_2-inf-dpor.txt │ │ │ │ ├── same_messages_3-same_messages_3-inf-dpor.txt │ │ │ │ ├── same_messages_4-same_messages_4-inf-dpor.txt │ │ │ │ ├── send_it_ets-send_it_ets-inf-dpor.txt │ │ │ │ ├── sleeping_races_1-sleeping_races_1-inf-dpor.txt │ │ │ │ ├── sleeping_races_2-sleeping_races_2-inf-dpor.txt │ │ │ │ ├── sleeping_races_3_read-sleeping_races_3_read-inf-dpor.txt │ │ │ │ ├── sleeping_races_3_write-sleeping_races_3_write-inf-dpor.txt │ │ │ │ ├── sleeping_races_4-sleeping_races_4-inf-dpor.txt │ │ │ │ ├── sleeping_races_5-sleeping_races_5-inf-dpor.txt │ │ │ │ ├── sleeping_races_6-sleeping_races_6-inf-dpor.txt │ │ │ │ ├── sleeping_races_6-sleeping_races_6-inf-source.txt │ │ │ │ ├── sleeping_races_7-sleeping_races_7-inf-dpor.txt │ │ │ │ ├── sleeping_races_8-sleeping_races_8-inf-dpor.txt │ │ │ │ ├── stress-stress-inf-dpor.txt │ │ │ │ ├── sync_start-sync_start-inf-dpor.txt │ │ │ │ ├── test_after_spawns-test_after_spawns-inf-dpor.txt │ │ │ │ ├── timer_replayed-test-inf-dpor.txt │ │ │ │ ├── tricky_predecessors-tricky_predecessors-inf-dpor.txt │ │ │ │ ├── two_writers_readers_1-two_writers_readers_1-inf-dpor.txt │ │ │ │ ├── two_writers_readers_2-two_writers_readers_2-inf-dpor.txt │ │ │ │ ├── wakeup_many-wakeup_many-inf-dpor.txt │ │ │ │ ├── workers-workers-inf-dpor.txt │ │ │ │ ├── workers_2-workers_2-inf-dpor.txt │ │ │ │ └── writeread-writeread-inf-dpor.txt │ │ │ └── src/ │ │ │ ├── POPL_optimal_example.erl │ │ │ ├── after_test.erl │ │ │ ├── after_test_reduced.erl │ │ │ ├── combination_lock_1.erl │ │ │ ├── complete_test_1.erl │ │ │ ├── complete_test_2.erl │ │ │ ├── conditional_readers.erl │ │ │ ├── conditional_readers_2.erl │ │ │ ├── conditional_readers_3.erl │ │ │ ├── depend_1.erl │ │ │ ├── depend_2.erl │ │ │ ├── depend_3.erl │ │ │ ├── depend_4.erl │ │ │ ├── depend_4_screen.erl │ │ │ ├── depend_5.erl │ │ │ ├── depend_6.erl │ │ │ ├── depend_6_newest.erl │ │ │ ├── depend_6_round.erl │ │ │ ├── diff_dep_1.erl │ │ │ ├── diff_dep_2.erl │ │ │ ├── diff_dep_3.erl │ │ │ ├── diff_obs.erl │ │ │ ├── difficult_readers.erl │ │ │ ├── ets_cross.erl │ │ │ ├── ets_dependencies.erl │ │ │ ├── ets_dependencies_2.erl │ │ │ ├── ets_dependencies_3.erl │ │ │ ├── ets_dependencies_deeper.erl │ │ │ ├── ets_dependencies_deeper_2.erl │ │ │ ├── ets_dependencies_n.erl │ │ │ ├── ets_writing_same.erl │ │ │ ├── etsi.erl │ │ │ ├── etsi_2.erl │ │ │ ├── etsi_3.erl │ │ │ ├── etsi_4.erl │ │ │ ├── etsi_5.erl │ │ │ ├── etsi_6.erl │ │ │ ├── etsi_7.erl │ │ │ ├── etsi_8.erl │ │ │ ├── file_system_example.erl │ │ │ ├── hairbrush_teeth_1.erl │ │ │ ├── hairbrush_teeth_2.erl │ │ │ ├── hairbrush_teeth_3.erl │ │ │ ├── hairbrush_teeth_4.erl │ │ │ ├── independent_receivers.erl │ │ │ ├── indifferent_senders.erl │ │ │ ├── last_zero.erl │ │ │ ├── long_chain.erl │ │ │ ├── many_initials.erl │ │ │ ├── many_kill_2.erl │ │ │ ├── manywrite.erl │ │ │ ├── manywrite_2.erl │ │ │ ├── messages_1.erl │ │ │ ├── messages_2.erl │ │ │ ├── no_observers.erl │ │ │ ├── not_prerequisite_1.erl │ │ │ ├── observable_others.erl │ │ │ ├── precise_demonitor.erl │ │ │ ├── priorities.erl │ │ │ ├── process_info.erl │ │ │ ├── proxy.erl │ │ │ ├── proxy2.erl │ │ │ ├── readers.erl │ │ │ ├── readers_basic.erl │ │ │ ├── readers_rrw.erl │ │ │ ├── readers_rwr.erl │ │ │ ├── readers_wrr.erl │ │ │ ├── receive_order.erl │ │ │ ├── register_again.erl │ │ │ ├── register_again_2.erl │ │ │ ├── same_messages.erl │ │ │ ├── same_messages_2.erl │ │ │ ├── same_messages_3.erl │ │ │ ├── same_messages_4.erl │ │ │ ├── send_it_ets.erl │ │ │ ├── sleeping_races_1.erl │ │ │ ├── sleeping_races_2.erl │ │ │ ├── sleeping_races_3_read.erl │ │ │ ├── sleeping_races_3_write.erl │ │ │ ├── sleeping_races_4.erl │ │ │ ├── sleeping_races_5.erl │ │ │ ├── sleeping_races_6.erl │ │ │ ├── sleeping_races_7.erl │ │ │ ├── sleeping_races_8.erl │ │ │ ├── stress.erl │ │ │ ├── sync_start.erl │ │ │ ├── test_after_spawns.erl │ │ │ ├── timer_replayed.erl │ │ │ ├── tricky_predecessors.erl │ │ │ ├── two_writers_readers_1.erl │ │ │ ├── two_writers_readers_2.erl │ │ │ ├── wakeup_many.erl │ │ │ ├── workers.erl │ │ │ ├── workers_2.erl │ │ │ └── writeread.erl │ │ └── erlang_tests/ │ │ ├── results/ │ │ │ ├── erlang_maps-maps_fold-inf-optimal.txt │ │ │ ├── erlang_timer-cancel_bad-inf-optimal.txt │ │ │ ├── erlang_timer-cancel_badarg-inf-optimal.txt │ │ │ ├── erlang_timer-read_bad-inf-optimal.txt │ │ │ └── erlang_timer-read_badarg-inf-optimal.txt │ │ └── src/ │ │ ├── erlang_maps.erl │ │ └── erlang_timer.erl │ ├── test_template.erl │ └── test_template_stripped.erl └── tests-real/ ├── Makefile ├── README.md └── suites/ ├── check_version.escript ├── erlang-litmus/ │ ├── .gitignore │ ├── Makefile │ ├── run_litmus.escript │ └── test ├── makefile/ │ ├── Makefile │ └── makefile-tests ├── options/ │ ├── .gitignore │ ├── Makefile │ ├── autocomplete/ │ │ ├── autocomplete.sh │ │ ├── autocomplete_common.erl │ │ ├── autocomplete_help.erl │ │ ├── autocomplete_long.erl │ │ └── autocomplete_short.erl │ ├── autocomplete-tests │ ├── footer │ ├── header │ ├── option1-tests │ ├── option2-tests │ ├── option3-tests │ ├── other-tests │ └── src/ │ ├── bad.erl │ ├── bad_attribute.erl │ ├── bad_attribute_1.erl │ ├── bad_attribute_2.erl │ ├── bad_attribute_3.erl │ ├── bad_attribute_4.erl │ ├── bad_attribute_5.erl │ ├── debug_works.erl │ ├── eunit_sample.erl │ ├── excluded.erl │ ├── excluding.erl │ ├── first_and_other_deadlock.erl │ ├── foo.erl │ ├── from_shell.erl │ ├── load_nif.erl │ ├── other_deadlock.erl │ └── six.erl ├── output/ │ ├── Makefile │ ├── output-tests │ ├── ref/ │ │ └── graph.dot │ └── src/ │ ├── a_fun.erl │ ├── buggy.erl │ ├── deadlock.erl │ ├── fig3.1-ext.erl │ ├── harmless_exit.erl │ ├── many_scheds.erl │ ├── more_stacktrace.erl │ ├── race_info.erl │ ├── register.erl │ └── stacktrace.erl └── print_colors ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: https://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 # Tab indentation [Makefile] indent_style = tab ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Create a directory containing the following files and change into it: '...' 2. Run Concuerror in the following way: '...' **Expected behavior** A clear and concise description of what you expected to happen. **Environment (please complete the following information):** - OS: [e.g. Debian] - Concuerror Version: [retrieve by `concuerror --version`] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/other-issue.md ================================================ --- name: Other Issue about: An issue that is not a bug report or feature request --- Please think about the following before opening an Issue (click `Preview` above to read this more easily): * If you need help using Concuerror, visit the [website][website] for tutorials and general info. * If you have a question, read [the relevant section in the CONTRIBUTING guide][guide ask] first. * If you want to report a bug, read [the relevant section in the CONTRIBUTING guide][guide report a bug] first. * If you want to suggest a feature, read [the relevant section in the CONTRIBUTING guide][guide suggest feature] first. [guide ask]: /parapluu/Concuerror/blob/master/CONTRIBUTING.md#i-want-to-ask-a-question [guide report a bug]: /parapluu/Concuerror/blob/master/CONTRIBUTING.md#i-want-to-report-a-bug [guide suggest feature]: /parapluu/Concuerror/blob/master/CONTRIBUTING.md#i-want-to-suggest-a-feature [website]: http://parapluu.github.io/Concuerror ================================================ FILE: .github/codecov.yml ================================================ codecov: notify: after_n_builds: 4 comment: after_n_builds: 4 coverage: status: project: default: target: 94% patch: default: target: 90% fixes: - "_build/default/lib/concuerror/::" ================================================ FILE: .github/pull_request_template.md ================================================ ## Summary Describe what will be improved after merging this Pull Request. ## Checklist * [ ] Has tests (or doesn't need them) * [ ] Updates CHANGELOG (or too minor) * [ ] References related Issues ================================================ FILE: .github/scripts/covertool_combine ================================================ #!/usr/bin/env bash set -euo pipefail EBIN=$(rebar3 path --ebin --app concuerror) cd cover export COVER_COMBINE=all.coverdata ./cover-report data || true covertool/covertool -cover $COVER_COMBINE -ebin $EBIN -appname concuerror cd .. ================================================ FILE: .github/scripts/covertool_setup ================================================ #!/usr/bin/env bash set -euo pipefail make cover/data cd cover git clone https://github.com/covertool/covertool.git --branch 2.0.3 cd covertool make compile ================================================ FILE: .github/workflows/tests.yml ================================================ name: Tests on: push: branches: master pull_request: branches: master paths-ignore: - 'docs/**' schedule: # Run at 00:00 UTC on the first day of each month - cron: '00 00 1 * *' env: THREADS: 2 jobs: basic: runs-on: ubuntu-latest strategy: matrix: target: - lint - dialyzer container: erlang:23 name: ${{ matrix.target }} steps: - uses: actions/checkout@v4 - uses: actions/cache@v4 with: path: '~/.cache' key: ${{ runner.os }}-rebar-${{ matrix.target }}-${{ hashFiles(format('{0}{1}', github.workspace, '/rebar.config')) }} restore-keys: | ${{ runner.os }}-rebar-${{ matrix.target }}- ${{ runner.os }}-rebar- - run: make ${{ matrix.target }} doc: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true working-directory: doc - uses: erlef/setup-beam@v1 with: otp-version: '24' rebar3-version: 3.23.0 - run: make -C doc test tests-latest: runs-on: ubuntu-latest strategy: matrix: otp: # Two latest minor versions of the most recent OTP release, # if supported. # When a new major OTP release is enabled, also update the badge in # README.md and the argument given to generate_version_hrl in # rebar.config - '23' target: - 'tests-unit' - 'tests-1' - 'tests-2' - 'tests-real' container: erlang:${{ matrix.otp }} name: OTP ${{ matrix.otp }} - ${{ matrix.target }} steps: - uses: actions/checkout@v4 - if: matrix.target != 'tests-unit' run: .github/scripts/covertool_setup - run: make ${{ matrix.target }} env: CONCUERROR: ${{ github.workspace }}/priv/concuerror CONCUERROR_COVER: ${{ github.workspace}}/cover/data - if: matrix.target == 'tests-unit' run: | rebar3 covertool generate cp _build/test/covertool/concuerror.covertool.xml coverage.xml - if: matrix.target != 'tests-unit' run: | .github/scripts/covertool_combine cp cover/coverage.xml coverage.xml - uses: codecov/codecov-action@v3 with: file: coverage.xml flags: ${{ matrix.target }} tests-older: runs-on: ubuntu-latest strategy: matrix: # Last minor version of older OTP releases otp: - '22' - '21' - '20' target: - 'tests-1' - 'tests-2' - 'tests-real' container: erlang:${{ matrix.otp }} name: OTP ${{ matrix.otp }} - ${{ matrix.target }} steps: - uses: actions/checkout@v4 - run: make ${{ matrix.target }} ================================================ FILE: .github/workflows/website.yml ================================================ name: Website on: push: branches: master paths: 'docs/**' pull_request: branches: master paths: 'docs/**' schedule: # Run at 00:00 UTC on the first day of each month - cron: '00 00 1 * *' jobs: test: strategy: matrix: target: - test - test-external name: ${{ matrix.target }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.2 bundler-cache: true working-directory: docs - run: make -C docs ${{ matrix.target }} ================================================ FILE: .gitignore ================================================ # NOTE! Please use 'git ls-files -i --exclude-standard' # command after changing this file, to see if there are # any tracked files which get ignored after the change. _build *.beam concuerror_report.txt erl_crash.dump thediff rebar3 ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased](https://github.com/parapluu/Concuerror/tree/master) ### Added ### Removed ### Changed - moved from Travis to Github Actions ### Fixed ## [0.21.0](https://github.com/parapluu/Concuerror/releases/tag/0.21.0) - 2020-10-31 ### Added - bash_completion (#308) - log_all option (#320) - support for OTP 23.0 (#311) ### Removed - support for OTP 16, OTP 17, OTP 18, and OTP 19 ### Changed - handling of demonitors (#281) - handling of process_info(..., messages) (#283) ### Fixed - fixed stacktrace information (#276) ## [0.20](https://github.com/parapluu/Concuerror/releases/tag/0.20) - 2018-07-15 ### Added - edoc documentation (#274) - support for OTP 21.0 (#264) - style checking by Elvis (#266) - base infrastructure for EUnit tests (and CT eventually) (#263) ### Changed - use rebar3 for building (#255) ## [0.19](https://github.com/parapluu/Concuerror/releases/tag/0.19) - 2018-05-21 ### Added - Total state space size and time to completion estimation - `--first_process_errors_only` option - Parts of [aronisstav/erlang-concurrency-litmus-tests](https://github.com/aronisstav/erlang-concurrency-litmus-tests) as a testsuite - [Codecov](https://codecov.io/github/parapluu/Concuerror) code coverage tracking - [contributor's guide](./CONTRIBUTING.md) - [Github Pull Request and Issue templates](./.github/) ### Removed - untested code for 'hijacking' processes (e.g. application_controller (#2)) ### Changed - progress bar format - symbolic PIDs are now shown as "" - report shows mailbox contents when a deadlock is detected - significantly optimized DPOR implementations - moved concuerror executable to /bin directory ### Fixed - handling of stacktraces - exclude instrumentation time from timeouts ## [0.18](https://github.com/parapluu/Concuerror/releases/tag/0.18) - 2018-02-20 ### Added - `--observers` option as a synonym of `--use_receive_patterns` - Support for erlang:hibernate/3 - Support for more safe built-in operations - A Changelog ### Changed - Completely reworked the implementation of `--use_receive_patterns` - `--use_receive_patterns` default is now `true` ### Fixed - Handling of exit_signals sent to self() (#5) ## [0.17](https://github.com/parapluu/Concuerror/releases/tag/0.17) - 2017-10-17 ## [0.16](https://github.com/parapluu/Concuerror/releases/tag/0.16) - 2016-10-25 ## [0.15](https://github.com/parapluu/Concuerror/releases/tag/0.15) - 2016-08-29 ## [0.14](https://github.com/parapluu/Concuerror/releases/tag/0.14) - 2015-06-10 ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute to Concuerror First of all, thank you for taking time to contribute to Concuerror! If you don't know where to start, here are some ["help wanted"][help wanted] issues. ## I want to ask a question. Use the [mailing list][mailing list] and take a look at the [FAQ][faq] if you haven't already! ## I want to report a bug. * Ensure that you can reproduce the bug on the [latest version of the `master` branch][latest]. * Take a moment to check whether the bug has already been reported by searching Github's [Issues][Issues]. If you find a relevant one, leave a comment there. * If you cannot find an existing relevant Issue, [open a new bug Issue][open a new bug Issue]. ## I want to write a patch that fixes a bug. * Find/write at least one small test that fails without the patch. * Fork Concuerror's repository and push a new branch for your patch, using a suitably descriptive name. * Make sure to also commit the tests that need the patch. You can find out how test suites are organized in the README files of the [test][test], [tests][tests] and [tests-real][tests-real] directories. * Make a [Pull Request][pull request]. ## I want to suggest a feature. * **Take a moment to check whether a similar feature has already been requested** by searching Github's [Issues][Issues]. If you find a relevant Issue, leave a comment there. * Apart from the main repository, take a look at the Issues page of the developer's own fork: [@aronisstav Issues][aronisstav Issues] * If you cannot find an existing relevant Issue, [open a new feature Issue][open a new feature Issue]. ## Code of Conduct This project and everyone participating in it is governed by the [Contributor Covenant Code of Conduct][Contributor Covenant Code of Conduct]. By participating, you are expected to uphold this code. Please report unacceptable behavior to info@concuerror.com. Thanks! :heart: [@aronisstav](https://github.com/aronisstav) [aronisstav Issues]: https://github.com/aronisstav/Concuerror/issues [Contributor Covenant Code of Conduct]: ./CODE_OF_CONDUCT.md [faq]: http://parapluu.github.io/Concuerror/faq [help wanted]: https://github.com/parapluu/Concuerror/labels/help%20wanted [Issues]: https://github.com/parapluu/Concuerror/issues [latest]: https://github.com/parapluu/Concuerror/ [mailing list]: http://parapluu.github.io/Concuerror/contact [open a new bug Issue]: https://github.com/parapluu/Concuerror/issues/new?template=issue_template_bug.md&labels=bug [open a new feature Issue]: https://github.com/parapluu/Concuerror/issues/new?template=issue_template_feature.md&labels=feature [pull request]: https://github.com/parapluu/Concuerror/compare [test]: ./test [tests]: ./tests [tests-real]: ./tests-real ================================================ FILE: LICENSE ================================================ Copyright (c) 2014-2018, Stavros Aronis () and Kostis Sagonas (). All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Makefile ================================================ ###----------------------------------------------------------------------------- ### Application info ###----------------------------------------------------------------------------- NAME := concuerror REBAR=$(shell which rebar3 || echo "./rebar3") .PHONY: default default bin/$(NAME): $(REBAR) $(REBAR) escriptize MAKEFLAGS += --no-builtin-rules .SUFFIXES: ###----------------------------------------------------------------------------- ### Rebar ###----------------------------------------------------------------------------- REBAR_URL="https://s3.amazonaws.com/rebar3/rebar3" ./$(REBAR): curl -o $@ $(REBAR_URL) && chmod +x $@ ###----------------------------------------------------------------------------- ### Compile ###----------------------------------------------------------------------------- .PHONY: dev native pedantic dev native pedantic: $(REBAR) $(REBAR) as $@ escriptize ###----------------------------------------------------------------------------- ### Edoc ###----------------------------------------------------------------------------- .PHONY: edoc edoc: $(REBAR) $(REBAR) edoc ###----------------------------------------------------------------------------- ### Lint ###----------------------------------------------------------------------------- .PHONY: lint lint: $(REBAR) $(REBAR) as lint lint ###----------------------------------------------------------------------------- ### Dialyzer ###----------------------------------------------------------------------------- .PHONY: dialyzer dialyzer: $(REBAR) $(REBAR) dialyzer ###----------------------------------------------------------------------------- ### Test ###----------------------------------------------------------------------------- CONCUERROR?=$(abspath bin/$(NAME)) .PHONY: tests tests: tests-1 tests-2 .PHONY: tests-1 tests-1: bin/$(NAME) @$(RM) tests/thediff @(cd tests; ./runtests.py suites/ba*/src/*) .PHONY: tests-2 tests-2: bin/$(NAME) @$(RM) tests/thediff @(cd tests; ./runtests.py suites/b[^a]*/src/* suites/[^b]*/src/*) ## -j 1: ensure that the outputs of different suites are not interleaved .PHONY: tests-real tests-real: bin/$(NAME) @$(RM) $@/thediff $(MAKE) -j 1 -C $@ \ TOP_DIR=$(abspath .) \ CONCUERROR=$(CONCUERROR) \ DIFFER=$(abspath tests/differ) \ DIFFPRINTER=$(abspath $@/thediff) .PHONY: tests-unit tests-unit: bin/$(NAME) $(REBAR) eunit ###----------------------------------------------------------------------------- ### Cover ###----------------------------------------------------------------------------- .PHONY: cover cover: cover/data bin/$(NAME) $(RM) $) and Kostis Sagonas (). All rights reserved Copyright (c) 2011-2013, Alkis Gotovos (), Maria Christakis () and Kostis Sagonas (). All rights reserved. Concuerror is distributed under the Simplified BSD License. Details can be found in the [LICENSE][license] file. [bash_completion]: ./resources/bash_completion/concuerror [changelog]: ./CHANGELOG.md [codecov]: https://codecov.io/gh/parapluu/Concuerror [hex]: https://hexdocs.pm/concuerror/ [license]: ./LICENSE [release]: ./resources/how-to-release.md [tests]: https://github.com/parapluu/Concuerror/actions/workflows/tests.yml [website]: http://parapluu.github.io/Concuerror [codecov badge]: https://codecov.io/gh/parapluu/Concuerror/branch/master/graph/badge.svg [erlang versions badge]: https://img.shields.io/badge/erlang-20.3%20to%2023.3-blue.svg [hex badge]: https://img.shields.io/badge/hex-docs-green.svg [tests badge]: https://github.com/parapluu/Concuerror/actions/workflows/tests.yml/badge.svg ================================================ FILE: bin/.gitignore ================================================ concuerror ================================================ FILE: cover/.gitignore ================================================ *.coverdata *.COVER.html covertool data ================================================ FILE: cover/cover-report ================================================ #!/usr/bin/env escript %%! +S1 -nostick -noshell -pa ../_build/default/lib/concuerror/ebin main([CoverDir]) -> {ok, Files} = file:list_dir(CoverDir), Full = [filename:join([CoverDir,F]) || F <- Files], Foreach = fun(F) -> cover:import(F) end, lists:foreach(Foreach, Full), case is_run_to_combine() of false -> Foreach2 = fun(M) -> cover:analyse_to_file(M, [html]) end, lists:foreach(Foreach2, cover:imported_modules()); {true, OutputFile} -> cover:export(OutputFile) end, {result, Result, _} = cover:analyse(coverage, module), print(Result). print(List) -> %% THRESHOLDS ThresholdSingle = 0.85, Threshold = 0.94, %% HEADER to_stderr("~-30s | ~5s | ~6s | ~5s~n", ["Module", "Lines", "NotCov", "Cov %"]), print_separator(), AddPointerSuffix = fun(true) -> " <--"; (false) -> "" end, Fold = fun({Mod, {Cov, NotCov}}, {Fail, TotSum, CovSum}) -> Tot = Cov + NotCov, Pct = Cov / Tot, Fails = Pct < ThresholdSingle, %% PER MODULE ENTRY to_stderr( "~-30s | ~5w | ~6w | ~5.1f~s~n", [Mod, Tot, NotCov, Pct * 100, AddPointerSuffix(Fails)]), {Fail orelse Fails, TotSum + Tot, CovSum + Cov} end, {Fail, TotSum, CovSum} = lists:foldl(Fold, {false, 0,0}, List), Pct = CovSum / TotSum, TotalFail = Pct < Threshold, %% FOOTER print_separator(), to_stderr("~-30s ~5w ~6w ~5.1f~s~n", ["Total", TotSum, CovSum, Pct * 100, AddPointerSuffix(TotalFail)]), case Fail of false -> ok; true -> to_stderr( "~nFail: Code coverage for some module(s) is below ~.1f% threshold!~n", [ThresholdSingle * 100]) end, case TotalFail of false -> ok; true -> to_stderr( "~nFail: Total code coverage is below ~.1f% threshold!~n", [Threshold * 100]) end, case Fail orelse TotalFail of true -> halt(1); false -> halt(0) end. print_separator() -> to_stderr("~s~n", [[$- || _ <- lists:seq(1, 55)]]). to_stderr(Format, Args) -> io:format(standard_error, Format, Args). is_run_to_combine() -> case os:getenv("COVER_COMBINE") of false -> false; OutputFile -> {true, OutputFile} end. ================================================ FILE: doc/.gitignore ================================================ *_option.edoc *.css *.html edoc-info erlang.png Gemfile.lock ================================================ FILE: doc/Gemfile ================================================ source 'https://rubygems.org' gem "html-proofer" ================================================ FILE: doc/Makefile ================================================ .PHONY: default default: build Gemfile.lock: Gemfile bundle install touch $@ .PHONY: build build: make -C .. edoc .PHONY: test test: build Gemfile.lock bundle exec htmlproofer --disable-external=true --ignore-files "/vendor/" --log_level=debug --allow_missing_href=true --enforce_https=false . .PHONY: clean clean: $(RM) *_option.edoc *.html *.css erlang.png edoc-info Gemfile.lock ================================================ FILE: doc/overview.edoc ================================================ @title Concuerror's API specification @doc == Welcome! == This documentation contains Concuerror's API specification. It is complementary to the user guides and tutorials available at [https://parapluu.github.io/Concuerror]. Please start there if you are looking for general information about what the tool can do and how it works. == Table of Contents ==
  • {@section Invoking Concuerror}
  • {@section Help}
  • {@section Options}
  • {@section Standard Error Printout}
  • {@section Report File}
  • {@section Graph Output}
== Invoking Concuerror == The recommended method to invoke Concuerror is via the `bin/concuerror' escript (available after running `make'). See the documentation of the {@link concuerror} module for more information about the entry points and return values. == Options == Concuerror is configured using a number of options. See the documentation of the {@link concuerror_options} module. == Help == The {@link concuerror_options:help_option/0. `help'} option can be used to access help. == Standard Error Printout == By default, Concuerror prints diagnostic messages in the standard error stream. See the documentation of the {@link concuerror_options:verbosity_option/0. `verbosity'} option. == Report File == By default, Concuerror prints analysis findings in a report file. See the documentation of the {@link concuerror_options:output_option/0. `output'} option. == Graph Output == Optionally, Concuerror can produce a graphical representation of the exploration tree, by combining the explored interleavings. See the documentation of the {@link concuerror_options:graph_option/0. `graph'} option. ================================================ FILE: docs/.bundle/config ================================================ --- BUNDLE_PATH: "vendor/bundle" ================================================ FILE: docs/.gitignore ================================================ /_site .jekyll-cache vendor ================================================ FILE: docs/404.md ================================================ --- layout: search title: "Page Not Found" description: "Page not found" --- ## 404 Error Sorry, but the page you were trying to view does not exist. Search the site: ================================================ FILE: docs/CNAME ================================================ concuerror.com ================================================ FILE: docs/Gemfile ================================================ source 'https://rubygems.org' gem "github-pages", group: :jekyll_plugins gem "html-proofer" gem "webrick" ================================================ FILE: docs/Makefile ================================================ .PHONY: default default: build JEKYLL = bundle exec jekyll PROOFER = bundle exec htmlproofer ./_site --checks "Images,Links,Script,Favicon,OpenGraph" .PHONY: jekyll jekyll: Gemfile bundle install .PHONY: build build: jekyll $(JEKYLL) build .PHONY: serve serve: jekyll $(JEKYLL) serve --config _config.yml,_config_local.yml .PHONY: test test: build $(PROOFER) --disable_external .PHONY: test-external test-external: build $(PROOFER) --ignore-status-codes "302,403" ================================================ FILE: docs/README.md ================================================ # Concuerror's Website Based on the [**So Simple Theme**](http://mmistakes.github.io) ## How to build `bundle install` `jekyll serve` ================================================ FILE: docs/_config.yml ================================================ # Base config title: "Concuerror" description: "Concuerror is a model checking tool for debugging, testing and verifying concurrent Erlang programs." logo: "/images/site-logo.png" url: "https://concuerror.com" # Owner/author information author: stavros # Theme remote_theme: "aronisstav/so-simple-theme@b968090" skin: "/assets/css/skins/default.css" locale: en-US date_format: "%B %-d, %Y" # Front Matter Defaults defaults: # Posts defaults - scope: path: "_posts" type: posts values: layout: post share: true read_time: true words_per_minute: 50 # Search search_full_content: true # Taxonomy pages category_archive_path: / # Fonts google_fonts: - name: "Source Sans Pro" weights: "400,400i,700,700i" - name: "Lora" weights: "400,400i,700,700i" # Footer Links footer_links: - title: Home url: / icon: fas fa-home - title: Github url: https://github.com/parapluu/Concuerror icon: fab fa-github - title: Github Stars url: https://github.com/parapluu/Concuerror/stargazers icon: fas fa-star - title: Feed url: feed.xml icon: fas fa-rss-square # Liquid liquid: error_mode: strict # https://github.com/mojombo/jekyll/wiki/Permalinks permalink: /:categories/:title:output_ext # Markdown markdown: kramdown kramdown: auto_ids: true footnote_nr: 1 entity_output: as_char syntax_highlighter: rouge toc_levels: 1..6 # Plugins plugins: - jekyll-feed - jekyll-gist - jekyll-redirect-from - jekyll-seo-tag # Feed feed: path: "feed.xml" # Redirects redirect_from: json: false # Include/exclude include: [".htaccess"] exclude: ["CNAME", "Gemfile", "Gemfile.lock", "Makefile", "README.md", "vendor"] ================================================ FILE: docs/_config_local.yml ================================================ url: "" ================================================ FILE: docs/_data/authors.yml ================================================ stavros: name: Stavros Aronis picture: https://www.gravatar.com/avatar/5730e85681def67cb10151437519f39d?s=200 email: aronisstav@gmail.com twitter: "@Vahnatai" links: - title: Twitter url: https://twitter.com/Vahnatai icon: fab fa-twitter-square - title: Github url: https://github.com/aronisstav icon: fab fa-github ================================================ FILE: docs/_data/navigation.yml ================================================ - title: Home url: / - title: News url: /news - title: Download url: /download - title: FAQ url: /faq - title: Tutorials url: /tutorials - title: API (Hexdocs) url: https://hexdocs.pm/concuerror - title: Publications url: /publications - title: Contact url: /contact icon: fas fa-envelope - title: Search url: /search - title: Github url: https://github.com/parapluu/Concuerror ================================================ FILE: docs/_data/text.yml ================================================ # Theme text and labels # English (default) # ----------------- en: &DEFAULT_EN skip_links: "Skip links" skip_primary_nav: "Skip to primary navigation" skip_content: "Skip to content" skip_footer: "Skip to footer" menu_label: "Menu" by: "by" min_read: "min read" read_more: "Read more…" categories: "Categories" tags: "Tags" pages_categorized: "Pages filed under" pages_tagged: "Pages tagged" table_of_contents: "Table of Contents" results_found: "Result(s) found" share: "Share" tweet: "Tweet" next: "Next" previous: "Previous" newer: "Newer" older: "Older" back_to_top: "Back to Top" search_placeholder_text: "Enter your search term..." powered_by: "Powered by" en-US: <<: *DEFAULT_EN en-CA: <<: *DEFAULT_EN en-GB: <<: *DEFAULT_EN en-AU: <<: *DEFAULT_EN # German # ----------------- de: &DEFAULT_DE skip_links: "Links überspringen" skip_primary_nav: "Zu Hauptmenü springen" skip_content: "Zu Inhalt springen" skip_footer: "Zu Fußzeile springen" menu_label: "Menü" by: "von" min_read: "min read" read_more: "Mehr lesen…" categories: "Kategorien" tags: "Tags" pages_categorized: "Seiten kategorisiert als" pages_tagged: "Seiten, getaggt als" table_of_contents: "Inhaltsverzeichnis" results_found: "Ergebnis(se) gefunden" share: "Teilen" tweet: "Tweet" next: "Vor" previous: "Zurück" newer: "Neuer" older: "Älter" back_to_top: "Zurück nach Oben" search_placeholder_text: "Suchtext eingeben..." powered_by: "Unterstützt von" de-DE: <<: *DEFAULT_DE de-AU: <<: *DEFAULT_DE de-CH: <<: *DEFAULT_DE # Spanish # ------- es: &DEFAULT_ES skip_links: "Saltar enlaces" skip_primary_nav: "Saltar a la navegación primaria" skip_content: "Saltar al contenido" skip_footer: "Saltar al pie de página" menu_label: "Menú" by: "por" min_read: "minuto(s) de lectura" read_more: "Leer más…" categories: "Categorías" tags: "Etiquetas" pages_categorized: "Páginas archivadas bajo" pages_tagged: "Páginas etiquetadas" table_of_contents: "Tabla de contenidos" results_found: "Resultado(s) encontrado(s)" share: "Compartir" tweet: "Tweet" next: "Siguiente" previous: "Anterior" newer: "Nuevo" older: "Antiguo" back_to_top: "Volver arriba" search_placeholder_text: "Ingrese su término de búsqueda ..." powered_by: "Energizado por" es-ES: <<: *DEFAULT_ES es-CL: <<: *DEFAULT_ES # French # ----------------- fr: &DEFAULT_FR skip_links: "Sauter les liens" skip_primary_nav: "Aller à la navigation principale" skip_content: "Aller au contenu" skip_footer: "Aller au pied de page" menu_label: "Menu" by: "par" min_read: "min de lecture" read_more: "En savoir plus…" categories: "Catégories" tags: "Tags" pages_categorized: "Pages catégorisées dans" pages_tagged: "Pages étiquettées" table_of_contents: "Table des matières" results_found: "Résultat(s) trouvé(s)" share: "Partager" tweet: "Tweet" next: "Suivant" previous: "Précédent" newer: "Plus récent" older: "Plus ancier" back_to_top: "Retour en haut" search_placeholder_text: "Entrer le terme à rechercher..." powered_by: "Propulsé par" fr-FR: <<: *DEFAULT_FR fr-BE: <<: *DEFAULT_FR fr-CA: <<: *DEFAULT_FR it: &DEFAULT_IT skip_links: "Salta links" skip_primary_nav: "Salta alla navigazione primaria" skip_content: "Vai al contenuto" skip_footer: "Vai al footer" menu_label: "Menu" by: "di" min_read: "min. per leggere" read_more: "Leggi di più…" categories: "Categorie" tags: "Tags" pages_categorized: "Pagine categorizzate come" pages_tagged: "Pagine con tag" table_of_contents: "Indice dei Contenuti" results_found: "Risultati della ricerca" share: "Condividi" tweet: "Tweet" next: "Avanti" previous: "Indietro" newer: "Nuovi" older: "Vecchi" back_to_top: "Torna in cima" search_placeholder_text: "Inserisci una parola da cercare..." powered_by: "Generato da" it-IT: <<: *DEFAULT_IT # Turkish # ----------------- tr: &DEFAULT_TR skip_links: "Linklere git" skip_primary_nav: "Birincil navigasyona git" skip_content: "İçeriğe git" skip_footer: "Sayfa altbilgisine git" menu_label: "Menü" by: "by" min_read: "dakikalık okuma" read_more: "Daha fazla…" categories: "Kategoriler" tags: "Etiketler" pages_categorized: "Pages filed under" pages_tagged: "Pages tagged" table_of_contents: "Başlıklar" results_found: "Sonuç bulundu" share: "Paylaş" tweet: "Tweet" next: "Sonraki" previous: "Önceki" newer: "Daha Yeni" older: "Daha Eski" back_to_top: "Başa Dön" search_placeholder_text: "Arama teriminizi girin..." powered_by: "Powered by" tr-TR: <<: *DEFAULT_TR ================================================ FILE: docs/_includes/footer-custom.html ================================================
{% if page.updated %}Page last modified on {{ page.updated | date: site.date_format }} | {% endif %} {{ site.data.text[site.locale].back_to_top | default: 'Back to Top' }} ↑

================================================ FILE: docs/_includes/head-custom.html ================================================ ================================================ FILE: docs/_includes/masthead.html ================================================
{% if site.logo %} {% endif %} {% assign page_image = page.image.path | default: page.image %}
================================================ FILE: docs/_includes/yt.html ================================================ ================================================ FILE: docs/_posts/2013-09-03-site.md ================================================ --- layout: post title: "Concuerror's website" category: news redirect_from: /site.html --- Concuerror just got a website! ================================================ FILE: docs/_posts/2013-09-23-euc-2013.md ================================================ --- layout: post title: "Tutorial at EUC 2013: Video and Slides" author: Kostis Sagonas category: tutorials --- Kostis Sagonas gave a presentation about Concuerror at the Erlang User Conference 2013. {% assign yt-link = "FpkjKN9wTKg" %} {% include yt.html %} [Get the slides](https://www.erlang-factory.com/upload/presentations/858/euc_pres.pdf). You can also find the video and slides [here](https://www.erlang-factory.com/conference/ErlangUserConference2013/speakers/KostisSagonas). ================================================ FILE: docs/_posts/2014-06-02-poolboy-example.md ================================================ --- layout: post title: "Concuerror basics (Testing Poolboy, Part 1)" category: tutorials updated: 2018-03-21 --- In this tutorial we will explain some basic use of Concuerror to analyze a few tests written for the [Poolboy](https://github.com/devinus/poolboy) library. {:.no_toc} Index ----- 1. This text will be replaced by the ToC, excluding the previous header (WOW!) {:toc} Setting up Concuerror, Poolboy and our first test ------------------------------------------------- ### Setting up Concuerror The first step is to download and make Concuerror as described in the [Download](/download) page. For the rest of the tutorial we will assume that the `concuerror` executable is in our path. For example, to get help for all of Concuerror's options we just run: {% highlight bash %} $ concuerror -h Usage: ./concuerror [-m ] [-t []] [-o []] [-h] [--version] [--pa ] [--pz ] [-f ] [-v ] [-q] [--print_depth []] [...] {% endhighlight %} ### Setting up Poolboy We will be using version [`1.2.2`](https://github.com/devinus/poolboy/releases/tag/1.2.2) of Poolboy. {% highlight bash %} $ git clone https://github.com/devinus/poolboy.git --branch 1.2.2 [...] $ cd poolboy poolboy $ make ==> poolboy (compile) Compiled src/poolboy_worker.erl Compiled src/poolboy_sup.erl Compiled src/poolboy.erl {% endhighlight %} We will be using the sample worker used in Poolboy's own tests, so we should `make` the tests as well: {% highlight bash %} poolboy $ make test [...] {% endhighlight %} We don't care about the actual result of the tests, but we now have a `.eunit` directory with `poolboy_test_worker.beam`. #### No special compilation needed Notice that we don't need to compile anything in a special way. Concuerror will intercept calls to any module that is in Erlang's code path and instrument the code before our test reaches it. ### Our first test Concuerror explores all the interleavings of an arbitrary execution scenario of a program with a single entry point. We will be extracting tests from [Poolboy's own test suite](https://github.com/devinus/poolboy/blob/1.2.2/test/poolboy_tests.erl). Let's begin with an adapted version of the start/stop test, which we save as [`poolboy_tests_1.erl`](https://gist.github.com/aronisstav/b67df16361cd9a2fa87e#file-poolboy_tests_1-erl): {% gist aronisstav/b67df16361cd9a2fa87e %} #### Using `.erl` files We don't have to compile our test, as Concuerror can also analyze `.erl` files, which are instrumented and compiled before the test starts. We are now ready to... Start testing! -------------- We now have our application code compiled and ready to go, and have written a small test. Next, we have to tell Concuerror to compile and load our test file (using option `-f`) and then start testing from module `poolboy_tests_1`, calling function `pool_startup`, which must have zero arity: {% highlight bash %} poolboy $ concuerror -f poolboy_tests_1.erl -m poolboy_tests_1 -t pool_startup {% endhighlight %} Several lines should be printed on our screen. Let's focus on a few of them: ### The output file {% highlight text %} Concuerror started at 02 Jun 2014 09:00:00 Writing results in concuerror_report.txt {% endhighlight %} Concuerror mainly produces a textual log, saved by default as `concuerror_report.txt`. You can specify a different filename with the `-o` option. ### Info messages {% highlight text %} Info: Instrumented poolboy_tests_1 {% endhighlight %} Log messages tagged as *Info* are standard, normal operation messages. Here, Concuerror reports that it compiled and instrumented our test file and started to run the test. {% highlight text %} Info: Instrumented io_lib Info: Instrumented poolboy Info: Instrumented proplists Info: Instrumented gen_server [...] {% endhighlight %} Concuerror can detect which modules are being used by the test, and instruments them automatically. You can see a few of them listed above. `io_lib` is also included, for reasons that are not important to explain right now. ### Warning messages {% highlight text %} Warning: Concuerror does not fully support erlang:get_stacktrace/0 ... {% endhighlight %} Log messages tagged as *Warnings* are non-critical, notifying about weak support for some feature or the use of an option that alters the output ### Tip messages {% highlight text %} Tip: An abnormal exit signal was sent to a process... {% endhighlight %} Log messages tagged as *Tips* are also non-critical, notifying of a suggested refactoring or option that can be used to make testing more efficient. ### The interesting bits! By default, Concuerror will stop exploration on the first error it encounters. In this particular case, it seems that in the very first interleaving we managed to trigger some behavior that the tool considers problematic. If we take a look at the output file `concuerror_report.txt`, we will see something like the following: {% highlight text %} {% raw %} * At step 49 process P.1 exited abnormally Reason: {{badmatch, {error, {'EXIT', {undef, [{poolboy_test_worker,start_link, [[{name,{local,poolboy_test}}, {worker_module,poolboy_test_worker}, {size,1}, {max_overflow,...}]], []}, {supervisor,do_start_child_i,3, [{file,"supervisor.erl"},{line,330}]}, {supervisor,handle_call,3,[{file,[...]},{line,...}]}, {gen_server,handle_msg,5,[{file,...},{...}]}, {proc_lib,init_p_do_apply,3,[{...}|...]}, {concuerror_callback,process_top_loop,1,[...]}]}}}}, [{poolboy,new_worker,1,[{file,"src/poolboy.erl"},{line,242}]}, [...] {% endraw %} {% endhighlight %} ### Using `-pa` to add directories in Erlang's code path Whoops! We forgot to add the `poolboy_test_worker` module to Erlang's code path. Concuerror uses the `--pa` option for this (notice the *two* dashes). Running it again... {% highlight bash %} poolboy $ concuerror -f poolboy_tests_1.erl -m poolboy_tests_1 -t pool_startup \ --pa .eunit {% endhighlight %} ... yields: {% highlight text %} [...] Tip: A process crashed with reason '{timeout, ...}'. This may happen when a call to a gen_server (or similar) does not receive a reply within some standard timeout. Use the '--after_timeout' option to treat after clauses that exceed some threshold as 'impossible'. Tip: An abnormal exit signal was sent to a process. This is probably the worst thing that can happen race-wise, as any other side-effecting operation races with the arrival of the signal. If the test produces too many interleavings consider refactoring your code. Tip: A process crashed with reason 'shutdown'. This may happen when a supervisor is terminating its children. You can use '--treat_as_normal shutdown' if this is expected behavior. Error: Stop testing on first error. (Check '-h keep_going'). [...] {% endhighlight %} Three tips and the same error. This time however, `concuerror_report.txt` contains something like: {% highlight text %} {% raw %} Erroneous interleaving 1: * At step 52 process P exited abnormally Reason: {timeout,{gen_server,call,[P.1,stop]}} Stacktrace: [{gen_server,call,2,[{file,"gen_server.erl"},{line,182}]}, {poolboy_tests_1,pool_startup,0, [{file,"poolboy_tests_1.erl"},{line,8}]}] * At step 76 process P.1.1.1 exited abnormally Reason: shutdown Stacktrace: [] * At step 85 process P.1.1 exited abnormally Reason: shutdown Stacktrace: [{proc_lib,exit_p,2,[{file,"proc_lib.erl"},{line,260}]}] {% endraw %} {% endhighlight %} This behavior seems to be expected within the context of the test. Find out why Concuerror reports it as problematic in the next part of this tutorial. {:.no_toc} # [
Continue to the next part!
]({% post_url 2014-06-03-poolboy-example-errors %}) ================================================ FILE: docs/_posts/2014-06-03-poolboy-example-errors.md ================================================ --- layout: post title: "Reading Concuerror's reports (Testing Poolboy, Part 2)" category: tutorials updated: 2018-03-21 --- We continue our tutorial on Concuerror, explaining the errors it can detect and the options that you can use to filter out those that are not important for your application. Read also the [first part]({% post_url 2014-06-02-poolboy-example %})! {:.no_toc} Index ----- 1. This text will be replaced by the ToC, excluding the previous header (WOW!) {:toc} Symbolic names -------------- Concuerror makes an effort to replace every Erlang PID that appears in its report with a symbolic name. The first process spawned is labelled *P* and every other process is named after the process that spawned it, with an integer denoting the order in which it was spawned i.e. *P*'s first "child" is named *P.1*, *P.1*'s third child is named *P.1.3* and so on. If you prefer to see the raw PIDs you can use `--symbolic_names false`. Concuerror is reusing the same processes, so the results should be consistent across different interleavings. Abnormal exits -------------- Every time a process exits abnormally, Concuerror will mark the interleaving as erroneous. This means that any exception that escapes to the top-level will trigger a warning. [In our example]({% post_url 2014-06-02-poolboy-example%}/#using--pa-to-add-directories-in-erlangs-code-path), processes *P*, *P.1.1* and *P.1.1.1* exited abnormally. {% highlight text %} {% raw %} Erroneous interleaving 1: * At step 52 process P exited abnormally Reason: {timeout,{gen_server,call,[P.1,stop]}} Stacktrace: [{gen_server,call,2,[{file,"gen_server.erl"},{line,182}]}, {poolboy_tests_1,pool_startup,0, [{file,"poolboy_tests_1.erl"},{line,8}]}] * At step 76 process P.1.1.1 exited abnormally Reason: shutdown Stacktrace: [] * At step 85 process P.1.1 exited abnormally Reason: shutdown Stacktrace: [{proc_lib,exit_p,2,[{file,"proc_lib.erl"},{line,260}]}] {% endraw %} {% endhighlight %} ### Ignoring "after" timeouts If we take a look at the trace we can see that *P* triggered the standard timeout clause of a `gen` call: {% highlight text %} 50: P: receive timeout expired after 5000 ms in gen.erl line 213 [...] 52: P: exits abnormally ({timeout,{gen_server,call,[P.1,stop]}}) {% endhighlight %} As explained [here](/faq/#how-does-concuerror-handle-timeouts-and-other-time-related-functions), Concuerror by default assumes that any *receive* statement may trigger the *after* clause, unless it is impossible for a matching message not to have already arrived. However, let's assume that we don't care about such timeouts. We can use the `--after_timeout 1000` to treat any timeout *higher than 1000ms* as *infinity*. Notice that the one of the tips we got earlier suggests the same course of action: {% highlight text %} Tip: A process crashed with reason '{timeout, ...}'. This may happen when a call to a gen_server (or similar) does not receive a reply within some standard timeout. Use the '--after_timeout' option to treat after clauses that exceed some threshold as 'impossible'. {% endhighlight %} ### Treating abnormal exit reasons as normal The other two processes exited abnormally because they were terminated by events generated by the delivery of a `stop` message to a supervisor. The supervisor then sent `shutdown` exit signals to the relevant processes. Again, let's assume that this is acceptable behaviour in our context, using `--treat_as_normal shutdown` (also suggested by a tip). {% highlight text %} Tip: A process crashed with reason 'shutdown'. This may happen when a supervisor is terminating its children. You can use '--treat_as_normal shutdown' if this is expected behavior. {% endhighlight %} A report without problems ------------------------- We now run Concuerror again, using the new options: {% highlight bash %} poolboy $ concuerror -f poolboy_tests_1.erl -m poolboy_tests_1 -t pool_startup \ --pa .eunit --after_timeout 1000 --treat_as_normal shutdown {% endhighlight %} This time the output finishes in: {% highlight text %} [...] Warning: Some abnormal exit reasons were treated as normal ('--treat_as_normal'). Done! (Exit status: completed) Summary: 0 errors, ... interleavings explored {% endhighlight %} Concuerror finished the exploration, finding all the different states that the program can reach. None of these interleavings had any processes terminating abnormally with a reason different from `shutdown`, nor any other errors. ### Interleavings as a graph Concuerror can also produce a [DOT](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) file, including all the interleavings. This feature can be enabled with `--graph `. You can then use `dot` to convert the DOT file to an image. ### Racing events The report includes a section where each pair of events that are racing is reported, to justify each additional interleaving. Let's take a look at the output: {% highlight text %} [...] ################################################################################ Race Pairs: -------------------------------------------------------------------------------- You can disable race pair messages with '--show_races false' * Exit signal (normal) from P.1 reaches P.1.1 is in race with Message ({'DOWN',#Ref<0.0.0.508>,process,P.1.1.1,shutdown}) from P.1.1.1 reaches P.1.1 * Message ({'DOWN',#Ref<0.0.0.508>,process,P.1.1.1,shutdown}) from P.1.1.1 reaches P.1.1 is in race with Timer #Ref<0.0.0.522>: expires, delivering {timeout,#Ref<0.0.0.522>,kill} to P.1.1 * P.1: exits normally is in race with P: true = erlang:demonitor(#Ref<0.0.0.407>, [flush]) in gen.erl line 215 {% endhighlight %} We can see e.g. that the delivery of an exit signal from a linked process races with the delivery of an arbitrary message, (since `P.1.1` is trapping exits), a similar race appears between a message from a timer and finally a process exiting races with the cancellation of a monitor, as the monitor message will be sent in one case but not in the other. The same pair of events may be reported multiple times, but this should happen only if we are exploring the reverse interleaving of a different race. **If you see a pair of events that you believe should not be racing, feel free to [report the discrepancy!](https://github.com/parapluu/Concuerror/issues/new) Concuerror's race detection accuracy is the most sensitive component regarding the total number of interleavings explored!** "Deadlocked" processes --------------------- Since we called `poolboy:stop/1`, all processes were properly stopped. What would happen if we had let a process lingering? We already have such a test, `pool_startup_no_stop/0` (we need to use `poolboy:start/1` instead of `start_link`, as the normal exit signal from the initial process also causes a shutdown). {% gist aronisstav/b67df16361cd9a2fa87e %} Running this test, yields: {% highlight bash %} poolboy $ concuerror -f poolboy_tests_1.erl -m poolboy_tests_1 --pa .eunit \ -t pool_startup_no_stop Concuerror started at 03 Jun 2014 12:00:00 Writing results in concuerror_report.txt [...] Done! (Exit status: warning) Summary: 1 errors, 1/1 interleavings explored {% endhighlight %} The output file now contains the following description: {% highlight text %} Erroneous interleaving 1: * Blocked at a 'receive' ("deadlocked"; other processes have exited): P.1 in gen_server.erl line 360 P.1.1 in gen_server.erl line 360 P.1.1.1 in gen_server.erl line 360 {% endhighlight %} Concuerror reports any processes that have not exited, when no other processes are available to run. Such processes are blocked at a *receive* statement and are considered "deadlocked". These warnings can be turned off by `--ignore_error deadlock`. Concuerror will always reset the test to the initial state, so leaving some processes running may be a better alternative to suffering from all the races between exit signals that appear at termination. ================================================ FILE: docs/_posts/2014-08-11-euc-2014.md ================================================ --- layout: post title: "Talk at EUC 2014: Video and Slides" updated: 2015-06-17 category: news redirect_from: /euc-2014.html --- Stavros Aronis gave a presentation about Concuerror at the Erlang User Conference 2014.

Stavros Aronis - Concuerror: Into Real Code from Erlang Solutions on Vimeo.

[Get the slides](https://www.erlang-factory.com/static/upload/media/1402329241902242concuerrorintorealcodestavrosaroniseuc14.pdf). You can also find the video and slides [here](https://www.erlang-factory.com/euc2014/stavros-aronis). ================================================ FILE: docs/_posts/2015-06-10-euc-2015.md ================================================ --- layout: post title: "Tutorial at EUC 2015: Slides" category: tutorials updated: 2015-06-10 --- Stavros Aronis is presenting a tutorial on Concuerror at the Erlang User Conference 2015. You can find more info [here](https://www.erlang-factory.com/euc2015/stavros-aronis). The slides are [here](https://docs.google.com/presentation/d/1dRUQ1jC0NXRyM-6WJWo15n-3hSZ1p7F8fl3X4bdhis8/pub?start=false&loop=false&delayms=60000). ================================================ FILE: docs/_posts/2017-08-14-euc-2017-talk.md ================================================ --- layout: post title: "Talk at EUC 2017: Video and Slides" category: news redirect_from: /euc-2017-talk.html --- Kostis Sagonas presented a talk on Concuerror at the Erlang User Conference 2017. {% assign yt-link = "WWBDUpmCUsI" %} {% include yt.html %} [Get the slides](https://docs.google.com/presentation/d/11nidXG5JazNXsNSoqW98oRlx7-e64ghQ_E1p_WMLitg). You can find more info [here](https://www.erlang-factory.com/euc2017/kostis-sagonas). ================================================ FILE: docs/_posts/2017-08-14-euc-2017-tutorial.md ================================================ --- layout: post title: "Tutorial at EUC 2017: Slides" category: tutorials --- Stavros Aronis presented a tutorial on Concuerror at the Erlang User Conference 2017. You can find more info [here](https://www.erlang-factory.com/euc2017/stavros-aronis). The slides are [here](https://goo.gl/Mhulbn). ================================================ FILE: docs/_posts/2017-10-03-publications-update.md ================================================ --- layout: post title: "Recent Publications and Homepage" category: news redirect_from: /publications-update.html --- The [Publications](/publications) page was updated with some recent publications. The [Homepage](/) was extended with more info. ================================================ FILE: docs/_posts/2017-10-08-concuerror-eunit.md ================================================ --- layout: post title: "How to run Concuerror with EUnit" category: tutorials updated: 2018-03-09 --- Here are some guidelines on how to use Concuerror as part of an EUnit suite. First of all, the "order" in which you run the two tools is significant: e.g. invoking Concuerror with the `test/0` function generated by EUnit is a bad idea, for at least two reasons: 1. you probably don't want to test all of EUnit's boilerplate code systematically and 2. the default test function generated by EUnit runs all tests, one after another; as a result, systematic testing will have to explore a number of schedulings that is the **product** of every individual test's schedulings! You should use Concuerror on single tests instead. Suppose you have the following EUnit tests: {% highlight erlang %} -module(eunit_sample). -include_lib("eunit/include/eunit.hrl"). %%============================================================================== foo_test() -> ?assert(true). msg_test() -> P = self(), spawn(fun() -> P ! foo end), spawn(fun() -> P ! bar end), receive Msg -> ?assert(foo, Msg) end. reg_test() -> Fun = fun() -> receive message -> ok after 100 -> timeout end end, P = spawn(Fun), register(p, P), p ! message, ?assert(true). {% endhighlight %} For this suite, running EUnit yields: {% highlight bash %} $ erlc eunit_sample.erl $ erl -noinput -s eunit_sample test -s erlang halt All 3 tests passed. {% endhighlight %} It is fairly easy to see, however, that the two latter tests can fail in particular interleavings[^1]. [^1]: In fact, the second and third tests MAY fail once in a while, but most times they won't! Invoking Concuerror on `msg_test/0` easily finds the error: {% highlight bash %} $ concuerror -m eunit_sample -t msg_test Concuerror v0.17 (...) started at 08 Oct 2017 16:19:48 [...] Errors were found! (check concuerror_report.txt) Done at 08 Oct 2017 16:23:49 (Exit status: error) Summary: 1 errors, 2/2 interleavings explored {% endhighlight %} The output file contains the trace: {% highlight text %} {% raw %} Concuerror v0.17 (...) started at 08 Oct 2017 16:23:49 Options: [...] ################################################################################ Interleaving #2 -------------------------------------------------------------------------------- Errors found: * At step 6 process P exited abnormally Reason: {{assertEqual,[{module,eunit_sample}, {line,15}, {expression,"Msg"}, {expected,foo}, {value,bar}]}, [...]} Stacktrace: [...] -------------------------------------------------------------------------------- Event trace: 1: P: P.1 = erlang:spawn(erlang, apply, [#Fun,[]]) in erlang.erl line 2693 2: P: P.2 = erlang:spawn(erlang, apply, [#Fun,[]]) in erlang.erl line 2693 3: P.2: bar = P ! bar in eunit_sample.erl line 13 4: P.2: exits normally 5: P: receives message (bar) in eunit_sample.erl line 14 6: P: exits abnormally ({{assertEqual,[{module,eunit_sample},{line,15},..]}}) 7: P.1: foo = P ! foo in eunit_sample.erl line 12 8: P.1: exits normally ################################################################################ Exploration completed! ################################################################################ [...] Info: -------------------------------------------------------------------------------- * Automatically instrumented module io_lib * Automatically instrumented module gen_server * Automatically instrumented module eunit_sample * Automatically instrumented module erlang * You can see pairs of racing instructions (in the report and '--graph') with '--show_races true' ################################################################################ Done at 08 Oct 2017 16:23:49 (Exit status: error) Summary: 1 errors, 2/2 interleavings explored {% endraw %} {% endhighlight %} Notice that Concuerror instrumented only some very basic modules. In contrast, invoking Concuerror on the `test/0` function that is automatically generated by EUnit (and is automatically tested by Concuerror), yields: {% highlight text %} $ concuerror -m eunit_sample Concuerror v0.17 (...) started at 08 Oct 2017 16:28:38 Writing results in concuerror_report.txt * Info: Automatically instrumented module io_lib * Info: Automatically instrumented module gen_server * Info: Automatically instrumented module eunit_sample * Info: Automatically instrumented module eunit * Info: Automatically instrumented module proplists * Info: Automatically instrumented module eunit_tty * Info: Automatically instrumented module eunit_listener * Info: Automatically instrumented module erlang * Info: Automatically instrumented module eunit_serial * Info: Automatically instrumented module sets * Info: Automatically instrumented module lists * Info: Automatically instrumented module eunit_lib * Info: Automatically instrumented module gb_trees * Info: Automatically instrumented module dict * Info: Automatically instrumented module eunit_server * Warning: Your test seems to try to set up an EUnit server. This is a bad idea, for at least two reasons: 1) you probably don't want to test all of EUnit's boilerplate code systematically and 2) the default test function generated by EUnit runs all tests, one after another; as a result, systematic testing will have to explore a number of schedulings that is the product of every individual test's schedulings! You should use Concuerror on single tests instead. * Info: Automatically instrumented module queue * Info: Automatically instrumented module eunit_proc * Tip: Increase '--print_depth' if output/graph contains "...". * Error: Stop testing on first error. (Check '-h keep_going'). * Error: Concuerror does not support calls to built-in erlang:statistics/1 (found in eunit_proc.erl line 324). If you cannot avoid its use, please contact the developers. Stacktrace: [{eunit_proc,with_timeout,3,[{file,"eunit_proc.erl"},{line,324}]}, {eunit_proc,run_group,2,[{file,"eunit_proc.erl"},{line,549}]}, {eunit_proc,child_process,2,[{file,"eunit_proc.erl"},{line,353}]}] Done at 08 Oct 2017 16:28:39 (Exit status: fail) Summary: 1 errors, 1/1 interleavings explored {% endhighlight %} Concuerror does not support calls to `erlang:statistics/1`. Additionally, it emits a *Warning* that this particular use is problematic. Fortunately, the EUnit suite can be extended to also run the tests with Concuerror: {% highlight erlang %} %%============================================================================== -define(concuerror_options, [{module, ?MODULE}, quiet]). foo_concuerror_test() -> ?assertEqual(ok, concuerror:run([{test, foo_test}|?concuerror_options])). msg_concuerror_test() -> ?assertEqual(ok, concuerror:run([{test, msg_test}|?concuerror_options])). reg_concuerror_test() -> ?assertEqual(ok, concuerror:run([{test, reg_test}|?concuerror_options])). {% endhighlight %} Invoking Concuerror again, yields: {% highlight bash %} $ erlc eunit_sample.erl $ erl -noinput -s eunit_sample test -s erlang halt eunit_sample: msg_concuerror_test...*failed* in function eunit_sample:'-msg_concuerror_test/0-fun-0-'/0 (/home/stavros/git/Concuerror/tests-real/suites/options/eunit/eunit_sample.erl, line 38) **error:{assertEqual,[{module,eunit_sample}, {line,38}, {expression,"concuerror : run ( [ { test , msg_test } | ? concuerror_options ] )"}, {expected,ok}, {value,error}]} output:<<"">> eunit_sample: reg_concuerror_test...*failed* in function eunit_sample:'-reg_concuerror_test/0-fun-0-'/0 (/home/stavros/git/Concuerror/tests-real/suites/options/eunit/eunit_sample.erl, line 41) **error:{assertEqual,[{module,eunit_sample}, {line,41}, {expression,"concuerror : run ( [ { test , reg_test } | ? concuerror_options ] )"}, {expected,ok}, {value,error}]} output:<<"">> ======================================================= Failed: 2. Skipped: 0. Passed: 4. {% endhighlight %} The second and third tests have failed as expected and if run individually you can debug them from the report. Have fun and keep testing! ================================================ FILE: docs/_posts/2017-10-14-concuerring-concuerror-ep-1.md ================================================ --- layout: post title: "Concuerring Concuerror" category: tutorials redirect_from: /concuerring-concuerror-ep-1.html --- ### ... and some useful lessons for testing complex applications. Developers learn the term 'heisenbug' when they run their concurrent code and see it succeeding and failing 'randomly'. Concurrent programming is hard and errors in concurrent programs are difficult to trigger and debug. Changing the program makes the bug go away. Tools that can detect such errors are complex, usually have limited abilities and are difficult to use. Hope must be abandoned. Concuerror, a stateless model checking tool capable of detecting concurrency errors in Erlang programs, recently started showing symptoms of suffering from a heisenbug itself. Some of the continuous integration jobs on [Travis CI](https://travis-ci.org/parapluu/Concuerror) would get stuck and timeout due to inactivity ([here](https://travis-ci.org/parapluu/Concuerror/builds/286063501) [are](https://travis-ci.org/parapluu/Concuerror/builds/286108892) [a](https://travis-ci.org/parapluu/Concuerror/builds/286510470) [few](https://travis-ci.org/parapluu/Concuerror/builds/286544528)). Starting those jobs again would sometimes make them succeed. Other, local runs of the test suite, would sometimes crash with Concuerror's own timeout message ('a process did not respond...'). These crashes would notably show up at different points within Concuerror's exploration of program schedulings. These are all symptoms of heisenbugs. A fairly common challenge for a program whose input is some other program (e.g. compilers, code analyzers, etc) is to run such program with itself as input. Especially for analysis tools, such as Concuerror, the goal is to assert that the program does not suffer from the errors it can detect. This is also known as "[eating one's own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food)". Could Concuerror find the concurrency bug it had? As its main developer I wanted the answer to be an easy "yes". Sadly, the complexity of the tool itself was initially too high for such a task. Thus began project [`dogfood`](https://github.com/aronisstav/Concuerror/tree/dogfood). Concuerror should be able to analyze itself, either by supporting more features or by not using them at all. As the 'Concuerror under test' would still need an [input](https://en.wikipedia.org/wiki/Turtles_all_the_way_down), the simplest Erlang function was picked: `test() -> ok.`. The stage was set for a (very nerdy) action film! ## Instrumentation and Code loading One of Concuerror's most useful features is its ability to find source code, instrument it, recompile and reload it on an Erlang VM [in a totally automatic and seamless way](/tutorials/basic-tutorial.html/#-is-it-really-that-simple). Doing this as part of a test, however, would require handling files and this is a very hard task in stateless model checking. In Erlang, this could entail unloads, resets and other similar horrors. Fortunately, Concuerror lets programs communicate with existing registered Erlang processes and allows them to read public ETS tables. Concuerror is using such an ETS table to mark code that has been already instrumented and loaded. By appending a run of the simple test input before the run of 'Concuerror under test' on that input, any code that needed instrumentation would be readily instrumented when inspected by the inner Concuerror. It is unclear how this trick can be generalized. Using files or ports is still hard in code under stateless model checking. ## Unsupported operations The next challenge was in the use of several Erlang builtin operations whose handling has not yet been implemented in Concuerror. Examples include operations that are [fairly problematic to begin with (e.g. `os:timestamp`)](/faq/#limitations) and others that are simply not yet implemented (e.g. `ets:update_element`). Such operations were either simplified in the tool or minimal support for them was added. The last approach was easy, as Concuerror simulates very few parts of Erlang; letting the tool 'just run' the operations (and consider them racing with everything else) is a good first approximation. ## Full sequentialization An early goal of the self-analysis was to assert that Concuerror would be a fully sequential tool: only one process could run at anytime, a constraint that could be enforced by synchronous requests between the components of the application. This approach was abandoned early as full sequentialization of the communication with the logger component was deemed too silly. Concurrent operations would instead be allowed, but any racing operations would be eliminated. ## Looking at the magic Once the first issues were handled, tests started showing a trace of events before getting stuck here or crashing there. This was the first time I saw how much is going on in a run of Concuerror. ETS tables were written and read, messages and signals were flying. I was dazzled. A number of simplifications were done using this info. When the analysis is over, processes can be [gently signaled to exit](https://github.com/parapluu/Concuerror/commit/cd55afb) (Concuerror used to kill them, something which is discouraged in systematic testing, as such kill signals race with almost every other operation). Some synchronization was [added](https://github.com/parapluu/Concuerror/commit/c20def7) in places where it would retain simplicity in the code. ## Improving the user experience Eating one's own dog food reveals small frustrating details that can ruin user experience. Badly formed options that are ignored. Unclear messages to the user. Sanding those hard edges is trivial once they are detected. Using the tool for real is the only way to do so. ## Simplifying the code In the end, solving a tricky knot involving the marking of 'processes under Concuerror' and the handling of `receive` statements split the commit history of the 'dog food' branch into two parts: * Before the [fix](https://github.com/parapluu/Concuerror/commit/c1c641e), Concuerror could not analyze itself: messages would be mysteriously lost between the outer and inner instances and the outer instance would get always stuck. This was not surprising: recursive use of the tool is (still) an esoteric endeavor and getting the instrumentation right is tricky. * After the fix, Concuerror **could analyze itself!** However, the analysis would terminate after exploring just one scheduling, which was correct. No races existed. Was it the case that the bug was still there, but could only be triggered with more complex input? That's what I initially thought. Stateless model checking is a very practical technique for finding concurrency errors, but cannot cover all inputs of a program under test. The reason to believe that the bug had not been fixed was that the fix was just preventing a send operation from being instrumented by moving it elsewhere. To avoid introducing a race **due** to the movement, another send operation was also moved. Their order remained the same. Or so I thought. ## Fixing the bug Concuerror instruments and reloads code in a way that is completely transparent, unless a process has been 'marked' by the tool. Marked processes mostly execute their original code, but wait for permission from Concuerror's scheduler before performing any operations that could be involved in races and also notify the scheduler about the result of any such operation. When a process under Concuerror receives a message, Concuerror's instrumentation intercepts it and places it in a separate queue. This is done because Concuerror's scheduler itself communicates with processes via messages and this interception keeps channels of communication clean. When a process under Concuerror is about to execute a receive statement, the instrumentation inspects the patterns, the existence of an after clause and the messages in the queue. If a matching message is found it's "sent again" (the first send in the story above), so that the actual receive statement can then be executed, finding the message in the mailbox. If no intercepted message matches but an after statement should be executed, no message should be placed in the mailbox. In either case, the process would notify Concuerror's scheduler, sending a message to it (the second send in the story above) *before* executing the actual receive statement. This opened up the possibility that the scheduler would send a message of its own, asking for the next event, before the receive statement was really executed. This was fine and well if a message was about to be received (the message would already be in the mailbox), but if the process was supposed to execute the after clause, it could 'accidentally' receive Concuerror's scheduler message. The process might then crash. Or it might ignore the message and reach the point before the next operation for which it would have to notify the scheduler. It would then wait forever for the scheduler's message (which was already accidentally consumed). The scheduler would also either crash, blaming the process for not responding within reasonable time to its request for a next event, or wait forever (depending on the `--timeout` option used). Having to do the possible "send again" as the absolutely last thing (as this was the operation moved into uninstrumented code), forced the notification to also be moved after the receive statement (with the timeout) was completed. The scheduler's message could no longer be lost. The test had only one interleaving. The bug was fixed. ## ... and a philosophical question In the version before the fix, Concuerror could **not** cleanly find this bug, but was also prevented from running correctly: there was too much instrumentation on the "sent again" handling of a message. After a partial fix the bug could still not really be found: the `receive` statement whose after clause was vulnerable would be uninstrumented (all the inspection would have happened beforehand). Yet, in the version after the fix, Concuerror did not have the bug. So, at the end of the day, did Concuerror find the bug, or was it me? :-) ================================================ FILE: docs/_posts/2017-11-06-Announcing-Mailing-List.md ================================================ --- layout: post title: "Announcing a Mailing List" category: news redirect_from: /Announcing-Mailing-List.html --- Concuerror now has a [mailing list](/contact). ================================================ FILE: docs/_posts/2018-07-15-hex.md ================================================ --- layout: post title: "Available on Hex.pm (with API specification)" category: news redirect_from: /hex.html --- Concuerror is now also available via [Hex.pm](https://hex.pm/packages/concuerror). Specification about its API is available on [Hexdocs](https://hexdocs.pm/concuerror). ================================================ FILE: docs/_posts/2020-10-10-tips.md ================================================ --- layout: post title: "How to optimize Concuerror's search?" category: news redirect_from: /tips.html --- ## Table of contents {:.no_toc} 1. This text will be replaced by the ToC {:toc} This post contains tips for optimizing Concuerror's search. ## The bound options Concuerror has a number of different bound-related options. These can be used to reduce the search space is different ways. ### Depth bound The depth bound (`-d, --depth_bound`) is a limit on how many events[^1] there can be in a single execution of the given test. Concuerror requires test executions to have finite length, and the depth limit is a simple way to guarantee that every execution has a limited number of events. However, **if an execution reaches this limit Concuerror cannot prove correctness of the test** (and it will report so but you can make it keep exploring executions anyway). ### Interleaving bound The interleaving bound (`-i, --interleaving_bound`) is a hard limit on how many interleavings of the given test will be explored. Concuerror is currently always deterministic, so, unless the test or other options are modified, the same interleavings will be explored, in the same order, and running a test repeatedly will not return different results. ### Scheduling bound Exploring all the interleavings of a concurrent program (and therefore verifying it), even with Concuerror's specialized techniques, can lead to exploring a number of interleavings that grows exponentially with the length of the execution. If one is interested in finding bugs, on the other hand, it might be fruitful to explore "simpler" schedulings before trying more elaborate ones. Concuerror can help achieve that goal by using a scheduling bound. As explained in the Bounding chapter in ["Effective Techniques for Stateless Model Checking"](/publications): >This [goal] can be achieved using bounding techniques, that impose constraints on how/when processes can be scheduled by a stateless model checking algorithm and force the algorithm to focus the exploration on schedulings that satisfy those constraints. In this way, bugs in ‘simpler’ schedulings can be detected faster than when using exhaustive exploration. Schedulings that violate the constraints can also be explored, but each exploration begins with a budget (also called a bound), which is spent whenever the algorithm schedules processes in a way that violates the constraints. When no budget remains, the SMC algorithm can only explore schedulings that satisfy the constraints. #### "Simpler" schedulings When schedule bounding techniques are used, the "simplest" scheduling is chosen by the combination of scheduling options, currently `--scheduling` and `--strict_scheduling`. #### Scheduling bound The `-b, --scheduling_bound` option defines the value of the scheduling bound. #### Scheduling bound types The possible options for scheduling bounding are * `none` (default), if no scheduling bounding should be used * `delay` (recommended, when using bounding, and selected by default if a value is chosen for `--scheduling_bound` and no type is specified), which in Concuerror does **not** correspond to the technique found in other bibliography, but at a variant which we have named "exploration tree bounding" in the same publication[^2]. A benefit of this technique is that it is compatible with any DPOR algorithm and leads to fairly smooth scaling of the explored search space with the increase of the bound. * `bpor`, as described by \[16\] in the same publication[^3], which is based on limiting the number of times a process can be preempted. A practical weakness of this technique is that when a process becomes blocked *any* other process can be scheduled without consuming bounding budget, possibly leading to unpredictable scaling behaviours. * `ubpor`, which is a mostly abandoned experimental variant of `bpor` and is not recommended. ## Using the bounds and optimizing your tests This section provides some general guidelines on how to use Concuerror effectively. ### See why Concuerror is exploring interleavings Concuerror explores interleavings based on racing operations it detects. Using the `log_all` option together with `show_races` (and a small `interleaving bound`, to just sample the space of the test) can let you see the racing operations in a test, and can be a useful aid in simplifying. ### "Shutdown" sequences of OTP behaviours If you are spawning processes using supervisors or OTP behaviours, it is often the case that when your main test process finishes a cascade of shutdowns will be triggered, which may or may not lead to more races. If you are not interested in the behaviour of your processes shutting down, blocking the main test process with a `receive after infinity -> ok end` (and ignoring the deadlocks with `--ignore-error deadlock`) might simplify your test. ### Start simple Two or three processes and relatively simple test scenarios are enough to trigger most race conditions. Even a simple "sequential concatenation" of two complex racing scenarios will lead to a state space that has the size of the product of the two individual scenarios (Concuerror is dynamic and cannot know e.g. that the second scenario always, independently follows the first). ### Use a scheduling bound Using a scheduling bound can quickly and effectively lead to a better search for bugs in your tests. A good way to summarize this is: "if you suspect a bug is there, a small scheduling bound will find it". ### Read the hints Concuerror emits hints upon detecting some patterns that may lead to unexpected explosion of the search space. Reading and understanding them is recommended. ## Final note Concuerror is a fairly expert tool and mastering its use requires practise. If you have further questions consider communicating over the [suggested channels](/contact)! [^1]: As events, Concuerror considers any kind of Erlang built-in operation that can be potentially interfering with operations in other processes, such as the delivery of a message, the expiration of a timeout, or writing and reading values in an ETS table. If you are interested in a snapshot of what kind of operations in Erlang programs can interfere, read the paper ["The shared-memory interferences of Erlang/OTP built-ins"](/publications). [^2]: ["Effective Techniques for Stateless Model Checking"](/publications) [^3]: Same as above. ================================================ FILE: docs/_posts/2020-10-17-code-beam-sf-2019.md ================================================ --- layout: post title: "Talk at CODE BEAM SF 2019: Slides" category: news redirect_from: /code-beam-sf-2019.html --- Stavros Aronis presented a talk on Concuerror at CODE BEAM 2019. You can find more info [here](https://codesync.global/speaker/stavros-aronis/#301modeling-and-verifying-distributed-applications-with-concuerror). The slides are [here](/assets/pdf/CodeBEAMSF2019.pdf). ================================================ FILE: docs/_posts/2020-10-17-functional-conf-2018.md ================================================ --- layout: post title: "Talk at Functional Conf 2018: Video and Slides" category: news redirect_from: /functional-conf-2018.html --- Stavros Aronis presented a talk on Concuerror at Functional Conf 2018. {% assign yt-link = "xAoIn_3b_n8" %} {% include yt.html %} You can find more info [here](https://confengine.com/functional-conf-2018/proposal/8528/conquering-race-conditions-in-erlang-programs-with-concuerror). The slides are [here](https://drive.google.com/file/d/11dhg9lG2v1cHpDrgLPcUlVvna39qHVFG/preview?feature=oembed). ================================================ FILE: docs/_posts/2020-10-24-basic-tutorial.md ================================================ --- layout: post category: tutorials --- Before you launch Concuerror, you need a test that is **terminating** (ideally in any scheduling of the processes) and **closed** (does not require any inputs). You should also keep in mind that systematic testing (unlike stress-testing) does not encourage (or require) the use of too many processes! **All** schedulings of the test will be explored, so "the simpler, the better"! Once you have such a test, all you have to do is compile your code as usual (preferably with `debug_info`) and then invoke Concuerror from your shell, specifying the module and function that contains your test: {% highlight bash %} $ concuerror -m my_module -t my_test {% endhighlight %} You can also invoke `concuerror:run/1` from an Erlang shell: {% highlight erlang %} 1> concuerror:run([{module, my_module}, {test, my_test}]). {% endhighlight %} or: {% highlight erlang %} 2> concuerror:run([{entry_point, {my_module, my_test, []}]). {% endhighlight %} The tool automatically instruments any modules used in the test, using Erlang's automatic code loading infrastructure, so nothing more is in principle needed! Read the [FAQ](/faq) for more help. ## ... is it really that simple? Well, for many programs that is probably enough! If your test is named `test` you can even skip the `-t` option! If a scheduling leads to one or more processes crashing or deadlocking, Concuerror will print a detailed trace of all the events that lead to the error and by default stop the exploration. You can then use this trace to debug the detected error. Otherwise it will keep exploring schedulings, until it has checked them all. [Will the exploration ever finish?](/faq/#will-the-exploration-ever-finish) ## Read more A more detailed introductory tutorial, including sample code is available [here](/tutorials/poolboy-example.html). [Check out more tutorials here!](/tutorials) ================================================ FILE: docs/contact.md ================================================ --- layout: page permalink: /contact/index.html title: "Contact" description: "Information about contacting Concuerror's developers" updated: 2018-02-22 --- Here is information about how to contact the developers. ## Mailing List The preferred way of communication is the mailing list. ### Archives ### Subscribe Send an email to [concuerror-request@freelists.org](mailto:concuerror-request@freelists.org?subject=Subscribe&body=I+want+to+subscribe+to+Concuerror's+mailing+list!) with 'Subscribe' in the Subject field OR Visit the list's page at . ### FAQ You can find FAQ about mailing lists hosted by freelists.org at . ================================================ FILE: docs/download.md ================================================ --- layout: page permalink: /download/index.html title: "Download Concuerror" description: "Information on how to download Concuerror." --- ## Github Concuerror's latest stable version is available on [Github](https://github.com/parapluu/Concuerror): {% highlight bash %} $ git clone https://github.com/parapluu/Concuerror.git $ cd Concuerror $ make {% endhighlight %} The preferred way to start concuerror is via the `bin/concuerror` escript. ## Hex.pm Concuerror is also [available via Hex.pm](https://hex.pm/packages/concuerror). This enables you to include it in your project using any of the building tools that support Hex.pm dependencies. ## Supported OTP Releases See [Supported OTP Releases](https://github.com/parapluu/Concuerror#supported-otp-releases). We use [Github Actions](https://github.com/parapluu/Concuerror/actions) to test: * The *two last* minor versions of the 'current' major Erlang/OTP release * The *last* minor version of recent older major releases You can also find older versions of Concuerror [here](https://github.com/mariachris/Concuerror.git). ================================================ FILE: docs/faq.md ================================================ --- layout: page permalink: /faq/index.html title: "Concuerror's FAQ" description: "Concuerror's Frequently Asked Questions" updated: 2017-10-16 --- # Frequently Asked Questions {:.no_toc} 1. This text will be replaced by the ToC, excluding the previous header (WOW!) {:toc} ## How do I report a bug? The preferred way is to use the repository's [Issues page](https://github.com/parapluu/Concuerror/issues/new), but you can also [contact the developers]({% link contact.md %}) in other ways. ## How can I tell Concuerror where to find the code used by my test? Concuerror automatically instruments and reloads any code used by your test transparently relying on Erlang's code path. It also supports a number of familiar options to specify input files. Check `concuerror -h input` for more info. ## Will the exploration ever finish? Complex concurrent programs can have a LOT of schedulings! Concuerror uses a technique called Optimal Dynamic Partial Order Reduction to filter through them and has special knowledge about the interferences of Erlang/OTP built-ins (see the relevant [Publications](/publications)). Still, all this reduction power may sometimes not be enough. If Concuerror keeps running for a while, you may want to limit the exploration (using e.g. the `--interleaving_bound` option) and visualize the explored schedulings via the `--graph` option (the [dot](https://graphviz.org/) tool is needed to produce an image). You can then see which operations the tool considers as racing using the `--show_races` option and possibly simplify the test. Concuerror may also print tips during its execution, suggesting ways to improve the effectiveness of testing. A large number of options are available to fine tune the tool. You can find out more about them by using `--help all`. ## Can Concuerror do 'random testing'? A 'random testing' mode has not yet been implemented in Concuerror (add your voice to the related [Issue page](https://github.com/parapluu/Concuerror/issues/16)). If your goal is not verification, Concuerror has its own way to sample the interleavings in a less systematic way: schedule bounding. You can read more about it by `--help bound`. ## How does Concuerror work? (extended) Concuerror runs the program under test in a controlled way so that only one process runs at a time. 1. It begins with an arbitrary initial run, during which it logs any operations that affect shared state, such as sending and delivery of messages, operations on ETS tables etc. The result is a sequence of such events, called an *interleaving*. 2. Afterwards it analyses this interleaving, finding pairs of events that could have different results if they were to happen in the reverse order (e.g. a message being delivered before the receiving process could have executed an after clause instead). 3. Then it "plans" new interleavings of the program that will force this reverse order for each such pair. Any such interleavings "replay" some events from the one currently analyzed up to some instruction and then diverge, in order to execute other events before those involved in a race. 4. Finally it checks for the "latest" place where it has made such a "plan", actually replays all events up to that point and continues as described in the plan. At some point in this new interleaving new behaviours may emerge. 5. It repeats this approach from step 2, until no other such plans remain. This is a technique known as _stateless model checking with dynamic partial order reduction_ ([read more](/publications)). ## How does Concuerror control the scheduling of processes? Concuerror automatically adds instrumentation code and reloads any module that is used by the test. The instrumentation forces any process involved in the test to stop and report any operation that affects shared state. ## Does Concuerror support all of Erlang's operations/libraries? Concuerror supports the complete Erlang language and can instrument programs of any size. It can be the case that support for some built-in operations is not complete but this is a matter of prioritization of tasks. There are however certain limitations regarding e.g. [timeouts](#how-does-concuerror-handle-timeouts-and-other-time-related-functions) and [non-deterministic functions](#how-does-concuerror-handle-non-deterministic-functions). ## How can I get rid of '...' in output files? Use a higher `--print_depth`. ## Is there a mailing list? [Yes]({% link contact.md %})! ## Is there a changelog? [Yes](https://github.com/parapluu/Concuerror/blob/master/CHANGELOG.md)! ## Limitations ### How does Concuerror handle timeouts and other time-related functions? #### Timeouts Timeouts may appear as part of an Erlang [`receive`](https://www.erlang.org/doc/system/expressions.html#receive) statement or calls to [`erlang:send_after/3`](https://www.erlang.org/doc/apps/erts/erlang.html#send_after/3) and [`erlang:start_timer/3`](https://www.erlang.org/doc/apps/erts/erlang.html#start_timer/3). Due to the fact that Concuerror's instrumentation has an overhead on the execution time of the program, Concuerror normally disregards the actual timeout values and assumes: * **For** `receive` **timeouts**: the `after` clause is always assumed to be possible to reach. Concuerror *will* explore interleavings that trigger the `after` clause, unless it is impossible for a matching message to not have arrived by the time the `receive` statement is reached. * **For** `send_after`**-like timeouts**: The timeout message may arrive at any time until canceled. You can use `-- after-timeout N` to make Concuerror regard timeouts higher than `N` as infinity. #### Time-related functions (E.g. `erlang:time/0`) Concuerror handles such functions together with other [non-deterministic functions](#how-does-concuerror-handle-non-deterministic-functions). ### How does Concuerror handle non-deterministic functions? The first time Concuerror encounters a non-deterministic function it records the returned result. In every subsequent interleaving that is supposed to 'branch-out' *after* the point where the non-deterministic function was called, the recorded result will be supplied, to ensure consistent exploration of the interleavings. This may result in unexpected effects, as for example measuring elapsed time by calling `erlang:time/0` twice may use a recorded result for the first call and an updated result for the second call (if it after the branching-out point), with the difference between the two increasing in every interleaving. ### How does Concuerror handle NIFs? Concuerror cannot intercept, instrument or perform race analysis in modules using NIFs and is very likely to crash if such modules are used. Better support may be included in the future. ================================================ FILE: docs/feed.xml ================================================ --- layout: null --- {{ site.title }} Jekyll {{ site.time | date_to_xmlschema }} {{ site.url }}/ {{ site.owner.name }} {{ site.url }}/ {{ site.owner.email }} {% for post in site.posts limit:20 %} <![CDATA[{{ post.title | cdata_escape }}]]> {% if post.link %}{{ post.link }}{% else %}{{ site.url }}{{ post.url }}{% endif %} {{ site.url }}{{ post.id }} {% if post.modified %}{{ post.modified | to_xmlschema }}T00:00:00-00:00 {{ post.date | date_to_xmlschema }} {% else %}{{ post.date | date_to_xmlschema }} {{ post.date | date_to_xmlschema }}{% endif %} {{ site.owner.name }} {{ site.url }} {{ site.owner.email }} {{ post.excerpt | xml_escape }} <p><a href="{{ site.url }}{{ post.url }}">{{ post.title }}</a> was originally published by {{ site.owner.name }} at <a href="{{ site.url }}">{{ site.title }}</a> on {{ post.date | date: "%B %d, %Y" }}.</p> {% endfor %} ================================================ FILE: docs/index.md ================================================ --- layout: page updated: 2020-10-25 description: "Homepage of the Concuerror, a tool for debugging, testing and verifying concurrent Erlang programs." --- **Concuerror** is a **stateless model checking tool** for **Erlang** programs. It can be used to **detect** and **debug** concurrency errors, such as **deadlocks** and errors due to **race conditions**. Moreover it can **verify** the absence of such errors, because it tests programs **systematically**, unlike techniques based on randomness.
[Get Concuerror!](/download){: .btn .btn--success .btn--large }
## Latest News [Read the latest news here](/news). ## How to use Concuerror? [Read the basic tutorial here]({% post_url 2020-10-24-basic-tutorial %}). ## What are race conditions? Concuerror's operation is based on detecting pairs of operations that are racing, i.e. could have a different result if scheduled in the opposite order. You can see some of these pairs by using the [`--show_races` option](https://hexdocs.pm/concuerror/concuerror_options.html#show_races_option-0). Remember, however, that not all race conditions are necessarily bad! An example of benign racing is when worker processes report their progress by sending messages to a work managing process. These messages are racing, as they may reach (and be received) by the managing process in different orders. Race conditions become can lead to errors when the program expects certain events to occur in a particular order, but that order is not always guaranteed. Such errors are often hard to reproduce, when they require rare, particular schedulings. Concuerror systematically explores all "meaningfully different" schedulings, detecting all such errors or verifying their absence. ## How does Concuerror work? Concuerror schedules the Erlang processes spawned in the test as if only a single scheduler was available. During execution, the tool records a trace of any calls to built-in operations that can behave differently depending on the scheduling (e.g., receive statements, registry operations, ETS operations). It then analyzes the trace, detecting pairs of operations that are really racing. Based on this analysis, it explores more schedulings, reversing the order of execution of such pairs. This is a technique known as _stateless model checking with dynamic partial order reduction_. [Read more details about how Concuerror works here](/faq/#how-does-concuerror-work-extended). ================================================ FILE: docs/news.md ================================================ --- layout: posts permalink: /news/index.html title: News description: "Links to all posts on this website, sorted by date." show_excerpts: false --- ================================================ FILE: docs/publications.md ================================================ --- layout: page permalink: /publications/index.html title: Publications description: "Scientific publications regarding and involving Concuerror." updated: 2020-10-03 --- This is a list of publications related to Concuerror. Most titles are links to official versions of the articles. ## Dissertation
[**Effective Techniques for Stateless Model Checking**](https://urn.kb.se/resolve?urn=urn:nbn:se:uu:diva-333541)
Stavros Aronis.
PhD Dissertation, Uppsala University, 2018.
## Papers

ACM DL Author-ize service**Modelling distributed Erlang within a single node**
Stavros Aronis, Viktória Fördős, and Dániel Szoboszlay
Erlang 2018 Proceedings of the 17th ACM SIGPLAN International Workshop on Erlang, 2018

[**Optimal Dynamic Partial Order Reduction with Observers**](https://rdcu.be/LLeU)
Stavros Aronis, Bengt Jonsson, Magnus Lång, and Konstantinos Sagonas.
TACAS 2018, Tools and Algorithms for the Construction and Analysis of Systems. Lecture Notes in Computer Science, vol 10806, 2018.
The official publication is available at link.springer.com via [its DOI link](https://doi.org/10.1007/978-3-319-89963-3_14).
[**Testing and Verifying Chain Repair Methods for Corfu Using Stateless Model Checking**](/assets/pdf/iFM2017.pdf)
Stavros Aronis, Scott Lystig Fritchie, and Konstantinos Sagonas.
IFM 2017, Integrated Formal Methods: Proceedings of the 13th International Conference, 2017.
The official publication is available at link.springer.com via [its DOI link](https://doi.org/10.1007/978-3-319-66845-1_15).

ACM DL Author-ize service**The shared-memory interferences of Erlang/OTP built-ins**
Stavros Aronis and Konstantinos Sagonas.
Erlang 2017, Proceedings of the 16th ACM SIGPLAN International Workshop on Erlang, 2017.

ACM DL Author-ize service**Optimal dynamic partial order reduction**
Parosh Abdulla, Stavros Aronis, Bengt Jonsson, and Konstantinos Sagonas.
POPL '14, Proceedings of the 41st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, 2014.

[**Systematic Testing for Detecting Concurrency Errors in Erlang Programs**](/assets/pdf/ICST2013.pdf)
Maria Christakis, Alkis Gotovos, and Konstantinos Sagonas.
ICST 2013, Proceedings of the International Conference on Software Testing, Verification and Validation (ICST), 2013.
The official publication is available at ieeexplore.ieee.org via [its DOI link](https://doi.org/10.1109/ICST.2013.50).

ACM DL Author-ize service**Test-driven development of concurrent programs using Concuerror**
Alkis Gotovos, Maria Christakis, and Konstantinos Sagonas.
Erlang '11, Proceedings of the 10th ACM SIGPLAN workshop on Erlang, 2011.

## Theses
[**Systematic Testing of Concurrent Erlang Programs: Some Experiences**](https://dx.doi.org/10.26240/heal.ntua.16898)
Ilias Tsitsimpis.
Diploma Thesis, National Technical University of Athens, 2013.
[**Dynamic Systematic Testing of Concurrent Erlang Programs**](https://dx.doi.org/10.26240/heal.ntua.7193)
Alkis Gotovos.
Diploma Thesis, National Technical University of Athens, 2011.
================================================ FILE: docs/search.md ================================================ --- layout: search permalink: /search/index.html title: Search description: "Search Concuerror's website" --- ================================================ FILE: docs/sitemap.xml ================================================ --- layout: null --- {{ site.url }} {% for post in site.posts %} {{ site.url }}{{ post.url }} {% endfor %} ================================================ FILE: docs/tutorials.md ================================================ --- layout: category permalink: /tutorials/index.html title: Tutorials description: "Links to tutorials on how to use Concuerror, sorted by date." show_excerpts: false taxonomy: tutorials --- This is a list of tutorials on how to use Concuerror. If you are looking for documentation, check the [API](https://hexdocs.pm/concuerror). ================================================ FILE: elvis.config ================================================ [{elvis, [{config, [ #{ dirs => ["src"] , filter => "*.erl" , rules => [ {elvis_style, module_naming_convention, #{ regex => "^concuerror(_[a-z]+)*$" , ignore => [] }} , {elvis_style, no_tabs} , {elvis_style, no_trailing_whitespace} , {elvis_style, macro_module_names} , {elvis_style, operator_spaces, #{ rules => [ {right, "++"} , {left, "++"} , {right, "->"} , {left, "->"} , {right, "!"} , {left, "!"} ]}} , {elvis_style, nesting_level, #{level => 5}} , {elvis_style, god_modules, #{ limit => 20 , ignore => [ concuerror_options ] }} , {elvis_style, no_if_expression} , {elvis_style, function_naming_convention, #{ regex => "^[a-z]{2}([a-z0-9]*_?)*$"}} , {elvis_style, state_record_and_type} , {elvis_style, no_spec_with_records} , {elvis_style, dont_repeat_yourself, #{min_complexity => 13}} , {elvis_style, used_ignored_variable} , {elvis_style, no_debug_call, #{ignore => []}} ] } , #{ dirs => ["src"] , filter => "*.erl" , rules => [ {elvis_style, operator_spaces, #{ rules => [ {right, ","} ] }} , {elvis_style, line_length, #{ limit => 80 , skip_comments => false }} ] , ignore => [ concuerror_dependencies ] } , #{ dirs => ["."] , filter => "rebar.config" , rules => [ {elvis_project, no_deps_master_rebar, #{ignore => []}} , {elvis_project, protocol_for_deps_rebar, #{ignore => []}} ] } ] }]}]. ================================================ FILE: priv/concuerror ================================================ #!/usr/bin/env escript %%! +S1 -boot start_clean -noshell -pa . -pa ebin %% This script is used instead of the 'escriptized' version of the %% tool to collect code coverage data, as it was not obvious how to %% use cover with escript's embedded zip archive. main(Args) -> ScriptName = read_link(escript:script_name()), ScriptDir = filename:dirname(ScriptName), TopDir = filename:dirname(ScriptDir), ExtraDirs = [filename:join([TopDir, "_build", Profile, "lib", App, "ebin"]) || Profile <- ["default", "dev"], App <- ["concuerror", "getopt"] ], ok = code:add_pathsa(ExtraDirs), concuerror:main(Args). -spec read_link(file:filename()) -> file:filename(). read_link(Filename) -> case file:read_link_all(Filename) of {ok, Follow} -> read_link(Follow); _Other -> Filename end. ================================================ FILE: priv/generate_option_docfiles ================================================ #!/usr/bin/env escript %%! -noshell -pa _build/docs/lib/concuerror/ebin main(Dir) -> concuerror_options:generate_option_docfiles(Dir). ================================================ FILE: priv/generate_version_hrl ================================================ #!/usr/bin/env escript %%! -noshell %% The expected argument is the latest OTP version supported by %% Concuerror. main([[LD, LU|_] = _LatestOTP]) -> MasterMajor = list_to_integer([LD, LU]) + 1, CurrentOTPRelease = case erlang:system_info(otp_release) of "R" ++ _ -> 16; %% ... or earlier [D,U|_] -> list_to_integer([D,U]) end, io:format("-define(OTP_VERSION, ~w).~n", [CurrentOTPRelease]), add_befores_for(CurrentOTPRelease + 1, MasterMajor). add_befores_for(Release, MasterMajor) when Release =< MasterMajor -> add_before_for(Release), add_befores_for(Release + 1, MasterMajor); add_befores_for(_, _MasterMajor) -> ok. add_before_for(Release) -> io:format("-define(BEFORE_OTP_~p, true).~n", [Release]). ================================================ FILE: rebar.config ================================================ {minimum_otp_vsn, "20.0"}. {erl_opts, [ debug_info , warn_export_vars , warn_unused_import , warn_missing_spec , warn_untyped_record ]}. {deps, [{getopt, "1.0.1"}]}. {escript_incl_apps, [concuerror, getopt]}. {escript_main_app, concuerror}. {escript_name, concuerror}. {escript_emu_args, "%%! +S1 -boot start_clean -noshell -pa . -pa ebin\n"}. {pre_hooks, [ {compile, "priv/generate_version_hrl 23 > src/concuerror_otp_version.hrl"} , {edoc, "priv/generate_option_docfiles doc"} ]}. {post_hooks, [ {escriptize, "cp \"$REBAR_BUILD_DIR/bin/concuerror\" ./bin"} ]}. {profiles, [ {dev, [ {erl_opts, [{d, 'DEV', "true"}]} ]} , {docs, [ {erl_opts, [{d, 'DOC', "true"}]} ]} , {lint, [ {plugins, [{rebar3_lint, "0.1.10"}]} ]} , {native, [ {erl_opts, [native]} ]} , {pedantic, [ {erl_opts, [warnings_as_errors]} ]} , {test, [ {erl_opts, [export_all, nowarn_missing_spec]} ]} ]}. {edoc_opts, [ {macros, [{'DOC', "true"}]} , {preprocess, true} , {title, "Concuerror's API specification"} ]}. {dialyzer, [ {warnings, [ unmatched_returns , unknown ]} , {base_plt_apps, [ compiler , crypto , erts , getopt , kernel , stdlib , syntax_tools , tools ]} ]}. {project_plugins, [covertool]}. {cover_enabled, true}. {cover_export_enabled, true}. {covertool, [ {coverdata_files, [ "eunit.coverdata" ]} , {include_apps, [concuerror]} ]}. ================================================ FILE: resources/DPOR_paper_material/DPOR_README ================================================ Optimal Dynamic Partial Order Reduction for Analysis of Concurrent Programs --------------------------------------------------------------------------- Contents: --------- 1. Introduction 2. Prerequisites 3. Quick intro 4. Tests 5. Interesting details in the code 1. Introduction --------------- The purpose of this readme file is to offer a brief guide around an experimental version of the Concuerror tool, which was developed to include an algorithm for optimal partial order reduction of the state space generated by the exploration of concurrent applications written in the Erlang language. The experimental functionality, as well as two more similar extensions/modifications are enabled with command line options. The tool runs the stable version, if these are not provided. The stable version supports a few more Erlang built-in functions that require preemption points and has been tested and integrated with a GUI. The experimental versions use the command line interface. 2. Prerequisites ---------------- Concuerror is an Erlang application, so you will need an Erlang runtime system to run it. Most Linux distributions have suitable packages. To run the original Concuerror testsuite you will also need Python. The application startup and shutdown relies on a bash script, so it is currently not possible to run Concuerror on a Windows machine. You can build the application using 'make' CAUTION: This file's information refer to a version of the code that has been tagged under git as 'POPL_submission'. Check out that version before continuing. 3. Quick intro -------------- Concuerror expects as input a set of Erlang source modules, a target function and a preemption bound. Its output in results.txt are all the traces of the program that had some concurrency error. We explain each briefly the command line options: - source files : are the files that will be instrumented to include preemption points before built-in functions that may affect the global state. Option: -f - target function : is an exported function in one of the files given as input. This is the function that will be run by the first process. Option: -t [ ] (arguments are optional. If none is given, the function with 0 arity will be called) - preemption bound : designates how many 'unnecessary' preemptions are allowed in the current run. Concuerror will always allow enabled processes to run after a process has become blocked (by trying to execute a receive when no matching messages are in its mailbox) and will also allow processes to be interrupted while still being enabled for other processes to be scheduled instead as many times as the preemption bound. Option: -p (default value is 2) - versions : by default you will be running the stable version of Concuerror. The following command line options can be used to enable 3 alternative versions, based on the same machinery: --dpor_fake : is a 'sanity' check version of Concuerror using the modified scheduler, but treating all operations as dependent. Should give results similar to those of the stable version, with maybe a few more interleavings. --dpor : is our experimental extension. Uses simple source sets to decide additional interleavings, together with our set of rules for dependencies between Erlang built-in functions. --dpor_flanagan : is a version using the algorithm proposed by Flanagan and Godefroid, extended with sleep sets as described in our cited paper. Examples: To run stable Concuerror on two modules test.erl and foo.erl in your home directory, using test:run/0 as your starting function and infinite preemption bound: ./concuerror -f ~/test.erl ~/foo.erl -t test run -p inf To run the same test using our experimental extension: ./concuerror -f ~/test.erl ~/foo.erl -t test run -p inf --dpor You can run ./concuerror --help for description of a few more command line options. 4. Tests -------- You can instantly run two different testsuites that showcase the experimental version: a) The dpor_tests collection b) Concuerror's stable testsuite, which has been adapted slightly to run the experimental version instead of the stable one. Let's go into more details: a) dpor_tests ------------- This is a collection of motivating examples that were used during the development of the experimental version. They include toy Erlang programs as well as all the examples presented in the paper. The toy tests were written to expose dependencies in the supported Erlang built-in functions and to showcase the differences and strengths between the different versions of the tool. The tests output is compared against a stored expected output to decide success or failure. A few (less than 5, usually 1) of the tests are expected to fail: in these cases a diff of the expected output and the real output should show environment related changes, as the traces sometimes include information that is environment sensitive. These tests are in the dpor_tests directory and you can run all of them by: dpor_tests/dpor_test ... and a specific test by: dpor_tests/dpor_test dpor_tests/dpor_test/src/.erl The output is written in the dpor_tests/new_results directory and is compared with the reference output in dpor_tests/results. If it differs the test is reported as FAILED and the output is left for comparison. You can then use a (graphical) diff tool (e.g. meld) to see the differences in the outputs. You can of course run any of the tests with e.g.: ./concuerror -f dpor_tests/src/.erl -t -p inf --dpor Interesting tests in dpor_tests: -------------------------------- - ets_dependencies.erl : This is the simple 2 readers vs 1 writer example. - ets_dependencies_n.erl : This is the extended example presented in the paper. You can run this example with a varying number of readers like this: T=ets_dependencies ./concuerror -f dpor_tests/src/$T.erl -t $T $T -p inf --dpor - file_system_example.erl : The file system example written in Erlang - independent_receivers.erl : A test with just two interleavings, where stable Concuerror explores 234300 interleavings. - register_again.erl : A test showing usage of Erlang built in functions. - ring_leader_election_symmetric : An implementation of leader election in a set of processes connected in a ring. - ring_leader_election_symmetric_buffer.erl : Same as before, with the difference that here mailboxes are 'modeled' as separate processes in such a way that 'sends' and 'receives' are also interleaved, leading to an explosion in the number of explored interleavings. - send_it_ets.erl : An example showing why send operations with the same message to the same process must be also interleaved. b) Concuerror's stable testsuite -------------------------------- Concuerror's stable testsuite has also been run with --dpor to check for any missing dependencies. The files are stored in testsuite/suites, including reference results. Running the tests creates the testsuite/results (which can again be diffed against the reference directory in case of failures). You can run the suite by: make test 87 of the tests are expected to fail because the reference results are those obtained by running --dpor_fake. This is to show the difference, which in most cases favors --dpor (unless an unsupported instruction is used, in which case the program crashes). The --dpor_fake results are in the dpor directories under each suite. They have been compared against the results (stored in the vanilla directories) obtained by running the stable version with a few added preemption points. These are in turn comparable with the original results (stored in the results directories). Interesting test in Concuerror's testsuite ------------------------------------------ - manolis_test_2workers: Corresponds to the rush_hour test presented in the paper. 5. Interesting details in the code ---------------------------------- Apart from concuerror_rep.erl which has all the replacement functions for the actual calls that are found in the instrumented modules, all the main algorithm run from concuerror_sched.erl. The dependent/2 boolean function returns true when two operations are dependent. The main loop of the algorithm is in the explore/1 function. Finally the two different DPOR versions differ in the details of add_all_backtracks/1 function. ================================================ FILE: resources/DPOR_paper_material/foo.erl ================================================ -module(foo). -export([foo/0]). foo() -> P = self(), spawn(fun() -> P ! a end), spawn(fun() -> P ! b end), spawn(fun() -> P ! c end), receive V -> V after 0 -> bloo end. ================================================ FILE: resources/DPOR_paper_material/foobar.erl ================================================ -module(foobar). -export([foo/0]). foo() -> bar(). bar() -> ok. ================================================ FILE: resources/DPOR_paper_material/my_test.erl ================================================ -module(my_test). -export([test/1]). test(small) -> foo_test(); test(large) -> foobar_test(). foo_test() -> dialyzer:run([{files, ["/home/stavros/git/Concuerror/resources/DPOR_paper_material/foo.erl"]}, {from, src_code}]). foobar_test() -> dialyzer:run([{files, ["/home/stavros/git/Concuerror/foobar.erl"]}, {from, src_code}]). ================================================ FILE: resources/DPOR_paper_material/run_dialyzer.sh ================================================ #!/usr/bin/env bash echo "\\hline" for i in small large; do f=0 for t in --dpor --dpor_source --dpor_classic; do if [ $f -eq 0 ]; then echo "\multirow{3}{*}{$T} & \multirow{3}{*}{$i} & " echo -n " o-DPOR &" elif [ $f -eq 1 ]; then echo -n " & & s-DPOR &" else echo -n " & & DPOR &" fi f=$((f+1)) ./conc_dia.sh $i $t | grep "OUT" | sed 's/OUT//' done echo "\\hline" done ================================================ FILE: resources/DPOR_paper_material/run_filesystem.sh ================================================ #!/usr/bin/env bash T=file_system_example echo "\\hline" for i in 16 18; do f=0 for t in --dpor --dpor_source --dpor_classic; do if [ $f -eq 0 ]; then echo "\multirow{3}{*}{file\_system} & \multirow{3}{*}{$i} & " echo -n " o-DPOR &" elif [ $f -eq 1 ]; then echo -n " & & s-DPOR &" else echo -n " & & DPOR &" fi f=$((f+1)) ./concuerror_mem --noprogress -f testsuite/suites/dpor/src/$T.erl \ -t $T main $i -p inf $t \ | grep "OUT" | sed 's/OUT//' done echo "\\hline" done ================================================ FILE: resources/DPOR_paper_material/run_indexer.sh ================================================ #!/usr/bin/env bash T=indexer_example echo "\\hline" for i in 12 15; do f=0 for t in --dpor --dpor_source --dpor_classic; do if [ $f -eq 0 ]; then echo "\multirow{3}{*}{$T} & \multirow{3}{*}{$i} & " echo -n " o-DPOR &" elif [ $f -eq 1 ]; then echo -n " & & s-DPOR &" else echo -n " & & DPOR &" fi f=$((f+1)) ./concuerror_mem --noprogress -f testsuite/suites/dpor/src/$T.erl \ -t $T main $i -p inf $t \ | grep "OUT" | sed 's/OUT//' done echo "\\hline" done ================================================ FILE: resources/DPOR_paper_material/run_last_zero.sh ================================================ #!/usr/bin/env bash T=sleeping_races_8 echo "\\hline" for i in 2 5 10; do f=0 for t in --dpor --dpor_source --dpor_classic; do if [ $f -eq 0 ]; then echo "\multirow{3}{*}{last_zero} & \multirow{3}{*}{$i} & " echo -n " o-DPOR &" elif [ $f -eq 1 ]; then echo -n " & & s-DPOR &" else echo -n " & & DPOR &" fi f=$((f+1)) ./concuerror_mem --noprogress -f testsuite/suites/dpor/src/$T.erl \ -t $T $T $i -p inf $t \ | grep "OUT" | sed 's/OUT//' done echo "\\hline" done ================================================ FILE: resources/DPOR_paper_material/run_readers.sh ================================================ #!/usr/bin/env bash T=readers echo "\\hline" for i in 2 8; do f=0 for t in --dpor --dpor_source --dpor_classic; do if [ $f -eq 0 ]; then echo "\multirow{3}{*}{$T} & \multirow{3}{*}{$i} & " echo -n " o-DPOR &" elif [ $f -eq 1 ]; then echo -n " & & s-DPOR &" else echo -n " & & DPOR &" fi f=$((f+1)) ./concuerror_mem --noprogress -f testsuite/suites/dpor/src/$T.erl \ -t $T $T $i -p inf $t \ | grep "OUT" | sed 's/OUT//' done echo "\\hline" done ================================================ FILE: resources/DPOR_paper_material/run_rush_hour.sh ================================================ #!/usr/bin/env bash T=rush_hour echo "\\hline" for i in -; do f=0 for t in --dpor --dpor_source --dpor_classic; do if [ $f -eq 0 ]; then echo "\multirow{3}{*}{rush\_hour} & \multirow{3}{*}{$i} & " echo -n " o-DPOR &" elif [ $f -eq 1 ]; then echo -n " & & s-DPOR &" else echo -n " & & DPOR &" fi f=$((f+1)) ./concuerror_mem --noprogress -f testsuite/suites/resources/src/manolis/*.erl \ -t rush_hour test_2workers_benchmark -p inf $t | \ grep "OUT" | sed 's/OUT//' done echo "\\hline" done ================================================ FILE: resources/bash_completion/concuerror ================================================ #!/bin/bash # bash_completion for concuerror # To use it, you can either: # - Link the file to e.g. `/etc/bash_completion.d` # - Run `. /path-to-the-file`, if you want to use it only for the current shell session # - Add `[ -s "/path-to-the-file" ] && \. "/path-to-the-file"` to your e.g. `.bashrc` file _concuerror() { # Keep this sorted alphabetically long_opts="--after_timeout --assertions_only --assume_racing --depth_bound --disable_sleep_sets --dpor --exclude_module --file --first_process_errors_only --graph --help --ignore_error --instant_delivery --interleaving_bound --keep_going --log_all --module --no_output --non_racing_system --observers --optimal --output --pa --print_depth --pz --quiet --scheduling --scheduling_bound --scheduling_bound_type --show_races --strict_scheduling --symbolic_names --test --timeout --treat_as_normal --use_receive_patterns --verbosity --version" # Keep this sorted alphabetically opts="-a -b -c -d -f -g -h -i -k -m -o -q -s -t -v -x" local cur prev if type _get_comp_words_by_ref &>/dev/null ; then _get_comp_words_by_ref cur prev else cur=$2 prev=$3 fi case $prev in --dpor) COMPREPLY=($(compgen -W "none optimal persistent source" -- ${cur})) ;; -f|--file|--pa|--pz) _filedir ;; -h|--help) help_args="all attributes progress" keywords="advanced basic bound console erlang errors experimental input output por visual" COMPREPLY=($(compgen -W "${help_args} ${keywords}" -- ${cur})) ;; -c|--scheduling_bound_type) COMPREPLY=($(compgen -W "bpor delay none ubpor" -- ${cur})) ;; --scheduling) COMPREPLY=($(compgen -W "newest oldest round_robin" -- ${cur})) ;; -a|--after_timeout|-i|--interleaving_bound|--timeout) COMPREPLY=($(compgen -W "infinity" -- ${cur})) ;; *) if [[ ${cur} == --* ]] ; then COMPREPLY=($(compgen -W "${long_opts}" -- ${cur})) elif [[ ${cur} == -* ]] ; then COMPREPLY=($(compgen -W "${opts}" -- ${cur})) else _filedir fi ;; esac } complete -F _concuerror concuerror ================================================ FILE: resources/erlang_questions/dets_bugs/alt_dets_bugs.erl ================================================ -module(alt_dets_bugs). -export([bug3/0, bug5/0, bug6/0]). -include_lib("eunit/include/eunit.hrl"). %% should always print [{0,0}], but sometimes prints [] bug3() -> dets:close(dets_table), sched:yield(), file:delete(dets_table), sched:yield(), dets:open_file(dets_table,[{type,bag}]), sched:yield(), spawn(fun() -> dets:open_file(dets_table,[{type,bag}]), sched:yield() end), spawn(fun() -> dets:insert(dets_table,[{0,0}]), sched:yield(), ?assertEqual([{0,0}], get_contents(dets_table)) end). %% should always print [{0,0}], but sometimes prints [] bug5() -> Self = self(), spawn(fun() -> [dets:close(dets_table) || _ <- "abcdefghijkl"], file:delete(dets_table), Parent = self(), {ok, _T} = dets:open_file(dets_table,[{type,bag}]), sched:yield(), spawn(fun() -> dets:open_file(dets_table,[{type,bag}]), sched:yield(), Parent ! done end), spawn(fun() -> dets:insert(dets_table,[{0,0}]), sched:yield(), ?assertEqual([{0,0}], get_contents(dets_table)), Parent ! done end), receive done -> receive done -> ok end end, Self ! ok end), receive ok -> ok end. bug6() -> dets:open_file(dets_table,[{type,bag}]), sched:yield(), dets:close(dets_table), sched:yield(), dets:open_file(dets_table,[{type,bag}]), sched:yield(), spawn(fun() -> dets:lookup(dets_table,0), sched:yield() end), spawn(fun() -> dets:insert(dets_table,{0,0}), sched:yield() end), dets:insert(dets_table,{0,0}), sched:yield(), ?assertEqual([{0,0}], match_object(dets_table)). get_contents(Name) -> Ret = dets:traverse(Name, fun(X)-> {continue,X} end), sched:yield(), Ret. match_object(Name) -> Ret = dets:match_object(Name,'_'), sched:yield(), Ret. ================================================ FILE: resources/erlang_questions/dets_bugs/dets_bugs.erl ================================================ -module(dets_bugs). -export([bug1/0, bug2/0, bug3/0, bug4/0, bug5/0, bug6/0]). %% should always print a boolean, but sometimes prints 'ok' bug1() -> {ok,T} = dets:open_file(dets_table,[{type,bag}]), spawn(fun()->dets:insert(T,[]) end), spawn(fun()->io:format("~p\n",[dets:insert_new(T,[])]) end). %% causes a bug message to appear bug2() -> file:delete(dets_table), T = dets:open_file(dets_table,[{type,set}]), spawn(fun() -> dets:delete(T,0) end), spawn(fun() -> dets:insert_new(T,{0,0}) end), ok. %% should always print [{0,0}], but sometimes prints [] bug3() -> dets:close(dets_table), file:delete(dets_table), {ok,_T} = dets:open_file(dets_table,[{type,bag}]), spawn(fun() -> dets:open_file(dets_table,[{type,bag}]) end), spawn(fun() -> dets:insert(dets_table,[{0,0}]), io:format("~p\n",[get_contents(dets_table)]) end). %% should always print [], but sometimes prints [{7,0}] bug4() -> dets:close(dets_table), file:delete(dets_table), {ok,_T} = dets:open_file(dets_table,[{type,bag}]), dets:insert(dets_table,{7,0}), spawn(fun() -> dets:open_file(dets_table,[{type,bag}]) end), spawn(fun() -> dets:delete(dets_table,7), io:format("~p\n",[get_contents(dets_table)]) end). %% should always print [{0,0}], but sometimes prints [] bug5() -> Self = self(), spawn(fun() -> [dets:close(dets_table) || _ <- "abcdefghijkl"], file:delete(dets_table), Parent = self(), {ok,_T} = dets:open_file(dets_table,[{type,bag}]), spawn(fun() -> dets:open_file(dets_table,[{type,bag}]), Parent ! done end), spawn(fun() -> dets:insert(dets_table,[{0,0}]), io:format("~p\n",[get_contents(dets_table)]), Parent ! done end), receive done -> receive done -> ok end end, Self ! ok end), receive ok -> ok end. bug6() -> dets:open_file(dets_table,[{type,bag}]), dets:close(dets_table), dets:open_file(dets_table,[{type,bag}]), spawn(fun() -> dets:lookup(dets_table,0) end), spawn(fun() -> dets:insert(dets_table,{0,0}) end), dets:insert(dets_table,{0,0}), dets:match_object(dets_table,'_'). get_contents(Name) -> dets:traverse(Name,fun(X)->{continue,X}end). ================================================ FILE: resources/flanagan.erl ================================================ %% -*- erlang-indent-level: 2 -*- -module(flanagan). -export([test/1, explore/2]). %% -define(DEBUG, true). %% -define(STEPWISE, true). -define(STATS, true). -define(STACK, true). -ifdef(DEBUG). -ifndef(STACK). -define(STACK, true). -endif. -endif. %%------------------------------------------------------------------------------ -record(state, { i, last, pstates, backtrack = [], done = [] }). -record(pstate, { commands, mailbox = 0 }). %%------------------------------------------------------------------------------ %% Sample Inputs %% M1 : INDEPENDENT SENDER RECEIVER PAIRS %% The following program has 5 processes: m1 (main), a & b (sender), c & d %% (receiver). Run with test(m1). %% main() -> %% Parent = self(), %% Rec1 = spawn(fun() -> receiver(Parent) end), %% Rec2 = spawn(fun() -> receiver(Parent) end), %% Snd1 = spawn(fun() -> sender(Rec1) end), %% Snd2 = spawn(fun() -> sender(Rec2) end), %% receive %% ok -> %% receive %% ok -> done %% end %% end. %% sender(Pid) -> %% Pid ! ok. %% receiver(Parent) -> %% receive %% ok -> Parent ! ok %% end. -ifdef(STATS). -define(stats_start, put(interleavings, 0)). -define(stats_report, io:format("Interleavings: ~p\n", [get(interleavings)])). -define(stats_inc, begin I = get(interleavings) + 1, put(interleavings, I) end). -else. -define(stats_start, ok). -define(stats_report, ok). -define(stats_inc, ok). -endif. -ifdef(STACK). -define(show_stack(Trace), io:format("~p\n",[get_stack(Trace)])). get_stack(Trace) -> get_stack(Trace, []). get_stack([], Acc) -> Acc; get_stack([#state{last = P}|Rest], Acc) -> get_stack(Rest, [P|Acc]). -else. -define(show_stack(_Trace), ok). -endif. test(M) -> InitPStates = dict:store(M, new_pstate(M), dict:new()), ?stats_start, Trace = init_trace(InitPStates), explore(Trace, new_clock_vector_dict()), ?stats_report. new_pstate(P) -> #pstate{commands = p(P)}. init_trace(InitPStates) -> [#state{i = 0, last = init, pstates = InitPStates}]. new_clock_vector_dict() -> dict:new(). p(m1) -> [{spawn, c}, {spawn, d}, {spawn, a}, {spawn, b}, rec, rec, exit]; p(a) -> [{send, c}, exit]; p(b) -> [{send, d}, exit]; p(c) -> [rec, {send, m1}, exit]; p(d) -> [rec, {send, m1}, exit]; p(m2) -> [{spawn, x}, {spawn, y}, {spawn, z}, {spawn, w}, {send, y}, exit]; p(x) -> [rec, {send,y}, exit]; p(y) -> [rec, rec, exit]; p(z) -> [rec, {send,x}, exit]; p(w) -> [{send,z}, exit]. %%------------------------------------------------------------------------------ do_command(#pstate{commands = [Command|Rest]} = PState) -> {Command, PState#pstate{commands = Rest}}. inc_mail(#pstate{mailbox = Mailbox} = PState) -> PState#pstate{mailbox = Mailbox + 1}. dec_mail(#pstate{mailbox = Mailbox} = PState) -> PState#pstate{mailbox = Mailbox - 1}. is_p_enabled(#pstate{commands = [Command|_], mailbox = Mailbox}) -> Value = case Command of exit -> true; {spawn, _Q} -> true; {send, _Q} -> true; rec -> Mailbox > 0 end, case Value of true -> {true, Command}; false -> false end; is_p_enabled(_) -> false. run(P, PStates) -> PState = dict:fetch(P, PStates), {Command, NextPState} = do_command(PState), case Command of {spawn, Q} -> NewPStates0 = dict:store(Q, #pstate{commands = p(Q)}, PStates), {Command, {ok, dict:store(P, NextPState, NewPStates0)}}; {send, Q} -> QState = dict:fetch(Q, PStates), NewPStates0 = dict:store(Q, inc_mail(QState), PStates), {Command, {ok, dict:store(P, NextPState, NewPStates0)}}; rec -> {Command, {ok, dict:store(P, dec_mail(NextPState), PStates)}}; exit -> {Command, {ok, dict:store(P, NextPState, PStates)}} end. %%------------------------------------------------------------------------------ %% --------------------------- %% Erlang dependency semantics %% --------------------------- dependent({ P1, _C1}, { P2, _C2}) when P1 =:= P2 -> true; dependent({_P1, {send, P2}}, {_P3, {send, P4}}) when P2 =:= P4 -> true; %dependent({_P1, {send, P2}}, { P3, rec}) when P2 =:= P3 -> true; %dependent({ P1, rec}, {_P2, {send, P3}}) when P1 =:= P3 -> true; dependent( _, _) -> false. dependent2({ P1, _C1}, { P2, _C2}) when P1 =:= P2 -> true; dependent2({_P1, {send, P2}}, {_P3, {send, P4}}) when P2 =:= P4 -> true; dependent2({_P1, {send, P2}}, { P3, rec}) when P2 =:= P3 -> true; dependent2({ P1, rec}, {_P2, {send, P3}}) when P1 =:= P3 -> true; dependent2( _, _) -> false. %%------------------------------------------------------------------------------ -define(breakpoint, case io:get_line("") of "q\n" -> throw(q); _ -> ok end). -ifdef(STEPWISE). -define(stepwise, ?breakpoint). -else. -define(stepwise, ok). -endif. -ifdef(DEBUG). -define(debug(A, B), io:format(A, B), ?stepwise). -define(debug_break(A, B), io:format(A, B), ?breakpoint). -else. -define(debug(_A, _B), ok). -define(debug_break(_A, _B), ok). -endif. -define(debug(A), ?debug(A, [])). explore(Trace, ClockMap) -> ?debug("Explore ~p:\n", [get_stack(Trace)]), UpdatedTrace = add_old_backtracks(Trace, ClockMap), FinalTrace = case pick_random_enabled(Trace) of none -> %% TODO: Report Check for deadlocks ?show_stack(Trace), ?stats_inc, UpdatedTrace; {ok, P} -> ?debug("Picking ~p for new step.\n", [P]), NewTrace = add_new_backtrack(P, UpdatedTrace), explore_backtracks(NewTrace, ClockMap) end, remove_last(FinalTrace). add_old_backtracks([#state{pstates=PStates}|_] = Trace, ClockMap) -> Nexts = get_all_nexts(PStates), ?debug("Backtrack points: Forall processes: ~p\n", [Nexts]), add_old_backtracks(Nexts, Trace, ClockMap). get_all_nexts(PStates) -> Fold = fun(P, #pstate{commands = Commands}, Acc) -> case Commands of [] -> Acc; [C|_] -> [{P,C}|Acc] end end, dict:fold(Fold, [], PStates). add_old_backtracks([], Trace, _ClockMap) -> ?debug("Done adding backtrack points\n"), Trace; add_old_backtracks([Command|Rest], Trace, ClockMap) -> ?debug(" ~p:\n",[Command]), NewTrace = add_old_backtracks_for_p(Command, Trace, [], ClockMap), add_old_backtracks(Rest, NewTrace, ClockMap). add_old_backtracks_for_p(_Cmd1, [], Acc, _ClockMap) -> lists:reverse(Acc); add_old_backtracks_for_p({ProcNext, _} = Next, [StateI|Rest], Acc, ClockMap) -> case StateI of #state{i = I, last = {ProcSi, _} = Si} -> Dependent = dependent(Next, Si), Clock = lookup_clock_value(ProcSi, lookup_clock(ProcNext, ClockMap)), ?debug(" ~p: ~p (Dep: ~p C: ~p)\n", [I, Si] ++ [Dependent] ++ [Clock]), case Dependent andalso I > Clock of false -> add_old_backtracks_for_p(Next, Rest, [StateI|Acc], ClockMap); true -> ?debug(" Dependent and i < Clock. Backtracking.\n"), [#state{pstates = PStates, backtrack = Backtrack} = Spi|Rest2] = Rest, ?debug(" Old backtrack: ~p\n", [Backtrack]), NewBacktrack = add_from_E(ProcNext, PStates, [StateI|Acc], ClockMap, Backtrack), ?debug(" New backtrack: ~p\n", [NewBacktrack]), lists:reverse(Acc, [StateI,Spi#state{backtrack = NewBacktrack}|Rest2]) end; #state{i = 0, last = init} -> add_old_backtracks_for_p(Next, Rest, [StateI|Acc], ClockMap) end. add_from_E(P, PStates, ForwardTrace, ClockMap, Backtrack) -> Enabled = all_enabled(PStates), case lists:member(P, Enabled) of true -> ?debug(" Enabled.\n"), ordsets:add_element(P, Backtrack); false -> ?debug(" Not Enabled.\n"), ClockVector = lookup_clock(P, ClockMap), case find_one_from_E(P, ClockVector, Enabled, ForwardTrace) of {true, Q} -> ?debug(" Q needs to happen: ~p\n", [Q]), ordsets:add_element(Q, Backtrack); false -> ?debug(" Adding all enabled: ~p\n", [Enabled]), ordsets:union(Backtrack, ordsets:from_list(Enabled)) end end. find_one_from_E(_P, _ClockVector, _Enabled, []) -> false; find_one_from_E(P, ClockVector, Enabled, [Sj|Rest]) -> #state{i = J, last = _Sj = {ProcSj, _}} = Sj, ?debug(" ~p: ~p\n", [J, ProcSj]), Satisfies = case lists:member(ProcSj, Enabled) of false -> ?debug(" Was not enabled\n"), false; true -> ClockValue = lookup_clock_value(ProcSj, ClockVector), ?debug(" Clock is: ~p\n", [ClockValue]), J =< ClockValue end, case Satisfies of true -> ?debug(" Found ~p\n", [ProcSj]), {true, ProcSj}; false -> find_one_from_E(P, ClockVector, Enabled, Rest) end. lookup_clock(PorS, ClockMap) -> case dict:find(PorS, ClockMap) of {ok, Clock} -> Clock; error -> dict:new() end. lookup_clock_value(P, CV) -> case dict:find(P, CV) of {ok, Value} -> Value; error -> 0 end. pick_random_enabled([#state{pstates = PStates}|_]) -> %% TODO: This is not really efficient case all_enabled(PStates) of [] -> none; [P|_] -> {ok, P} end. all_enabled(PStates) -> Fun = fun(P, PState, Acc) -> case is_p_enabled(PState) of false -> Acc; {true, _C} -> [P|Acc] end end, dict:fold(Fun, [], PStates). remove_last([_Last|Trace]) -> %% TODO: Replay trace till previous step. Trace. add_new_backtrack(P, [#state{} = State|Trace]) -> [State#state{backtrack = [P]}|Trace]. explore_backtracks(Trace, ClockMap) -> case pick_unexplored(Trace) of none -> ?debug("All backtracks explored.\n"), Trace; {ok, P, NewTrace} -> ?debug("Picking unexplored: ~p\n", [P]), case let_run(P, NewTrace) of {error, _Command, _Info} -> %% TODO: Report Something crashed %% TODO: Replay trace till previous step explore_backtracks(NewTrace, ClockMap); {ok, NewTrace2} -> NewClockMap = update_clock_map(NewTrace2, ClockMap), NewTrace3 = explore(NewTrace2, NewClockMap), explore_backtracks(NewTrace3, ClockMap) end end. pick_unexplored([State|Rest]) -> #state{backtrack = Backtrack, done = Done} = State, ?debug("Back:~p\nDone:~p\n",[Backtrack, Done]), case find_unique(Backtrack, Done) of {ok, P} -> {ok, P, [State#state{done = ordsets:add_element(P, Done)}|Rest]}; none -> none end. find_unique([], _Set2) -> none; find_unique([P|Set1], Set2) -> case ordsets:is_element(P, Set2) of true -> find_unique(Set1, Set2); false -> {ok, P} end. let_run(P, [#state{i = N, pstates = PStates}|_] = Trace) -> {Command, Result} = run(P, PStates), case Result of {ok, NewPStates} -> NewState = #state{i = N+1, last = {P, Command}, pstates = NewPStates}, NewTrace = [NewState|Trace], {ok, NewTrace}; {error, Info} -> {error, Command, Info} end. update_clock_map([#state{i = N, last = {P, _C} = Command}|Trace], ClockMap) -> CV = max_dependent(Command, Trace, ClockMap), CV2 = dict:store(P, N, CV), Ca = dict:store(P, CV2, ClockMap), dict:store(N, CV2, Ca). max_dependent(Command, Trace, ClockMap) -> max_dependent(Command, Trace, ClockMap, dict:new()). max_dependent(_Cmd, [], _ClockMap, Acc) -> Acc; max_dependent(Cmd1, [#state{i = N, last = Cmd2}|Trace], ClockMap, Acc) -> case dependent2(Cmd1, Cmd2) of true -> CI = lookup_clock(N, ClockMap), Merger = fun(_Key, V1, V2) -> max(V1, V2) end, NewAcc = dict:merge(Merger, CI, Acc), max_dependent(Cmd1, Trace, ClockMap, NewAcc); false -> max_dependent(Cmd1, Trace, ClockMap, Acc) end. ================================================ FILE: resources/how-to-release.md ================================================ # How to make a release Here is how to prepare a new release of Concuerror ## Decide release number Follow semantic versioning (link in CHANGELOG.md) `RELEASE=0.42` ## Fix and commit release number in CHANGELOG.md Format the UNRELEASED section in CHANGELOG.md `## [0.42](https://github.com/parapluu/Concuerror/releases/tag/0.42) - 2042-11-20` Then commit: `git commit -m "Update CHANGELOG for release ${RELEASE}"` The UNRELEASED section will be added back as the last step. ## Make the release tag `git tag -a ${RELEASE} -m "Release ${RELEASE}" -s` ## Ensure you are logged in to Hex `rebar3 hex user whoami` If no info shown: `rebar3 hex user auth` ## Push package to Hex `rebar3 hex publish` ## Push tag to repository `git push parapluu ${RELEASE}` ## Create release from tag Copy the most recent section of the CHANGELOG as description and leave title empty ## Add a new UNRELEASED section in CHANGELOG.md ``` ## [Unreleased](https://github.com/parapluu/Concuerror/tree/master) ### Added ### Removed ### Changed ### Fixed ``` ================================================ FILE: resources/old_include/gen.hrl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : General header file %%%---------------------------------------------------------------------- %%%---------------------------------------------------------------------- %%% Definitions %%%---------------------------------------------------------------------- %% Application name (atom and string). -define(APP_ATOM, list_to_atom(?APP_STRING)). %% Registered process names. -define(RP_GUI, '_._gui'). -define(RP_GUI_ANALYSIS, '_._gui_analysis'). -define(RP_SCHED, '_._sched'). -define(RP_SCHED_SEND, ?RP_SCHED). -define(RP_LID, '_._lid'). -define(RP_LID_SEND, ?RP_LID). %% Named ets table names. -define(NT_BLOCKED, '_._blocked'). -define(NT_ERROR, '_._error'). -define(NT_LID, '_._lid'). -define(NT_PID, '_._pid'). -define(NT_REF, '_._ref'). -define(NT_STATE1, '_._state1'). -define(NT_STATE2, '_._state2'). -define(NT_STATELEN, '_._state_len'). -define(NT_USED, '_._used'). -define(NT_TIMER, '_._timer'). -define(NT_INSTR_MODS, '_._instr_mods'). -define(NT_INSTR_BIFS, '_._instr_bifs'). -define(NT_INSTR_IGNORED, '_._instr_ignored'). -define(NT_INSTR, '_._instr_table'). -define(NT_OPTIONS, '_._conc_options'). %% Module containing replacement functions. -define(REP_MOD, concuerror_rep). %% Instrumented message atom. -define(INSTR_MSG, '_._instr_msg'). %% Instrumented modules prefix. -define(INSTR_PREFIX, "conc__"). %% Set-like data structure used in sched, lid and error modules. -define(SETS, ordsets). -define(SET_TYPE(X), [X]). %% XXX: bad -- ordsets does not export the type! %% Default options -define(DEFAULT_PREB, 2). -define(DEFAULT_INCLUDE, []). -define(DEFAULT_DEFINE, []). -define(DEFAULT_VERBOSITY, 0). %% Default export file. -define(EXPORT_EXT, ".txt"). -define(EXPORT_FILE, "results" ++ ?EXPORT_EXT). %% Internal error return code. -define(RET_INTERNAL_ERROR, 1). %% Host - Node names. -define(NODE, atom_to_list(node())). -define(HOST, lists:dropwhile(fun(E) -> E /= $@ end, ?NODE)). %% 'About' message -define(INFO_MSG, " Concuerror A systematic testing tool for concurrent Erlang programs. Version " ?VSN " "). %% Debug macros. -ifdef(COND_DEBUG). -define(debug_start, put(debug, true)). -define(debug_stop, erase(debug)). -define(debug(S_, L_), begin case get(debug) of true -> io:format(S_, L_); _ -> ok end end). -else. -define(debug_start, ok). -define(debug_stop, ok). -endif. -ifndef(COND_DEBUG). -ifdef(DEBUG). -define(debug(S_, L_), io:format(S_, L_)). -else. -define(debug(S_, L_), ok). -endif. %DEBUG -endif. %COND_DEBUG -define(debug(S_), ?debug(S_,[])). -define(DEBUG_DEPTH, 12). ================================================ FILE: resources/old_include/gui.hrl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : GUI header file %%%---------------------------------------------------------------------- %%%---------------------------------------------------------------------- %%% Definitions %%%---------------------------------------------------------------------- %% Initial frame size. -define(FRAME_SIZE_INIT, {1024, 768}). %% Menu specification: %% [{MenuName1, [MenuItem11, MenuItem12, ...]}, ...] -define(MENU_SPEC, [{"&File", [[{id, ?SAVEAS}, {text, "&Save As...\tCtrl-S"}, {help, "Save analysis information to file."}, {label, ?SAVEAS_MENU_ITEM}], [{id, ?wxID_SEPARATOR}, {kind, ?wxITEM_SEPARATOR}], [{id, ?EXIT}, {help, "Quit Concuerror."}]]}, {"&Edit", [[{id, ?PREFS}, {text, "&Preferences...\tCtrl-P"}, {help, "Edit Concuerror preferences."}]]}, {"&Module", [[{id, ?ADD}, {text, "&Add...\tCtrl-A"}, {help, "Add an existing erlang module."}], [{id, ?REMOVE}, {text, "&Remove\tCtrl-R"}, {help, "Remove selected module."}], [{id, ?wxID_SEPARATOR}, {kind, ?wxITEM_SEPARATOR}], [{id, ?CLEAR}, {text, "&Clear\tCtrl-C"}, {help, "Clear module list."}], [{id, ?REFRESH}, {text, "Re&fresh\tF5"}, {help, "Refresh selected module (reload file from disk)."}]]}, {"&Run", [[{id, ?ANALYZE}, {text, "Ana&lyze\tCtrl-L"}, {help, "Analyze selected function."}, {label, ?ANALYZE_MENU_ITEM}], [{id, ?STOP}, {text, "Sto&p\tCtrl-P"}, {help, "Stop analysis of selected function."}, {label, ?STOP_MENU_ITEM}]]}, {"&View", [[{id, ?wxID_ANY}, {text, "Source viewer color theme"}, {help, "Select a color theme for the source viewer."}, {sub, [[{id, ?THEME_LIGHT}, {text, "Light theme"}, {kind, ?wxITEM_RADIO}], [{id, ?THEME_DARK}, {text, "Dark theme"}, {kind, ?wxITEM_RADIO}]]} ]]}, {"&Help", [[{id, ?ABOUT}, {text, "&About"}, {help, "Show project info."}]]} ]). %% GUI component definitions -define(ABOUT, ?wxID_ABOUT). -define(ADD, ?wxID_ADD). -define(CLEAR, ?wxID_CLEAR). -define(REMOVE, ?wxID_REMOVE). -define(SEPARATOR, ?wxID_SEPARATOR). -define(EXIT, ?wxID_EXIT). -define(SAVEAS, ?wxID_SAVEAS). -define(REFRESH, ?wxID_REFRESH). -define(ANAL_STOP_SIZER, 500). -define(ANALYZE, 501). -define(ANALYZE_GAUGE, 502). -define(ANALYZE_MENU_ITEM, 503). -define(ERROR_ILEAVE_SPLITTER, 504). -define(ERROR_LIST, 505). -define(ERROR_TEXT, 506). -define(SAVEAS_MENU_ITEM, 508). -define(FRAME, 509). -define(FUNCTION_LIST, 510). -define(GRAPH_PANEL, 511). -define(ILEAVE_LIST, 512). -define(LOG_NOTEBOOK, 515). -define(LOG_TEXT, 516). -define(MOD_FUN_SPLITTER, 517). -define(MODULE_LIST, 518). -define(NOTEBOOK, 519). -define(NOTEBOOK_SPLITTER, 520). -define(PREB_BOUND_SPIN, 521). -define(PREB_ENABLED_CBOX, 522). -define(PREFS, 523). -define(PROC_TEXT, 524). -define(SCR_GRAPH, 526). -define(SOURCE_TEXT, 527). -define(STATIC_BMP, 528). -define(STATUS_BAR, 529). -define(STOP, 530). -define(STOP_GAUGE, 531). -define(STOP_MENU_ITEM, 532). -define(THEME_DARK, 533). -define(THEME_LIGHT, 534). -define(TOP_SPLITTER, 535). %% Splitter init-sizes -define(SPLITTER_INIT, [{?TOP_SPLITTER, 300}, {?MOD_FUN_SPLITTER, 300}, {?NOTEBOOK_SPLITTER, 530}, {?ERROR_ILEAVE_SPLITTER, 250}]). %% Splitter min-sizes -define(MIN_TOP, 250). -define(MIN_MOD_FUN, 50). -define(MIN_NOTEBOOK, 50). -define(MIN_ERROR_ILEAVE, 50). %% Splitter gravities -define(GRAV_TOP, 0.0). -define(GRAV_MOD_FUN, 0.3). -define(GRAV_NOTEBOOK, 0.8). -define(GRAV_ERROR_ILEAVE, 0.2). %% Preferences related definitions -define(PREF_PREB_ENABLED, 560). -define(PREF_PREB_BOUND, 561). -define(PREF_INCLUDE, 562). -define(PREF_DEFINE, 563). %% Analysis Result definitions -define(ANALYSIS_RET, 564). %% Other definitions -define(FILE_PATH, 565). %% Erlang keywords -define(KEYWORDS, "after begin case try cond catch andalso orelse end fun " "if let of query receive when bnot not div rem band and " "bor bxor bsl bsr or xor"). %% Source viewer styles -define(SOURCE_BG_DARK, {63, 63, 63}). -define(SOURCE_FG_DARK, {220, 220, 204}). %% -define(SOURCE_FG_DARK, {204, 220, 220}). -define(SOURCE_STYLES_DARK, [{?wxSTC_ERLANG_ATOM, ?SOURCE_FG_DARK, normal}, {?wxSTC_ERLANG_CHARACTER, {204, 147, 147}, normal}, {?wxSTC_ERLANG_COMMENT, {127, 159, 127}, normal}, {?wxSTC_ERLANG_DEFAULT, ?SOURCE_FG_DARK, normal}, {?wxSTC_ERLANG_FUNCTION_NAME, ?SOURCE_FG_DARK, normal}, {?wxSTC_ERLANG_KEYWORD, {240, 223, 175}, bold}, {?wxSTC_ERLANG_MACRO, {255, 207, 175}, normal}, {?wxSTC_ERLANG_NODE_NAME, ?SOURCE_FG_DARK, normal}, {?wxSTC_ERLANG_NUMBER, {140, 208, 211}, normal}, {?wxSTC_ERLANG_OPERATOR, ?SOURCE_FG_DARK, normal}, {?wxSTC_ERLANG_RECORD, {232, 147, 147}, normal}, {?wxSTC_ERLANG_SEPARATOR, ?SOURCE_FG_DARK, normal}, {?wxSTC_ERLANG_STRING, {204, 147, 147}, normal}, {?wxSTC_ERLANG_VARIABLE, {239, 239, 175}, bold}, {?wxSTC_ERLANG_UNKNOWN, {255, 0, 0}, normal}]). -define(SOURCE_BG_LIGHT, {255, 255, 255}). -define(SOURCE_FG_LIGHT, {30, 30, 30}). -define(SOURCE_STYLES_LIGHT, [{?wxSTC_ERLANG_ATOM, ?SOURCE_FG_LIGHT, normal}, {?wxSTC_ERLANG_CHARACTER, {120, 190, 120}, normal}, {?wxSTC_ERLANG_COMMENT, {20, 140, 20}, normal}, {?wxSTC_ERLANG_DEFAULT, ?SOURCE_FG_LIGHT, normal}, {?wxSTC_ERLANG_FUNCTION_NAME, ?SOURCE_FG_LIGHT, normal}, {?wxSTC_ERLANG_KEYWORD, {140, 40, 170}, bold}, {?wxSTC_ERLANG_MACRO, {180, 40, 40}, normal}, {?wxSTC_ERLANG_NODE_NAME, ?SOURCE_FG_LIGHT, normal}, {?wxSTC_ERLANG_NUMBER, ?SOURCE_FG_LIGHT, normal}, {?wxSTC_ERLANG_OPERATOR, {70, 70, 70}, normal}, {?wxSTC_ERLANG_RECORD, {150, 140, 40}, normal}, {?wxSTC_ERLANG_SEPARATOR, ?SOURCE_FG_LIGHT, normal}, {?wxSTC_ERLANG_STRING, {50, 50, 200}, normal}, {?wxSTC_ERLANG_VARIABLE, {20, 120, 140}, bold}, {?wxSTC_ERLANG_UNKNOWN, {255, 0, 0}, normal}]). ================================================ FILE: resources/old_include/instr.hrl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2013, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Ilias Tsitsimpis %%% Description : Instrumentation header file %%%---------------------------------------------------------------------- %%%---------------------------------------------------------------------- %%% Definitions %%%---------------------------------------------------------------------- %% List of attributes that should be stripped. -define(ATTR_STRIP, [type, spec, opaque, export_type, import_type, callback]). %% Instrumented auto-imported functions of 'erlang' module. -define(INSTR_ERL_FUN, [{demonitor, 1}, {demonitor, 2}, {exit, 2}, {halt, 0}, {halt, 1}, {is_process_alive, 1}, {link, 1}, {monitor, 2}, {process_flag, 2}, {register, 2}, {spawn, 1}, {spawn, 3}, {spawn_link, 1}, {spawn_link, 3}, {spawn_monitor, 1}, {spawn_monitor, 3}, {spawn_opt, 2}, {spawn_opt, 4}, {unlink, 1}, {unregister, 1}, {port_command, 2}, {port_command, 3}, {port_control, 3}, {apply, 3}, {whereis, 1}]). %% Instrumented functions called as erlang:FUNCTION. -define(INSTR_ERL_MOD_FUN, [{erlang, send, 2}, {erlang, send, 3}, {erlang, send_after, 3}, {erlang, start_timer, 3}] ++ [{erlang, F, A} || {F, A} <- ?INSTR_ERL_FUN]). %% Instrumented functions from ets module. -define(INSTR_ETS_FUN, [{ets, insert_new, 2}, {ets, lookup, 2}, {ets, select_delete, 2}, {ets, insert, 2}, {ets, delete, 1}, {ets, delete, 2}, {ets, filter, 3}, {ets, match, 2}, {ets, match, 3}, {ets, match_object, 2}, {ets, match_object, 3}, {ets, match_delete, 2}, {ets, new, 2}, {ets, info, 1}, {ets, info, 2}, {ets, foldl, 3}]). %% Instrumented mod:fun. -define(INSTR_MOD_FUN, ?INSTR_ERL_MOD_FUN ++ ?INSTR_ETS_FUN). %% Key in ?NT_INSTR to use for temp directory. -define(INSTR_TEMP_DIR, '_._instr_temp_dir'). %% Key in ?NT_INSTR to use for `fail-uninstrumented' flag. -define(FAIL_BB, '_._instr_fail_bb'). %% ------------------------------------------------------------------- %% BIFs (taken from file `otp/erts/emulator/beam/bif.tab' %% We don't care about `erlang' and `ets' BIFS as %% we don't rename them anyway. -define(PREDEF_BIFS, [% Bifs in `math' module {math, cos, 1}, {math, cosh, 1}, {math, sin, 1}, {math, sinh, 1}, {math, tan, 1}, {math, tanh, 1}, {math, acos, 1}, {math, acosh, 1}, {math, asin, 1}, {math, asinh, 1}, {math, atan, 1}, {math, atanh, 1}, {math, erf, 1}, {math, erfc, 1}, {math, exp, 1}, {math, log, 1}, {math, log10, 1}, {math, sqrt, 1}, {math, atan2, 2}, {math, pow, 2}, % Bifs in `os' module {os, putenv, 2}, {os, getenv, 0}, {os, getenv, 1}, {os, getpid, 0}, {os, timestamp, 0}, % Bifs in the `re' module {re, compile, 1}, {re, compile, 2}, {re, run, 2}, {re, run, 3}, % Bifs in the `lists' module {lists, member, 2}, {lists, reverse, 2}, {lists, keymember, 3}, {lists, keysearch, 3}, {lists, keyfind, 3}, % Bifs for `debugging' {erts_debug, disassemble, 1}, {erts_debug, breakpoint, 2}, {erts_debug, same, 2}, {erts_debug, flat_size, 1}, {erts_debug, get_internal_state, 1}, {erts_debug, set_internal_state, 2}, {erts_debug, display, 1}, {erts_debug, dist_ext_to_term, 2}, {erts_debug, instructions, 0}, % `Monitor testing' bif's {erts_debug, dump_monitors, 1}, {erts_debug, dump_links, 1}, % `Lock counter' bif's {erts_debug, lock_counters, 1}, % New Bifs in `R8' {code, get_chunk, 2}, {code, module_md5, 1}, {code, make_stub_module, 3}, {code, is_module_native, 1}, % New Bifs in `R10B' {string, to_integer, 1}, {string, to_float, 1}, % New Bifs in `R12B-5' {unicode, characters_to_binary, 2}, {unicode, characters_to_list, 2}, {unicode, bin_is_7bit, 1}, % New Bifs in `R13B-1' {net_kernel, dflag_unicode_io, 1}, % The `binary' match bifs {binary, compile_pattern, 1}, {binary, match, 2}, {binary, match, 3}, {binary, matches, 2}, {binary, matches, 3}, {binary, longest_common_prefix, 1}, {binary, longest_common_suffix, 1}, {binary, first, 1}, {binary, last, 1}, {binary, at, 2}, {binary, part, 2}, {binary, part, 3}, {binary, bin_to_list, 1}, {binary, bin_to_list, 2}, {binary, bin_to_list, 3}, {binary, list_to_bin, 1}, {binary, copy, 1}, {binary, copy, 2}, {binary, referenced_byte_size, 1}, {binary, encode_unsigned, 1}, {binary, encode_unsigned, 2}, {binary, decode_unsigned, 1}, {binary, decode_unsigned, 2}, % Helpers for unicode filenames {file, native_name_encoding, 0} ]). ================================================ FILE: resources/old_source/LICENSE ================================================ Copyright (c) 2011, Alkis Gotovos , Maria Christakis and Kostis Sagonas . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: resources/old_source/concuerror.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2012, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Ilias Tsitsimpis %%% Description : Command Line Interface %%%---------------------------------------------------------------------- -module(concuerror). %% UI exports. -export([gui/1, cli/0, analyze/1, export/2, stop/0, stop/1]). %% Log server callback exports. -export([init/1, terminate/2, handle_event/2]). -export_type([options/0]). -include("gen.hrl"). %%%---------------------------------------------------------------------- %%% Debug %%%---------------------------------------------------------------------- %%-define(TTY, true). -ifdef(TTY). -define(tty(), ok). -else. -define(tty(), error_logger:tty(false)). -endif. %%%---------------------------------------------------------------------- %%% Types %%%---------------------------------------------------------------------- -type analysis_ret() :: concuerror_sched:analysis_ret() | {'error', 'arguments', string()}. %% Command line options -type option() :: {'target', concuerror_sched:analysis_target()} | {'files', [file:filename()]} | {'output', file:filename()} | {'include', [file:name()]} | {'define', concuerror_instr:macros()} | {'dpor', 'full' | 'source' | 'classic'} | {'noprogress'} | {'quiet'} | {'preb', concuerror_sched:bound()} | {'gui'} | {'verbose', non_neg_integer()} | {'keep_temp'} | {'show_output'} | {'fail_uninstrumented'} | {'wait_messages'} | {'ignore_timeout', pos_integer()} | {'ignore', [module()]} | {'app_controller'} | {'help'}. -type options() :: [option()]. %%%---------------------------------------------------------------------- %%% UI functions %%%---------------------------------------------------------------------- %% @spec stop() -> ok %% @doc: Stop the Concuerror analysis -spec stop() -> ok. stop() -> try ?RP_SCHED ! stop_analysis catch error:badarg -> init:stop() end, ok. %% @spec stop_node([string(),...]) -> ok %% @doc: Stop the Concuerror analysis -spec stop([string(),...]) -> ok. stop([Name]) -> %% Disable error logging messages. ?tty(), Node = list_to_atom(Name ++ ?HOST), case rpc:call(Node, ?MODULE, stop, []) of {badrpc, _Reason} -> %% Retry stop([Name]); _Res -> ok end. %% @spec gui(options()) -> 'true' %% @doc: Start the Concuerror GUI. -spec gui(options()) -> 'true'. gui(Options) -> %% Disable error logging messages. ?tty(), concuerror_gui:start(Options). %% @spec cli() -> 'true' %% @doc: Parse the command line arguments and start Concuerror. -spec cli() -> 'true'. cli() -> %% Disable error logging messages. ?tty(), %% First get the command line options Args = init:get_arguments(), %% There should be no plain_arguments case init:get_plain_arguments() of [PlArg|_] -> io:format("~s: unrecognised argument: ~s\n", [?APP_STRING, PlArg]); [] -> case parse(Args, [{'verbose', 0}]) of {'error', 'arguments', Msg1} -> io:format("~s: ~s\n", [?APP_STRING, Msg1]); Opts -> cliAux(Opts) end end. cliAux(Options) -> %% Initialize timer table. concuerror_util:timer_init(), %% Start the log manager. _ = concuerror_log:start(), %% Create table to save options ?NT_OPTIONS = ets:new(?NT_OPTIONS, [named_table, public, set]), ets:insert(?NT_OPTIONS, Options), %% Handle options case lists:keyfind('gui', 1, Options) of {'gui'} -> gui(Options); false -> %% Attach the event handler below. case lists:keyfind('quiet', 1, Options) of false -> _ = concuerror_log:attach(?MODULE, Options), ok; {'quiet'} -> ok end, %% Run analysis case analyzeAux(Options) of {'error', 'arguments', Msg1} -> io:format("~s: ~s\n", [?APP_STRING, Msg1]); Result -> %% Set output file Output = case lists:keyfind(output, 1, Options) of {output, O} -> O; false -> ?EXPORT_FILE end, concuerror_log:log(0, "\nWriting output to file ~s... ", [Output]), case export(Result, Output) of {'error', Msg2} -> concuerror_log:log(0, "~s\n", [file:format_error(Msg2)]); ok -> concuerror_log:log(0, "done\n") end end end, %% Remove options table ets:delete(?NT_OPTIONS), %% Stop event handler concuerror_log:stop(), %% Destroy timer table. concuerror_util:timer_destroy(), 'true'. %% Parse command line arguments parse([], Options) -> Options; parse([{Opt, Param} | Args], Options) -> case atom_to_list(Opt) of T when T=:="t"; T=:="-target" -> case Param of [Module,Func|Pars] -> AtomModule = erlang:list_to_atom(Module), AtomFunc = erlang:list_to_atom(Func), case validateTerms(Pars, []) of {'error',_,_}=Error -> Error; AtomParams -> Target = {AtomModule, AtomFunc, AtomParams}, NewOptions = lists:keystore(target, 1, Options, {target, Target}), parse(Args, NewOptions) end; %% Run Eunit tests for specific module [Module] -> AtomModule = ?REP_MOD, AtomFunc = 'rep_eunit', Pars = [Module], case validateTerms(Pars, []) of {'error',_,_}=Error -> Error; AtomParams -> Target = {AtomModule, AtomFunc, AtomParams}, NewOptions = lists:keystore(target, 1, Options, {target, Target}), NewArgs = [{'D',["TEST"]} | Args], parse(NewArgs, NewOptions) end; _Other -> wrongArgument('number', Opt) end; F when F=:="f"; F=:="-files" -> case Param of [] -> wrongArgument('number', Opt); Files -> NewOptions = keyAppend(files, 1, Options, Files), parse(Args, NewOptions) end; O when O=:="o"; O=:="-output" -> case Param of [File] -> NewOptions = lists:keystore(output, 1, Options, {output, File}), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; "I" -> case Param of [Par] -> NewOptions = keyAppend('include', 1, Options, [Par]), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; "I" ++ Include -> case Param of [] -> parse([{'I', [Include]} | Args], Options); _Other -> wrongArgument('number', Opt) end; "D" -> case Param of [Par] -> case string:tokens(Par, "=") of [Name, Term] -> AtomName = erlang:list_to_atom(Name), case validateTerms([Term], []) of {'error',_,_}=Error -> Error; [AtomTerm] -> NewOptions = keyAppend('define', 1, Options, [{AtomName, AtomTerm}]), parse(Args, NewOptions) end; [Name] -> AtomName = erlang:list_to_atom(Name), case validateTerms(["true"], []) of {'error',_,_}=Error -> Error; [AtomTerm] -> NewOptions = keyAppend('define', 1, Options, [{AtomName, AtomTerm}]), parse(Args, NewOptions) end; _Other -> wrongArgument('type', Opt) end; _Other -> wrongArgument('number', Opt) end; "D" ++ Define -> case Param of [] -> parse([{'D', [Define]} | Args], Options); _Other -> wrongArgument('number', Opt) end; "-noprogress" -> case Param of [] -> NewOptions = lists:keystore(noprogress, 1, Options, {noprogress}), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; Q when Q=:="q"; Q=:="-quiet" -> case Param of [] -> NewOptions = lists:keystore(quiet, 1, Options, {quiet}), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; P when P=:="p"; P=:="-preb" -> case Param of [Preb] -> case string:to_integer(Preb) of {I, []} when I>=0 -> NewOptions = lists:keystore(preb, 1, Options, {preb, I}), parse(Args, NewOptions); _ when Preb=:="inf"; Preb=:="off" -> NewOptions = lists:keystore(preb, 1, Options, {preb, inf}), parse(Args, NewOptions); _Other -> wrongArgument('type', Opt) end; _Other -> wrongArgument('number', Opt) end; "-gui" -> case Param of [] -> NewOptions = lists:keystore(gui, 1, Options, {gui}), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; "v" -> case Param of [] -> NewOptions = keyIncrease('verbose', 1, Options), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; "-keep-tmp-files" -> case Param of [] -> NewOptions = lists:keystore('keep_temp', 1, Options, {'keep_temp'}), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; "-fail-uninstrumented" -> case Param of [] -> NewOptions = lists:keystore('fail_uninstrumented', 1, Options, {'fail_uninstrumented'}), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; "-app-controller" -> case Param of [] -> NewOptions = lists:keystore('app_controller', 1, Options, {'app_controller'}), parse(Args, NewOptions); _Other -> wrongArgument('number', Opt) end; "-ignore" -> case Param of [] -> wrongArgument('number', Opt); Ignores -> AtomIgns = [list_to_atom(Ign) || Ign <- Ignores], NewOptions = keyAppend('ignore', 1, Options, AtomIgns), parse(Args, NewOptions) end; "-show-output" -> case Param of [] -> NewOptions = lists:keystore('show_output', 1, Options, {'show_output'}), %% Disable progress parse([{'-noprogress', []} | Args], NewOptions); _Other -> wrongArgument('number', Opt) end; "-wait-messages" -> case Param of [] -> NewOptions = lists:keystore('wait_messages', 1, Options, {'wait_messages'}), parse(Args, NewOptions); _Ohter -> wrongArgument('number', Opt) end; T when T =:= "T"; T =:= "-ignore-timeout" -> case Param of [Timeout] -> case string:to_integer(Timeout) of {Timeout_Int, []} when Timeout_Int > 0 -> NewOptions = lists:keystore('ignore_timeout', 1, Options, {'ignore_timeout', Timeout_Int}), parse(Args, NewOptions); _Other -> wrongArgument('type', Opt) end; _Other -> wrongArgument('number', Opt) end; "-help" -> help(), erlang:halt(); DPOR when DPOR =:= "-dpor"; DPOR =:= "-dpor_optimal"; DPOR =:= "-dpor_source"; DPOR =:= "-dpor_classic" -> Flavor = case DPOR of "-dpor" -> full; "-dpor_optimal" -> full; "-dpor_source" -> source; "-dpor_classic" -> classic end, case lists:keysearch(dpor, 1, Options) of false -> NewOptions = [{dpor, Flavor}|Options], parse(Args, NewOptions); _ -> Msg = "multiple DPOR algorithms specified", {'error', 'arguments', Msg} end; EF when EF=:="root"; EF=:="progname"; EF=:="home"; EF=:="smp"; EF=:="noshell"; EF=:="noinput"; EF=:="sname"; EF=:="pa"; EF=:="cookie" -> %% erl flag (ignore it) parse(Args, Options); Other -> Msg = io_lib:format("unrecognised concuerror flag: -~s", [Other]), {'error', 'arguments', Msg} end. %% Validate user provided terms. validateTerms([], Terms) -> lists:reverse(Terms); validateTerms([String|Strings], Terms) -> case erl_scan:string(String ++ ".") of {ok, T, _} -> case erl_parse:parse_term(T) of {ok, Term} -> validateTerms(Strings, [Term|Terms]); {error, {_, _, Info}} -> Msg1 = io_lib:format("arg ~s - ~s", [String, Info]), {'error', 'arguments', Msg1} end; {error, {_, _, Info}, _} -> Msg2 = io_lib:format("info ~s", [Info]), {'error', 'arguments', Msg2} end. keyAppend(Key, Pos, TupleList, Value) -> case lists:keytake(Key, Pos, TupleList) of {value, {Key, PrevValue}, TupleList2} -> [{Key, Value ++ PrevValue} | TupleList2]; false -> [{Key, Value} | TupleList] end. keyIncrease(Key, Pos, TupleList) -> case lists:keytake(Key, Pos, TupleList) of {value, {Key, PrevValue}, TupleList2} -> [{Key, PrevValue+1} | TupleList2]; false -> [{Key, ?DEFAULT_VERBOSITY + 1} | TupleList] end. wrongArgument('number', Option) -> Msg = io_lib:format("wrong number of arguments for option -~s", [Option]), {'error', 'arguments', Msg}; wrongArgument('type', Option) -> Msg = io_lib:format("wrong type of argument for option -~s", [Option]), {'error', 'arguments', Msg}. help() -> io:format( ?INFO_MSG "\n" "usage: concuerror []\n" "Arguments:\n" " -t|--target module Run eunit tests for this module\n" " -t|--target module function [args]\n" " Specify the function to execute\n" " -f|--files modules Specify the files (modules) to instrument\n" " -o|--output file Specify the output file (default results.txt)\n" " -p|--preb number|inf Set preemption bound (default is 2)\n" " -I include_dir Pass the include_dir to concuerror\n" " -D name=value Define a macro\n" " --noprogress Disable progress bar\n" " -q|--quiet Disable logging (implies --noprogress)\n" " -v Verbose [use twice to be more verbose]\n" " --keep-tmp-files Retain all intermediate temporary files\n" " --fail-uninstrumented Fail if there are uninstrumented modules\n" " --ignore modules It is OK for these modules to be uninstrumented\n" " --show-output Allow program under test to print to stdout\n" " --wait-messages Wait for uninstrumented messages to arrive\n" " --app-controller Start an (instrumented) application controller\n" " -T|--ignore-timeout bound\n" " Treat big after Timeouts as infinity timeouts\n" " --gui Run concuerror with a graphical interface\n" " --help Show this help message\n" "\n" " DPOR algorithms:\n" " --dpor|--dpor_optimal Enables the optimal DPOR algorithm\n" " --dpor_classic Enables the classic DPOR algorithm\n" " --dpor_source Enables the DPOR algorithm based on source sets\n" "\n" "Examples:\n" " concuerror -DVSN=\\\"1.0\\\" --target foo bar arg1 arg2 " "--files \"foo.erl\" -o out.txt\n" " concuerror --gui -I./include --files foo.erl --preb inf\n\n"). %%%---------------------------------------------------------------------- %%% Analyze Commnad %%%---------------------------------------------------------------------- %% @spec analyze(options()) -> 'true' %% @doc: Run Concuerror analysis with the given options. -spec analyze(options()) -> analysis_ret(). analyze(Options) -> %% Disable error logging messages. ?tty(), %% Start the log manager. _ = concuerror_log:start(), Res = analyzeAux(Options), %% Stop event handler concuerror_log:stop(), Res. analyzeAux(Options) -> %% Get target Result = case lists:keyfind(target, 1, Options) of false -> Msg1 = "no target specified", {'error', 'arguments', Msg1}; {target, Target} -> %% Get input files case lists:keyfind(files, 1, Options) of false -> Msg2 = "no input files specified", {'error', 'arguments', Msg2}; {files, Files} -> %% Start the analysis concuerror_sched:analyze(Target, Files, Options) end end, %% Return result Result. %%%---------------------------------------------------------------------- %%% Export Analysis results into a file %%%---------------------------------------------------------------------- %% @spec export(concuerror_sched:analysis_ret(), file:filename()) -> %% 'ok' | {'error', file:posix() | badarg | system_limit} %% @doc: Export the analysis results into a text file. -spec export(concuerror_sched:analysis_ret(), file:filename()) -> 'ok' | {'error', file:posix() | badarg | system_limit | terminated}. export(Results, File) -> case file:open(File, ['write']) of {ok, IoDevice} -> case exportAux(Results, IoDevice) of ok -> file:close(IoDevice); Error -> Error end; Error -> Error end. exportAux({'ok', {_Target, RunCount, SBlocked}}, IoDevice) -> Msg = io_lib:format("Checked ~w interleaving(s). No errors found.\n", [RunCount]), SBMsg = case SBlocked of 0 -> ""; _ -> io_lib:format(" Encountered ~w sleep-set blocked trace(s).\n", [SBlocked]) end, file:write(IoDevice, Msg++SBMsg); exportAux({error, instr, {_Target, _RunCount, _SBlocked}}, IoDevice) -> Msg = "Instrumentation error.\n", file:write(IoDevice, Msg); exportAux({error, analysis, {_Target, RunCount, SBlocked}, Tickets}, IoDevice) -> TickLen = length(Tickets), Msg = io_lib:format("Checked ~w interleaving(s). ~w errors found.\n", [RunCount, TickLen]), SBMsg = case SBlocked of 0 -> "\n"; _ -> io_lib:format( " Encountered ~w sleep-set blocked trace(s).\n\n", [SBlocked]) end, case file:write(IoDevice, Msg++SBMsg) of ok -> case lists:foldl(fun writeDetails/2, {1, IoDevice}, concuerror_ticket:sort(Tickets)) of {'error', _Reason}=Error -> Error; _Ok -> ok end; Error -> Error end. %% Write details about each ticket writeDetails(_Ticket, {'error', _Reason}=Error) -> Error; writeDetails(Ticket, {Count, IoDevice}) -> Error = concuerror_ticket:get_error(Ticket), Description = io_lib:format("~p\n~s\n", [Count, concuerror_error:long(Error)]), Details = [" " ++ M ++ "\n" || M <- concuerror_ticket:details_to_strings(Ticket)], Msg = lists:flatten([Description | Details]), case file:write(IoDevice, Msg ++ "\n\n") of ok -> {Count+1, IoDevice}; WriteError -> WriteError end. %%%---------------------------------------------------------------------- %%% Log event handler callback functions %%%---------------------------------------------------------------------- -type state() :: {non_neg_integer(), %% Verbose level concuerror_util:progress() | 'noprogress'}. -spec init(term()) -> {'ok', state()}. %% @doc: Initialize the event handler. init(Options) -> Progress = case lists:keyfind(noprogress, 1, Options) of {noprogress} -> noprogress; false -> concuerror_util:init_state() end, Verbosity = case lists:keyfind('verbose', 1, Options) of {'verbose', V} -> V; false -> 0 end, {ok, {Verbosity, Progress}}. -spec terminate(term(), state()) -> 'ok'. terminate(_Reason, {_Verb, 'noprogress'}) -> ok; terminate(_Reason, {_Verb, {_RunCnt, _Errors, _Elapsed, Timer}}) -> concuerror_util:timer_stop(Timer), ok. -spec handle_event(concuerror_log:event(), state()) -> {'ok', state()}. handle_event({msg, String, MsgVerb}, {Verb, _Progress}=State) -> if Verb >= MsgVerb -> io:format("~s", [String]); true -> ok end, {ok, State}; handle_event({progress, _Type}, {_Verb, 'noprogress'}=State) -> {ok, State}; handle_event({progress, Type}, {Verb, Progress}) -> case concuerror_util:progress_bar(Type, Progress) of {NewProgress, ""} -> {ok, {Verb, NewProgress}}; {NewProgress, Msg} -> io:fwrite("\r\033[K" ++ Msg), {ok, {Verb, NewProgress}} end; handle_event('reset', {_Verb, 'noprogress'}=State) -> {ok, State}; handle_event('reset', {Verb, _Progress}) -> {ok, {Verb, concuerror_util:init_state()}}. ================================================ FILE: resources/old_source/concuerror_deps.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2013, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Stavros Aronis %%% Description : Dependency relation for Erlang %%%---------------------------------------------------------------------- -module(concuerror_deps). -export([may_have_dependencies/1, dependent/2, lock_release_atom/0]). -spec may_have_dependencies(concuerror_sched:transition()) -> boolean(). may_have_dependencies({_Lid, {error, _}, []}) -> false; may_have_dependencies({_Lid, {Spawn, _}, []}) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_monitor; Spawn =:= spawn_opt -> false; may_have_dependencies({_Lid, {'receive', {unblocked, _, _}}, []}) -> false; may_have_dependencies({_Lid, exited, []}) -> false; may_have_dependencies(_Else) -> true. -spec lock_release_atom() -> '_._concuerror_lock_release'. lock_release_atom() -> '_._concuerror_lock_release'. -define( ONLY_INITIALLY, true). -define( SYMMETRIC, true). -define( CHECK_MSG, true). -define( ALLOW_SWAP, true). -define(DONT_ALLOW_SWAP, false). -define(ONLY_AFTER_SWAP, false). -define( DONT_CHECK_MSG, false). -spec dependent(concuerror_sched:transition(), concuerror_sched:transition()) -> boolean(). dependent(A, B) -> dependent(A, B, ?CHECK_MSG, ?ALLOW_SWAP). %%============================================================================== %% Instructions from the same process are always dependent dependent({Lid, _Instr1, _Msgs1}, {Lid, _Instr2, _Msgs2}, ?ONLY_INITIALLY, ?ONLY_INITIALLY) -> true; %%============================================================================== %% XXX: This should be fixed in sched:recent_dependency_cv and removed dependent({_Lid1, _Instr1, _Msgs1}, {_Lid2, 'init', _Msgs2}, ?ONLY_INITIALLY, ?ONLY_INITIALLY) -> false; %%============================================================================== %% Decisions depending on send and receive statements: %%============================================================================== %% Sending to the same process: dependent({ Lid1, Instr1, PreMsgs1} = Trans1, { Lid2, Instr2, PreMsgs2} = Trans2, ?CHECK_MSG, AllowSwap) -> %% ProcEvidence = [{P, L} || {P, {_M, L}} <- PreMsgs2], %% Msgs2 = [{P, M} || {P, {M, _L}} <- PreMsgs2], %% Msgs1 = add_missing_messages(Lid1, Instr1, PreMsgs1, ProcEvidence), ProcEvidence1 = [{P, L, M} || {P, {_M, L, M}} <- PreMsgs1], ProcEvidence2 = [{P, L, M} || {P, {_M, L, M}} <- PreMsgs2], Msgs1 = add_missing_messages(Lid1, Instr1, PreMsgs1, ProcEvidence2), Msgs2 = add_missing_messages(Lid2, Instr2, PreMsgs2, ProcEvidence1), case Msgs1 =:= [] orelse Msgs2 =:= [] of true -> dependent(Trans1, Trans2, ?DONT_CHECK_MSG, AllowSwap); false -> Lids1 = ordsets:from_list(orddict:fetch_keys(Msgs1)), Lids2 = ordsets:from_list(orddict:fetch_keys(Msgs2)), case ordsets:intersection(Lids1, Lids2) of [] -> dependent(Trans1, Trans2, ?DONT_CHECK_MSG, AllowSwap); [Key] -> %% XXX: Can be refined case {orddict:fetch(Key, Msgs1), orddict:fetch(Key, Msgs2)} of {[V1], [V2]} -> LockReleaseAtom = lock_release_atom(), V1 =/= LockReleaseAtom andalso V2 =/= LockReleaseAtom; _Else -> true end; _ -> true %% XXX: Can be refined end end; %%============================================================================== %% Sending to an activated after clause depends on that receive's patterns OR %% Sending the message that triggered a receive's 'had_after' dependent({Lid1, Instr1, PreMsgs1} = Trans1, {Lid2, {Receive, Info}, _Msgs2} = Trans2, _CheckMsg, AllowSwap) when Receive =:= 'after'; (Receive =:= 'receive' andalso element(1, Info) =:= had_after) -> ProcEvidence = case Receive =:= 'after' of true -> [{Lid2, element(2, Info), element(3, Info)}]; false -> [] end, Msgs1 = add_missing_messages(Lid1, Instr1, PreMsgs1, ProcEvidence), Dependent = case orddict:find(Lid2, Msgs1) of {ok, MsgsToLid2} -> Fun = case Receive of 'after' -> element(1, Info); 'receive' -> Target = element(3, Info), OLid = element(2, Info), fun(X) -> X =:= Target andalso OLid =:= Lid1 end end, lists:any(Fun, MsgsToLid2); error -> false end, Dependent orelse (AllowSwap andalso dependent(Trans2, Trans1, ?CHECK_MSG, ?DONT_ALLOW_SWAP)); %%============================================================================== %% Other instructions are not in race with receive or after, if not caught by %% the message checking part. dependent({_Lid1, { _Any, _Details1}, _Msgs1}, {_Lid2, {Receive, _Details2}, _Msgs2}, _CheckMsg, ?ONLY_AFTER_SWAP) when Receive =:= 'after'; Receive =:= 'receive' -> false; %% Swapped version, as the message checking code can force a swap. dependent({_Lid1, {Receive, _Details1}, _Msgs1}, {_Lid2, { _Any, _Details2}, _Msgs2}, _CheckMsg, ?ONLY_AFTER_SWAP) when Receive =:= 'after'; Receive =:= 'receive' -> false; %%============================================================================== %% From here onwards, we have taken care of messaging races. %%============================================================================== %% ETS operations live in their own small world. dependent({_Lid1, {ets, Op1}, _Msgs1}, {_Lid2, {ets, Op2}, _Msgs2}, _CheckMsg, ?SYMMETRIC) -> dependent_ets(Op1, Op2); %%============================================================================== %% Registering a table with the same name as an existing one. dependent({_Lid1, { ets, { new, [_Table, Name, Options]}}, _Msgs1}, {_Lid2, {exit, {normal, {{_Heirs, Tables}, _Na, _Li, _Mo}}}, _Msgs2}, _CheckMsg, _AllowSwap) -> NamedTables = [N || {_Lid, {ok, N}} <- Tables], lists:member(named_table, Options) andalso lists:member(Name, NamedTables); %% Table owners exits race with any ets operation on the same table. dependent({_Lid1, { ets, { _Op, [Table|_Rest]}}, _Msgs1}, {_Lid2, {exit, {normal, {{_Heirs, Tables}, _Na, _Li, _Mo}}}, _Msgs2}, _CheckMsg, _AllowSwap) -> lists:keymember(Table, 1, Tables); %% %% Covered by next %% dependent({_Lid1, { ets, _Details1}, _Msgs1}, %% {_Lid2, {exit, _Details2}, _Msgs2}, %% _CheckMsg, _AllowSwap) -> %% false; %%============================================================================== %% No other operations race with ets operations. dependent({_Lid1, { ets, _Details1}, _Msgs1}, {_Lid2, {_Any, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Exits with heirs, links, monitors induce messages that create dependencies %% XXX: Should be removed when tracking reversals accurately. dependent({Lid1, {exit, {normal, {{Heirs1, _Tbls1}, _N1, L1, M1}}}, _Msgs1}, {Lid2, {exit, {normal, {{Heirs2, _Tbls2}, _N2, L2, M2}}}, _Msgs2}, _CheckMsg, ?SYMMETRIC) -> lists:member(Lid1, Heirs2) orelse lists:member(Lid2, Heirs1) orelse lists:member(Lid1, L2) orelse lists:member(Lid2, L1) orelse lists:member(Lid1, M2) orelse lists:member(Lid2, M1); %%============================================================================== %% Registered processes: %% Sending using name to a process that may exit and unregister. dependent({_Lid1, {send, {TName, _TLid, _Msg}}, _Msgs1}, {_Lid2, {exit, {normal, {_Tables, {ok, TName}, _L, _M}}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; dependent({_Lid1, {send, _Details1}, _Msgs1}, {_Lid2, {exit, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Register and unregister have the same dependencies. %% Use a unique value for the Pid to avoid checks there. dependent({Lid, {unregister, RegName}, Msgs}, B, CheckMsg, AllowSwap) -> dependent({Lid, {register, {RegName, make_ref()}}, Msgs}, B, CheckMsg, AllowSwap); dependent(A, {Lid, {unregister, RegName}, Msgs}, CheckMsg, AllowSwap) -> dependent(A, {Lid, {register, {RegName, make_ref()}}, Msgs}, CheckMsg, AllowSwap); %%============================================================================== %% Send using name before process has registered itself (or after unregistering). dependent({_Lid1, {register, {RegName, _TLid}}, _Msgs1}, {_Lid2, { send, {RegName, _Lid, _Msg}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; %% No other races between register and send. dependent({_Lid1, {register, _Details1}, _Msgs1}, {_Lid2, { send, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Two registers using the same name or the same process. dependent({_Lid1, {register, {RegName1, TLid1}}, _Msgs1}, {_Lid2, {register, {RegName2, TLid2}}, _Msgs2}, _CheckMsg, ?SYMMETRIC) -> RegName1 =:= RegName2 orelse TLid1 =:= TLid2; %%============================================================================== %% Register a process that may exit. dependent({_Lid1, {register, {_RegName, TLid}}, _Msgs1}, { TLid, { exit, {normal, _Info}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; %% Register for a name that might be in use. dependent({_Lid1, {register, {Name, _TLid}}, _Msgs1}, {_Lid2, { exit, {normal, {_Tables, {ok, Name}, _L, _M}}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; %% No other races between register and exit. dependent({_Lid1, {register, _Details1}, _Msgs1}, {_Lid2, { exit, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Whereis using name before process has registered itself. dependent({_Lid1, {register, {RegName, _TLid1}}, _Msgs1}, {_Lid2, { whereis, {RegName, _TLid2}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; %% No other races between register and whereis. dependent({_Lid1, {register, _Details1}, _Msgs1}, {_Lid2, { whereis, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Process alive and exits. dependent({_Lid1, {is_process_alive, TLid}, _Msgs1}, { TLid, { exit, {normal, _Info}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; %% No other races between is_process_alive and exit. dependent({_Lid1, {is_process_alive, _Details1}, _Msgs1}, {_Lid2, { exit, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Process registered and exits. dependent({_Lid1, {whereis, {Name, _TLid1}}, _Msgs1}, {_Lid2, { exit, {normal, {_Tables, {ok, Name}, _L, _M}}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; %% No other races between whereis and exit. dependent({_Lid1, {whereis, _Details1}, _Msgs1}, {_Lid2, { exit, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Demonitor/link/unlink and exit. dependent({_Lid, {Linker, TLid}, _Msgs1}, {TLid, { exit, {normal, _Info}}, _Msgs2}, _CheckMsg, _AllowSwap) when Linker =:= demonitor; Linker =:= link; Linker =:= unlink -> true; %% No other races between demonitor/link/unlink and exit. dependent({_Lid1, {Linker, _Details1}, _Msgs1}, {_Lid2, { exit, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) when Linker =:= demonitor; Linker =:= link; Linker =:= unlink -> false; %%============================================================================== %% Depending on the order, monitor's Info is different. dependent({_Lid, {monitor, {TLid, _MonRef}}, _Msgs1}, {TLid, { exit, {normal, _Info}}, _Msgs2}, _CheckMsg, _AllowSwap) -> true; dependent({_Lid1, {monitor, _Details1}, _Msgs1}, {_Lid2, { exit, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Trap exits flag and linked process exiting. dependent({Lid1, {process_flag, {trap_exit, _Value, Links1}}, _Msgs1}, {Lid2, { exit, {normal, {_Tables, _N, Links2, _M}}}, _Msgs2}, _CheckMsg, _AllowSwap) -> lists:member(Lid2, Links1) orelse lists:member(Lid1, Links2); %% Trap exits flag and explicit exit signals. dependent({ Lid1, {process_flag, {trap_exit, _Value, _Links1}}, _Msgs1}, {_Lid2, { exit_2, {TLid, _Reason}}, _Msgs2}, _CheckMsg, _AllowSwap) -> Lid1 =:= TLid; %% No other races between setting a process flag and exiting. dependent({_Lid1, {process_flag, _Details1}, _Msgs1}, {_Lid2, { exit, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) -> false; %%============================================================================== %% Setting a process_flag is not in race with linking. Happening together, they %% can cause other races, however. dependent({_Lid1, {process_flag, _Details1}, _Msgs1}, {_Lid2, {LinkOrUnlink, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) when LinkOrUnlink =:= link; LinkOrUnlink =:= unlink -> false; %%============================================================================== %% Spawning is independent with everything else. dependent({_Lid1, {Spawn, _Details1}, _Msgs1}, {_Lid2, { _Any, _Details2}, _Msgs2}, _CheckMsg, _AllowSwap) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_monitor; Spawn =:= spawn_opt -> false; %%============================================================================== %% Swap the two arguments if the test is not symmetric by itself. dependent(TransitionA, TransitionB, _CheckMsg, ?ALLOW_SWAP) -> dependent(TransitionB, TransitionA, ?CHECK_MSG, ?DONT_ALLOW_SWAP); dependent(TransitionA, TransitionB, _CheckMsg, ?DONT_ALLOW_SWAP) -> case independent(TransitionA, TransitionB) of true -> false; maybe -> io:format("ALERT! Not certainly independent:\n ~p\n ~p\n", [TransitionA, TransitionB]), true end. %%============================================================================== %%============================================================================== -spec independent(concuerror_sched:transition(), concuerror_sched:transition()) -> 'true' | 'maybe'. independent({_Lid1, {Op1, _}, _Msgs1}, {_Lid2, {Op2, _}, _Msgs2}) -> Independent = [ { monitor, demonitor}, { monitor, send}, { demonitor, send}, { whereis, send}, { link, send}, { unlink, send}, {process_flag, send}, {process_flag, monitor}, { unlink, monitor}, { register, monitor}, { whereis, unlink}, { unlink, register}, { whereis, monitor}, { whereis, demonitor}, { unlink, demonitor}, { register, demonitor}, {process_flag, register}, { whereis, is_process_alive}, { demonitor, is_process_alive}, { monitor, is_process_alive}, { send, is_process_alive}, { link, monitor} ], %% XXX: This should probably be removed. Solo = [send_after,exit_2], case %% Assuming that all the races of an instruction with another instance %% of itself have already been caught. Op1 =:= Op2 orelse lists:member({Op1, Op2},Independent) orelse lists:member({Op2, Op1},Independent) orelse lists:member(Op1, Solo) orelse lists:member(Op2, Solo) of true -> true; false -> maybe end. add_missing_messages(Lid, Instr, PreMsgs, ProcEvidence) -> Msgs = [{P, M} || {P, {M, _L, _M}} <- PreMsgs], case Instr of {send, {_RegName, Lid2, Msg}} -> add_missing_message(Lid2, Msg, Msgs); {exit, _} -> LMsg = {'EXIT', Lid, normal}, %% XXX: Dummy could be problematic MMsg = {'DOWN', dummy, process, Lid, normal}, LAdder = fun(P, M) -> add_missing_message(P, LMsg, M) end, MAdder = fun(P, M) -> add_missing_message(P, MMsg, M) end, Fold = fun({P, Links, Monitors}, Acc) -> Acc1 = case lists:member(Lid, Links) of true -> LAdder(P, Acc); false -> Acc end, Acc2 = case lists:member(Lid, Monitors) of true -> MAdder(P, Acc1); false -> Acc1 end, Acc2 end, lists:foldl(Fold, Msgs, ProcEvidence); {exit_2, {To, Msg}} -> %% XXX: Too strong. add_missing_message(To, Msg, Msgs); _ -> Msgs end. %% XXX: Not accurate for monitor DOWN messages due to dummy add_missing_message(Lid, Msg, Msgs) -> try true = lists:member(Msg, orddict:fetch(Lid, Msgs)) of _ -> Msgs catch _:_ -> orddict:append(Lid, Msg, Msgs) end. %% ETS table dependencies: dependent_ets(Op1, Op2) -> dependent_ets(Op1, Op2, ?ALLOW_SWAP). %%============================================================================== dependent_ets({MajorOp, [Tid1, Name1|_]}, {_, [Tid2, Name2|_]}, _AllowSwap) when MajorOp =:= info; MajorOp =:= delete -> (Tid1 =:= Tid2) orelse (Name1 =:= Name2); %%============================================================================== dependent_ets({insert, [T, _, Keys1, KP, Objects1, true]}, {insert, [T, _, Keys2, KP, Objects2, true]}, ?SYMMETRIC) -> case ordsets:intersection(Keys1, Keys2) of [] -> false; Keys -> Fold = fun(_K, true) -> true; (K, false) -> lists:keyfind(K, KP, Objects1) =/= lists:keyfind(K, KP, Objects2) end, lists:foldl(Fold, false, Keys) end; dependent_ets({insert, _Details1}, {insert, _Details2}, ?SYMMETRIC) -> false; %%============================================================================== dependent_ets({insert_new, [_, _, _, _, _, false]}, {insert_new, [_, _, _, _, _, false]}, ?SYMMETRIC) -> false; dependent_ets({insert_new, [T, _, Keys1, KP, _Objects1, _Status1]}, {insert_new, [T, _, Keys2, KP, _Objects2, _Status2]}, ?SYMMETRIC) -> ordsets:intersection(Keys1, Keys2) =/= []; dependent_ets({insert_new, _Details1}, {insert_new, _Details2}, ?SYMMETRIC) -> false; %%============================================================================== dependent_ets({insert_new, [T, _, Keys1, KP, _Objects1, _Status1]}, { insert, [T, _, Keys2, KP, _Objects2, true]}, _AllowSwap) -> ordsets:intersection(Keys1, Keys2) =/= []; dependent_ets({insert_new, _Details1}, { insert, _Details2}, _AllowSwap) -> false; %%============================================================================== dependent_ets({Insert, [T, _, Keys, _KP, _Objects1, true]}, {lookup, [T, _, K]}, _AllowSwap) when Insert =:= insert; Insert =:= insert_new -> ordsets:is_element(K, Keys); dependent_ets({Insert, _Details1}, {lookup, _Details2}, _AllowSwap) when Insert =:= insert; Insert =:= insert_new -> false; %%============================================================================== dependent_ets({lookup, _Details1}, {lookup, _Details2}, ?SYMMETRIC) -> false; %%============================================================================== dependent_ets({new, [_Tid1, Name, Options1]}, {new, [_Tid2, Name, Options2]}, ?SYMMETRIC) -> lists:member(named_table, Options1) andalso lists:member(named_table, Options2); %%============================================================================== dependent_ets({ new, _Details1}, {_Any, _Details2}, ?DONT_ALLOW_SWAP) -> false; dependent_ets({_Any, _Details1}, { new, _Details2}, ?DONT_ALLOW_SWAP) -> false; %%============================================================================== dependent_ets(Op1, Op2, ?ALLOW_SWAP) -> dependent_ets(Op2, Op1, ?DONT_ALLOW_SWAP); dependent_ets(Op1, Op2, ?DONT_ALLOW_SWAP) -> concuerror_log:log(3, "Not certainly independent (ETS):\n ~p\n ~p\n", [Op1, Op2]), true. ================================================ FILE: resources/old_source/concuerror_error.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Error interface %%%---------------------------------------------------------------------- -module(concuerror_error). -export([long/1, mock/0, new/1, short/1, type/1]). -export_type([error/0]). -include("gen.hrl"). -type error_type() :: 'assertion_violation' | 'deadlock' | 'exception'. -type error() :: {error_type(), term()}. -spec new(term()) -> error(). new({deadlock, _Set} = Deadlock) -> Deadlock; new({{assertion_failed, Details}, _Any}) -> {assertion_violation, Details}; new({{assertEqual_failed, Details}, _Any}) -> {assertion_violation, Details}; new(Reason) -> {exception, Reason}. -spec type(error()) -> nonempty_string(). type({deadlock, _Blocked}) -> "Deadlock"; type({assertion_violation, _Details}) -> "Assertion violation"; type({exception, _Details}) -> "Exception". -spec short(error()) -> nonempty_string(). short({deadlock, Blocked}) -> OldList = lists:sort(?SETS:to_list(Blocked)), {List, [Last]} = lists:split(length(OldList) - 1, OldList), Fun = fun(L, A) -> A ++ concuerror_lid:to_string(L) ++ ", " end, lists:foldl(Fun, "", List) ++ concuerror_lid:to_string(Last); short({assertion_violation, [{module, Module}, {line, Line}|_Rest]}) -> OldModule = concuerror_instr:old_module_name(Module), concuerror_util:flat_format("~p.erl:~p", [OldModule, Line]); short({exception, Reason}) -> lists:flatten(io_lib:format("~W", [Reason, 3])). -spec long(error()) -> nonempty_string(). long({deadlock, _Blocked} = Error) -> Format = "Error type : Deadlock~n" "Blocked processes : ~s", concuerror_util:flat_format(Format, [short(Error)]); long({assertion_violation, [{module, Module}, {line, Line}, _Xpr, {expected, Exp}, {value, Val}]}) -> Format = "Error type : Assertion violation~n" "Module:Line : ~p.erl:~p~n" "Expected : ~p~n" "Value : ~p", OldModule = concuerror_instr:old_module_name(Module), concuerror_util:flat_format(Format, [OldModule, Line, Exp, Val]); long({exception, Details}) -> Format = "Error type : Exception~n" "Details : ~p", concuerror_util:flat_format(Format, [Details]). -spec mock() -> {'exception', 'foobar'}. mock() -> {exception, foobar}. ================================================ FILE: resources/old_source/concuerror_gui.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Graphical User Interface %%%---------------------------------------------------------------------- -module(concuerror_gui). %% UI exports. -export([start/1]). %% Log server callback exports. -export([init/1, terminate/2, handle_event/2]). -include_lib("wx/include/wx.hrl"). -include("gen.hrl"). -include("gui.hrl"). %% Log event handler internal state. -type state() :: {non_neg_integer(), %% Verbose level concuerror_util:progress() | 'noprogress'}. %%%---------------------------------------------------------------------- %%% UI functions %%%---------------------------------------------------------------------- %% @spec start(concuerror:options()) -> 'true' %% @doc: Start the Concuerror GUI. -spec start(concuerror:options()) -> 'true'. start(Options) -> register(?RP_GUI, self()), _ = wx:new(), %% Start the object reference mapping service. ref_start(), Frame = setupFrame(), wxFrame:show(Frame), setSplitterInitSizes(), %% Attach the event handler below. _ = concuerror_log:attach(?MODULE, {wx:get_env(), Options}), %% Load preferences from Options. loadPrefs(Options), refresh(), %% Start the replay server. loop(), %% Save possibly edited preferences to file. savePrefs(), ref_stop(), wx:destroy(), unregister(?RP_GUI). %%%---------------------------------------------------------------------- %%% Log event handler callback functions %%%---------------------------------------------------------------------- -spec init(term()) -> {'ok', state()}. %% @doc: Initialize the event handler. %% %% Note: The wx environment is set once in this function and is subsequently %% used by all callback functions. If any change is to happen to the %% environment (e.g. new elements added dynamically), `set_env' will have %% to be called again (by manually calling a special update_environment %% function for each update?). init({Env, Options}) -> wx:set_env(Env), Progress = case lists:keyfind(noprogress, 1, Options) of {noprogress} -> noprogress; false -> concuerror_util:init_state() end, Verbosity = case lists:keyfind('verbose', 1, Options) of {'verbose', V} -> V; false -> 0 end, {ok, {Verbosity, Progress}}. -spec terminate(term(), state()) -> 'ok'. terminate(_Reason, {_Verb, 'noprogress'}) -> ok; terminate(_Reason, {_Verb, {_RunCnt, _NumErrors, _Elapsed, Timer}}) -> concuerror_util:timer_stop(Timer), ok. -spec handle_event(concuerror_log:event(), state()) -> {'ok', state()}. handle_event({msg, String, MsgVerb}, {Verb, _Progress}=State) -> if Verb >= MsgVerb -> wxTextCtrl:appendText(ref_lookup(?LOG_TEXT), String); true -> ok end, {ok, State}; handle_event({progress, _Type}, {_Verb, 'noprogress'}=State) -> {ok, State}; handle_event({progress, Type}, {Verb, Progress}) -> case Type of {error, Ticket} -> Error = concuerror_ticket:get_error(Ticket), ErrorItem = concuerror_util:flat_format("~s~n~s", [concuerror_error:type(Error), concuerror_error:short(Error)]), List = ref_lookup(?ERROR_LIST), wxControlWithItems:append(List, ErrorItem), addListData(?ERROR_LIST, [Ticket]), ok; _Other -> ok end, case concuerror_util:progress_bar(Type, Progress) of {NewProgress, ""} -> {ok, {Verb, NewProgress}}; {NewProgress, Msg} -> This = ref_lookup(?LOG_TEXT), NumLines = wxTextCtrl:getNumberOfLines(This), FromPos = wxTextCtrl:xYToPosition(This, 0, NumLines-1), EndPos = wxTextCtrl:getLastPosition(This), wxTextCtrl:replace(This, FromPos, EndPos, Msg), {ok, {Verb, NewProgress}} end; handle_event('reset', {_Verb, 'noprogress'}=State) -> {ok, State}; handle_event('reset', {Verb, _Progress}) -> {ok, {Verb, concuerror_util:init_state()}}. %%%---------------------------------------------------------------------- %%% Setup functions %%%---------------------------------------------------------------------- setupFrame() -> Frame = wxFrame:new(wx:null(), ?FRAME, ?APP_STRING), ref_add(?FRAME, Frame), MenuBar = wxMenuBar:new(), setupMenu(MenuBar, ?MENU_SPEC), wxFrame:setMenuBar(Frame, MenuBar), _ = wxFrame:createStatusBar(Frame, [{id, ?STATUS_BAR}]), wxEvtHandler:connect(Frame, close_window), wxEvtHandler:connect(Frame, command_menu_selected), wxEvtHandler:connect(Frame, command_button_clicked), wxEvtHandler:connect(Frame, command_listbox_selected), wxEvtHandler:connect(Frame, command_listbox_doubleclicked), wxEvtHandler:connect(Frame, command_splitter_sash_pos_changed), _ = setupTopSplitter(Frame), wxWindow:setSize(Frame, ?FRAME_SIZE_INIT), %% wxWindow:fit(Frame), wxFrame:center(Frame), Frame. setupTopSplitter(Parent) -> Splitter = wxSplitterWindow:new(Parent, [{id, ?TOP_SPLITTER}]), ref_add(?TOP_SPLITTER, Splitter), LeftPanel = wxPanel:new(Splitter), LeftSizer = setupLeftColumn(LeftPanel), wxWindow:setSizer(LeftPanel, LeftSizer), _ = wxSizer:fit(LeftSizer, LeftPanel), RightPanel = wxPanel:new(Splitter), RightSizer = setupRightColumn(RightPanel), wxWindow:setSizer(RightPanel, RightSizer), _ = wxSizer:fit(RightSizer, RightPanel), wxSplitterWindow:setMinimumPaneSize(Splitter, ?MIN_TOP), wxSplitterWindow:setSashGravity(Splitter, ?GRAV_TOP), wxSplitterWindow:splitVertically(Splitter, LeftPanel, RightPanel), Splitter. %% Sets initial sizes for all splitters. setSplitterInitSizes() -> Fun = fun(S, V) -> wxSplitterWindow:setSashPosition(ref_lookup(S), V) end, lists:foreach(fun ({S, V}) -> Fun(S, V) end, ?SPLITTER_INIT). %% Setup left column of top-level panel, including module and function %% listboxes and several buttons. setupLeftColumn(Parent) -> Splitter = wxSplitterWindow:new(Parent, [{id, ?MOD_FUN_SPLITTER}]), ref_add(?MOD_FUN_SPLITTER, Splitter), ModulePanel = wxPanel:new(Splitter), ModuleSizer = setupModuleSizer(ModulePanel), wxWindow:setSizerAndFit(ModulePanel, ModuleSizer), FunctionPanel = wxPanel:new(Splitter), FunctionSizer = setupFunctionSizer(FunctionPanel), wxWindow:setSizerAndFit(FunctionPanel, FunctionSizer), wxSplitterWindow:setMinimumPaneSize(Splitter, ?MIN_MOD_FUN), wxSplitterWindow:setSashGravity(Splitter, ?GRAV_MOD_FUN), wxSplitterWindow:splitHorizontally(Splitter, ModulePanel, FunctionPanel), %% Add padding to the whole sizer. LeftColumnSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(LeftColumnSizerOuter, Splitter, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), LeftColumnSizerOuter. setupModuleSizer(Parent) -> ModuleBox = wxStaticBox:new(Parent, ?wxID_ANY, "Modules"), ModuleList = wxListBox:new(Parent, ?MODULE_LIST), ref_add(?MODULE_LIST, ModuleList), AddButton = wxButton:new(Parent, ?ADD), RemButton = wxButton:new(Parent, ?REMOVE), ClearButton = wxButton:new(Parent, ?CLEAR), RefreshButton = wxButton:new(Parent, ?REFRESH), %% Setup button sizers AddRemSizer = wxBoxSizer:new(?wxHORIZONTAL), _ = wxSizer:add(AddRemSizer, AddButton, [{proportion, 1}, {flag, ?wxRIGHT}, {border, 5}]), _ = wxSizer:add(AddRemSizer, RemButton, [{proportion, 1}, {flag, ?wxLEFT}, {border, 5}]), ClrSizer = wxBoxSizer:new(?wxHORIZONTAL), _ = wxSizer:add(ClrSizer, ClearButton, [{proportion, 1}, {flag, ?wxRIGHT}, {border, 5}]), _ = wxSizer:add(ClrSizer, RefreshButton, [{proportion, 1}, {flag, ?wxLEFT}, {border, 5}]), %% Setup module sizers ModuleSizer = wxStaticBoxSizer:new(ModuleBox, ?wxVERTICAL), _ = wxSizer:add(ModuleSizer, ModuleList, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxTOP bor ?wxLEFT bor ?wxRIGHT}, {border, 10}]), _ = wxSizer:add(ModuleSizer, AddRemSizer, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxTOP bor ?wxLEFT bor ?wxRIGHT}, {border, 10}]), _ = wxSizer:add(ModuleSizer, ClrSizer, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), %% Add padding to the whole sizer. ModuleSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(ModuleSizerOuter, ModuleSizer, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxBOTTOM}, {border, 5}]), ModuleSizerOuter. setupFunctionSizer(Parent) -> %% Create widgets FunctionBox = wxStaticBox:new(Parent, ?wxID_ANY, "Functions"), FunctionList = wxListBox:new(Parent, ?FUNCTION_LIST, [{style, ?wxLB_SORT}]), ref_add(?FUNCTION_LIST, FunctionList), AnalyzeButton = wxButton:new(Parent, ?ANALYZE, [{label, "Ana&lyze"}]), ref_add(?ANALYZE, AnalyzeButton), StopButton = wxButton:new(Parent, ?STOP, [{label, "&Stop"}]), ref_add(?STOP, StopButton), %% Setup sizers AnalStopSizer = wxBoxSizer:new(?wxHORIZONTAL), _ = wxSizer:add(AnalStopSizer, AnalyzeButton, [{proportion, 1}, {flag, ?wxRIGHT}, {border, 5}]), _ = wxSizer:add(AnalStopSizer, StopButton, [{proportion, 1}, {flag, ?wxLEFT}, {border, 5}]), ref_add(?ANAL_STOP_SIZER, AnalStopSizer), FunctionSizer = wxStaticBoxSizer:new(FunctionBox, ?wxVERTICAL), _ = wxSizer:add(FunctionSizer, FunctionList, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxTOP bor ?wxLEFT bor ?wxRIGHT}, {border, 10}]), _ = wxSizer:add(FunctionSizer, AnalStopSizer, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), %% Add padding to the whole sizer. FunctionSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(FunctionSizerOuter, FunctionSizer, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxTOP}, {border, 0}]), FunctionSizerOuter. %% Setup right column of top-level panel, including a notebook for displaying %% tabbed main, graph and source code panels and another notebook for displaying %% log messages. setupRightColumn(Parent) -> Splitter = wxSplitterWindow:new(Parent, [{id, ?NOTEBOOK_SPLITTER}]), ref_add(?NOTEBOOK_SPLITTER, Splitter), TopPanel = wxPanel:new(Splitter), TopSizer = setupMainNotebookSizer(TopPanel), wxWindow:setSizerAndFit(TopPanel, TopSizer), BottomPanel = wxPanel:new(Splitter), BottomSizer = setupLogNotebookSizer(BottomPanel), wxWindow:setSizerAndFit(BottomPanel, BottomSizer), wxSplitterWindow:setMinimumPaneSize(Splitter, ?MIN_NOTEBOOK), wxSplitterWindow:setSashGravity(Splitter, ?GRAV_NOTEBOOK), wxSplitterWindow:splitHorizontally(Splitter, TopPanel, BottomPanel), %% Add padding to the whole sizer. RightColumnSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(RightColumnSizerOuter, Splitter, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), RightColumnSizerOuter. %% Setup main notebook, containing 3 tabs: %% Main tab: Contains a list showing the errors encountered and another list %% showing the selected erroneous interleaving. %% Graph tab: Displays a process interaction graph of the selected erroneous %% interleaving. %% Source tab: Displays the source code for the selected module. %% TODO: Temporarily removed graph tab. setupMainNotebookSizer(Parent) -> %% Notebook widgets. Notebook = wxNotebook:new(Parent, ?NOTEBOOK, [{style, ?wxNB_NOPAGETHEME}]), ref_add(?NOTEBOOK, Notebook), %% Setup tab panels. MainPanel = setupMainPanel(Notebook), _GraphPanel = setupGraphPanel(Notebook), SourcePanel = setupSourcePanel(Notebook), %% Add tabs to notebook. wxNotebook:addPage(Notebook, MainPanel, "Main", [{bSelect, true}]), %% TODO: Temporarily removed graph tab. %% wxNotebook:addPage(Notebook, GraphPanel, "Graph", [{bSelect, false}]), wxNotebook:addPage(Notebook, SourcePanel, "Source", [{bSelect, false}]), %% Add padding to the notebook. NotebookSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(NotebookSizerOuter, Notebook, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxBOTTOM}, {border, 5}]), NotebookSizerOuter. setupMainPanel(Parent) -> MainPanel = wxPanel:new(Parent), Splitter = wxSplitterWindow:new(MainPanel, [{id, ?ERROR_ILEAVE_SPLITTER}]), ref_add(?ERROR_ILEAVE_SPLITTER, Splitter), ErrorPanel = wxPanel:new(Splitter), ErrorSizer = setupErrorListSizer(ErrorPanel), wxWindow:setSizerAndFit(ErrorPanel, ErrorSizer), IleavePanel = wxPanel:new(Splitter), IleaveSizer = setupIleaveListSizer(IleavePanel), wxWindow:setSizerAndFit(IleavePanel, IleaveSizer), wxSplitterWindow:setMinimumPaneSize(Splitter, ?MIN_ERROR_ILEAVE), wxSplitterWindow:setSashGravity(Splitter, ?GRAV_ERROR_ILEAVE), wxSplitterWindow:splitVertically(Splitter, ErrorPanel, IleavePanel), %% Add padding to the panel. MainPanelSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(MainPanelSizerOuter, Splitter, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), wxWindow:setSizer(MainPanel, MainPanelSizerOuter), MainPanel. setupErrorListSizer(Parent) -> ErrorBox = wxStaticBox:new(Parent, ?wxID_ANY, "Errors"), ErrorList = wxListBox:new(Parent, ?ERROR_LIST), ref_add(?ERROR_LIST, ErrorList), %% Setup sizers. ErrorSizer = wxStaticBoxSizer:new(ErrorBox, ?wxVERTICAL), _ = wxSizer:add(ErrorSizer, ErrorList, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), %% Add padding to the whole sizer. ErrorSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(ErrorSizerOuter, ErrorSizer, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxRIGHT}, {border, 5}]), ErrorSizerOuter. setupIleaveListSizer(Parent) -> IleaveBox = wxStaticBox:new(Parent, ?wxID_ANY, "Process interleaving"), IleaveList = wxListBox:new(Parent, ?ILEAVE_LIST), ref_add(?ILEAVE_LIST, IleaveList), %% Setup sizers. IleaveSizer = wxStaticBoxSizer:new(IleaveBox, ?wxVERTICAL), _ = wxSizer:add(IleaveSizer, IleaveList, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), %% Add padding to the whole sizer. IleaveSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(IleaveSizerOuter, IleaveSizer, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxLEFT}, {border, 5}]), IleaveSizerOuter. %% Setup the graph panel. %% A static bitmap combined with a scrolled window is used for %% displaying the graph image. setupGraphPanel(Parent) -> Panel = wxPanel:new(Parent), ScrGraph = wxScrolledWindow:new(Panel), ref_add(?SCR_GRAPH, ScrGraph), wxWindow:setOwnBackgroundColour(ScrGraph, {255, 255, 255}), wxWindow:clearBackground(ScrGraph), Bmp = wxBitmap:new(), StaticBmp = wxStaticBitmap:new(ScrGraph, ?STATIC_BMP, Bmp), ref_add(?STATIC_BMP, StaticBmp), %% Setup sizer. PanelSizer = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(PanelSizer, ScrGraph, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), wxWindow:setSizer(Panel, PanelSizer), Panel. setupSourcePanel(Parent) -> Panel = wxPanel:new(Parent), SourceText = wxStyledTextCtrl:new(Panel), ref_add(?SOURCE_TEXT, SourceText), setupSourceText(SourceText, light), %% Setup sizer. PanelSizer = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(PanelSizer, SourceText, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), wxWindow:setSizer(Panel, PanelSizer), Panel. %% Setup source viewer, using a styled text control. %% Ref is a reference to the wxStyledTextCtrl object and theme is %% either 'light' or 'dark'. setupSourceText(Ref, Theme) -> NormalFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL, []), BoldFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxBOLD, []), case Theme of dark -> Styles = ?SOURCE_STYLES_DARK, BgColor = ?SOURCE_BG_DARK; light -> Styles = ?SOURCE_STYLES_LIGHT, BgColor = ?SOURCE_BG_LIGHT end, wxStyledTextCtrl:styleClearAll(Ref), wxStyledTextCtrl:styleSetFont(Ref, ?wxSTC_STYLE_DEFAULT, NormalFont), wxStyledTextCtrl:styleSetBackground(Ref, ?wxSTC_STYLE_DEFAULT, BgColor), wxStyledTextCtrl:setLexer(Ref, ?wxSTC_LEX_ERLANG), wxStyledTextCtrl:setMarginType(Ref, 0, ?wxSTC_MARGIN_NUMBER), Width = wxStyledTextCtrl:textWidth(Ref, ?wxSTC_STYLE_LINENUMBER, "99999"), wxStyledTextCtrl:setMarginWidth(Ref, 0, Width), wxStyledTextCtrl:setMarginWidth(Ref, 1, 0), %% wxStyledTextCtrl:setScrollWidth(Ref, 1000), %% wxStyledTextCtrl:setSelectionMode(Ref, ?wxSTC_SEL_LINES), wxStyledTextCtrl:setReadOnly(Ref, true), wxStyledTextCtrl:setWrapMode(Ref, ?wxSTC_WRAP_WORD), SetStyles = fun({Style, Color, Option}) -> case Option of bold -> wxStyledTextCtrl:styleSetFont(Ref, Style, BoldFont); _Other -> wxStyledTextCtrl:styleSetFont(Ref, Style, NormalFont) end, wxStyledTextCtrl:styleSetForeground(Ref, Style, Color), wxStyledTextCtrl:styleSetBackground(Ref, Style, BgColor) end, lists:foreach(fun(Style) -> SetStyles(Style) end, Styles), wxStyledTextCtrl:setKeyWords(Ref, 0, ?KEYWORDS). %% Setup a notebook for displaying log messages. setupLogNotebookSizer(Parent) -> %% Log notebook widgets (notebook -> panel -> textcontrol). Notebook = wxNotebook:new(Parent, ?LOG_NOTEBOOK, [{style, ?wxNB_NOPAGETHEME}]), ref_add(?LOG_NOTEBOOK, Notebook), %% Setup tab panels LogPanel = setupLogPanel(Notebook), ErrorPanel = setupErrorPanel(Notebook), %% Add tabs to log notebook. wxNotebook:addPage(Notebook, LogPanel, "Log", [{bSelect, true}]), wxNotebook:addPage(Notebook, ErrorPanel, "Problems", [{bSelect, false}]), NotebookSizerOuter = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(NotebookSizerOuter, Notebook, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxBOTTOM}, {border, 0}]), NotebookSizerOuter. setupLogPanel(Parent) -> Panel = wxPanel:new(Parent), LogText = wxTextCtrl:new(Panel, ?LOG_TEXT, [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]), ref_add(?LOG_TEXT, LogText), Style = wxTextAttr:new(), wxTextAttr:setFont(Style, wxFont:new(10, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, -1)), wxTextCtrl:setDefaultStyle(LogText, Style), PanelSizer = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(PanelSizer, LogText, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), wxWindow:setSizer(Panel, PanelSizer), Panel. setupErrorPanel(Parent) -> Panel = wxPanel:new(Parent), ErrorText = wxTextCtrl:new(Panel, ?ERROR_TEXT, [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]), ref_add(?ERROR_TEXT, ErrorText), Style = wxTextAttr:new(), wxTextAttr:setFont(Style, wxFont:new(10, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, -1)), wxTextCtrl:setDefaultStyle(ErrorText, Style), PanelSizer = wxBoxSizer:new(?wxVERTICAL), _ = wxSizer:add(PanelSizer, ErrorText, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), wxWindow:setSizer(Panel, PanelSizer), Panel. %% Menu constructor according to specification (see gui.hrl). setupMenu(MenuBar, [{Title, Items}|Rest]) -> setupMenu(MenuBar, [{Title, Items, []}|Rest]); setupMenu(_MenuBar, []) -> ok; setupMenu(MenuBar, [{Title, Items, Options}|Rest]) -> Menu = wxMenu:new(Options), setupMenuItems(Menu, Items), wxMenuBar:append(MenuBar, Menu, Title), setupMenu(MenuBar, Rest). setupMenuItems(_Menu, []) -> ok; setupMenuItems(Menu, [Options|Rest]) -> Item = case lists:keytake(sub, 1, Options) of {value, {sub, SubItems}, NewOptions} -> Submenu = wxMenu:new(), setupMenuItems(Submenu, SubItems), I = createMenuItem(NewOptions), wxMenuItem:setSubMenu(I, Submenu), I; false -> createMenuItem(Options) end, _ = wxMenu:append(Menu, Item), setupMenuItems(Menu, Rest). createMenuItem(Options) -> case lists:keytake(label, 1, Options) of {value, {label, Label}, NewOptions} -> Item = wxMenuItem:new(NewOptions), ref_add(Label, Item), Item; false -> wxMenuItem:new(Options) end. %%%---------------------------------------------------------------------- %%% GUI element reference store/retrieve interface %%%---------------------------------------------------------------------- ref_add(Id, Ref) -> ets:insert(?NT_REF, {Id, Ref}). ref_lookup(Id) -> ets:lookup_element(?NT_REF, Id, 2). ref_start() -> ?NT_REF = ets:new(?NT_REF, [set, public, named_table]), ok. ref_stop() -> ets:delete(?NT_REF). %%%---------------------------------------------------------------------- %%% Helper functions %%%---------------------------------------------------------------------- addArgs(_Parent, _Sizer, Max, Max, Refs) -> lists:reverse(Refs); addArgs(Parent, Sizer, I, Max, Refs) -> %% XXX: semi-hack, custom width, default height (-1) Ref = wxTextCtrl:new(Parent, ?wxID_ANY, [{size, {170, -1}}]), HorizSizer = wxBoxSizer:new(?wxHORIZONTAL), _ = wxSizer:add(HorizSizer, wxStaticText:new(Parent, ?wxID_ANY, io_lib:format("Arg~p: ", [I + 1])), [{proportion, 0}, {flag, ?wxALIGN_CENTER bor ?wxRIGHT}, {border, 5}]), _ = wxSizer:add(HorizSizer, Ref, [{proportion, 1}, {flag, ?wxALIGN_CENTER bor ?wxALL}, {border, 0}]), _ = wxSizer:add(Sizer, HorizSizer, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), addArgs(Parent, Sizer, I + 1, Max, [Ref|Refs]). %% Module-adding dialog addDialog(Parent) -> Caption = "Open erlang module", Wildcard = "Erlang source|*.erl| All files|*", DefaultDir = ref_lookup(?FILE_PATH), DefaultFile = "", Dialog = wxFileDialog:new(Parent, [{message, Caption}, {defaultDir, DefaultDir}, {defaultFile, DefaultFile}, {wildCard, Wildcard}, {style, ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST bor ?wxFD_MULTIPLE}]), case wxDialog:showModal(Dialog) of ?wxID_OK -> NewFiles = wxFileDialog:getPaths(Dialog), ModuleList = ref_lookup(?MODULE_LIST), OldFiles = getStrings(ModuleList), case checkDuplicates(OldFiles, NewFiles) of false -> addListItems(?MODULE_LIST, NewFiles), ref_add(?FILE_PATH, getDirectory()); Duplicates -> wxTextCtrl:appendText(ref_lookup(?ERROR_TEXT), io_lib:format("Duplicate modules: " "~p~n", [Duplicates])), continue end; _Other -> continue end, wxDialog:destroy(Dialog). addListData(Id, DataList) -> List = ref_lookup(Id), Count = wxControlWithItems:getCount(List), setListData_aux(List, DataList, Count - 1). %% Add items to ListBox (Id) and select first of newly added modules addListItems(Id, Items) -> List = ref_lookup(Id), Count = wxControlWithItems:getCount(List), wxListBox:insertItems(List, Items, Count), wxControlWithItems:setSelection(List, Count). analyze_proc() -> Env = wx:get_env(), spawn_link(fun() -> wx:set_env(Env), analyze(), send_event_msg_to_self(?ERROR_LIST) end). %% Analyze selected function. analyze() -> Module = getModule(), {Function, Arity} = getFunction(), ModuleList = ref_lookup(?MODULE_LIST), %% Get the list of files to be instrumented. Files = getStrings(ModuleList), %% Check if a module and function is selected. if Module =/= '', Function =/= '' -> case Arity of 0 -> analyze_aux(Module, Function, [], Files); %% If the function to be analyzed is of non-zero arity, %% a dialog window is displayed prompting the user to enter %% the function's arguments. Count -> Frame = ref_lookup(?FRAME), case argDialog(Frame, Count) of {ok, Args} -> analyze_aux(Module, Function, Args, Files); %% User pressed 'cancel' or closed dialog window. _Other -> continue end end; true -> continue end. analyze_aux(Module, Function, Args, Files) -> analysis_init(), Target = {Module, Function, Args}, Preb = case ref_lookup(?PREF_PREB_ENABLED) of true -> {preb, ref_lookup(?PREF_PREB_BOUND)}; false -> {preb, inf} end, Include = {'include', ref_lookup(?PREF_INCLUDE)}, Define = {'define', ref_lookup(?PREF_DEFINE)}, Dpor = case ets:lookup(?NT_OPTIONS, 'dpor') of [] -> {'dpor', 'none'}; [Flavor] -> Flavor end, Opts = [Include, Define, Preb, Dpor], Result = concuerror_sched:analyze(Target, Files, Opts), ref_add(?ANALYSIS_RET, Result), analysis_cleanup(). %% Initialization actions before starting analysis (clear log, etc.). analysis_init() -> Separator = "----o----o----o----o----o----o----o----o----o----o----o\n", wxTextCtrl:appendText(ref_lookup(?LOG_TEXT), Separator), clearProbs(), clearErrors(), clearIleaves(), disableMenuItems(), AnalStopSizer = ref_lookup(?ANAL_STOP_SIZER), AnalyzeButton = ref_lookup(?ANALYZE), Parent = wxWindow:getParent(AnalyzeButton), AnalyzeGauge = wxGauge:new(Parent, ?wxID_ANY, 100, [{style, ?wxGA_HORIZONTAL}]), ref_add(?ANALYZE_GAUGE, AnalyzeGauge), wxSizer:replace(AnalStopSizer, AnalyzeButton, AnalyzeGauge), wxWindow:destroy(AnalyzeButton), wxSizer:layout(AnalStopSizer), start_pulsing(AnalyzeGauge). %% Cleanup actions after completing analysis %% (reactivate `analyze` button, etc.). analysis_cleanup() -> enableMenuItems(), AnalyzeGauge = ref_lookup(?ANALYZE_GAUGE), stop_pulsing(AnalyzeGauge), AnalStopSizer = ref_lookup(?ANAL_STOP_SIZER), Parent = wxWindow:getParent(AnalyzeGauge), AnalyzeButton = wxButton:new(Parent, ?ANALYZE, [{label, "Ana&lyze"}]), ref_add(?ANALYZE, AnalyzeButton), wxSizer:replace(AnalStopSizer, AnalyzeGauge, AnalyzeButton), wxWindow:destroy(AnalyzeGauge), wxMenuItem:enable(ref_lookup(?STOP_MENU_ITEM)), try StopGauge = ref_lookup(?STOP_GAUGE), stop_pulsing(StopGauge), StopButton = wxButton:new(Parent, ?STOP, [{label, "&Stop"}]), ref_add(?STOP, StopButton), wxSizer:replace(AnalStopSizer, StopGauge, StopButton), wxWindow:destroy(StopGauge) catch error:badarg -> continue end, wxSizer:layout(AnalStopSizer). checkDuplicates(OldFiles, NewFiles) -> OldBase = [concuerror_util:get_module_name(O) || O <- OldFiles], NewBase = [concuerror_util:get_module_name(N) || N <- NewFiles], IBase = sets:intersection(sets:from_list(OldBase), sets:from_list(NewBase)), case sets:size(IBase) of 0 -> false; _ -> sets:to_list(IBase) end. start_pulsing(Gauge) -> Env = wx:get_env(), Pid = spawn(fun() -> wx:set_env(Env), pulse(Gauge) end), [Hash] = io_lib:format("~c", [erlang:phash2(Gauge)]), Reg = list_to_atom("_._GP_" ++ Hash), register(Reg, Pid). stop_pulsing(Gauge) -> [Hash] = io_lib:format("~c", [erlang:phash2(Gauge)]), Reg = list_to_atom("_._GP_" ++ Hash), Reg ! stop. pulse(Gauge) -> wxGauge:pulse(Gauge), receive stop -> ok after 200 -> pulse(Gauge) end. %% Dialog prompting the user to insert function arguments (valid erlang terms %% without the terminating `.`). argDialog(Parent, Argnum) -> Dialog = wxDialog:new(Parent, ?wxID_ANY, "Enter arguments"), TopSizer = wxBoxSizer:new(?wxVERTICAL), Box = wxStaticBox:new(Dialog, ?wxID_ANY, "Arguments"), InSizer = wxStaticBoxSizer:new(Box, ?wxVERTICAL), Refs = addArgs(Dialog, InSizer, 0, Argnum, []), ButtonSizer = wxBoxSizer:new(?wxHORIZONTAL), _ = wxSizer:add(ButtonSizer, wxButton:new(Dialog, ?wxID_OK), [{proportion, 0}, {flag, ?wxRIGHT}, {border, 5}]), _ = wxSizer:add(ButtonSizer, wxButton:new(Dialog, ?wxID_CANCEL), [{proportion, 0}, {flag, ?wxLEFT}, {border, 5}]), _ = wxSizer:add(TopSizer, InSizer, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), _ = wxSizer:add(TopSizer, ButtonSizer, [{proportion, 0}, {flag, ?wxALIGN_CENTER bor ?wxRIGHT bor ?wxLEFT bor ?wxBOTTOM}, {border, 10}]), wxWindow:setSizer(Dialog, TopSizer), _ = wxSizer:fit(TopSizer, Dialog), case wxDialog:showModal(Dialog) of ?wxID_OK -> clearProbs(), ValResult = validateArgs(0, Refs, [], ?ERROR_TEXT), wxDialog:destroy(Dialog), case ValResult of {ok, _Args} = Ok -> Ok; _Other -> argDialog(Parent, Argnum) end; _Other -> wxDialog:destroy(Dialog), continue end. %% Preferences dialog. prefsDialog(Parent) -> %% Get current preferences. PrebEnabled = ref_lookup(?PREF_PREB_ENABLED), PrebBound = ref_lookup(?PREF_PREB_BOUND), %% Set up sizers and components. Dialog = wxDialog:new(Parent, ?wxID_ANY, "Preferences"), TopSizer = wxBoxSizer:new(?wxVERTICAL), %% Preemption bounding options. PrebBox = wxStaticBox:new(Dialog, ?wxID_ANY, "Preemption bounding"), PrebBoxSizer = wxStaticBoxSizer:new(PrebBox, ?wxVERTICAL), HorizSizer1 = wxBoxSizer:new(?wxHORIZONTAL), %% Semi-hack: Custom width, default height. PrebEnabledCheckBox = wxCheckBox:new(Dialog, ?PREB_ENABLED_CBOX, "", [{style, ?wxALIGN_RIGHT}]), ref_add(?PREB_ENABLED_CBOX, PrebEnabledCheckBox), wxCheckBox:setValue(PrebEnabledCheckBox, PrebEnabled), _ = wxSizer:add(HorizSizer1, wxStaticText:new(Dialog, ?wxID_ANY, "Enable preemption bounding:"), [{proportion, 1}, {flag, ?wxALIGN_CENTER bor ?wxALL}, {border, 0}]), _ = wxSizer:add(HorizSizer1, PrebEnabledCheckBox, [{proportion, 0}, {flag, ?wxALIGN_CENTER bor ?wxALL}, {border, 0}]), HorizSizer2 = wxBoxSizer:new(?wxHORIZONTAL), PrebBoundSpinCtrl = wxSpinCtrl:new(Dialog, [{id, ?PREB_BOUND_SPIN}, {size, {50, -1}}, {min, 0}, {initial, PrebBound}]), _ = wxSizer:add(HorizSizer2, wxStaticText:new(Dialog, ?wxID_ANY, "Preemption bound:"), [{proportion, 1}, {flag, ?wxALIGN_CENTER bor ?wxALL}, {border, 0}]), _ = wxSizer:add(HorizSizer2, PrebBoundSpinCtrl, [{proportion, 0}, {flag, ?wxALIGN_CENTER bor ?wxALL}, {border, 0}]), _ = wxSizer:add(PrebBoxSizer, HorizSizer1, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), _ = wxSizer:add(PrebBoxSizer, HorizSizer2, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), %% Buttons. ButtonSizer = wxBoxSizer:new(?wxHORIZONTAL), _ = wxSizer:add(ButtonSizer, wxButton:new(Dialog, ?wxID_CANCEL), [{proportion, 3}, {flag, ?wxLEFT}, {border, 0}]), _ = wxSizer:addStretchSpacer(ButtonSizer), _ = wxSizer:add(ButtonSizer, wxButton:new(Dialog, ?wxID_OK, [{label, "&Save"}]), [{proportion, 4}, {flag, ?wxRIGHT}, {border, 0}]), %% Top level sizer. _ = wxSizer:add(TopSizer, PrebBoxSizer, [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, 10}]), _ = wxSizer:add(TopSizer, ButtonSizer, [{proportion, 0}, {flag, ?wxALIGN_CENTER bor ?wxEXPAND bor ?wxRIGHT bor ?wxLEFT bor ?wxBOTTOM}, {border, 10}]), wxWindow:setSizer(Dialog, TopSizer), _ = wxSizer:fit(TopSizer, Dialog), %% Show dialog. case wxDialog:showModal(Dialog) of ?wxID_OK -> %% Save preferences. ref_add(?PREF_PREB_ENABLED, wxCheckBox:getValue(PrebEnabledCheckBox)), ref_add(?PREF_PREB_BOUND, wxSpinCtrl:getValue(PrebBoundSpinCtrl)); _Other -> continue end. %% For now always load default preferences on startup. loadPrefs(Options) -> %% Set initial file load path (used by the module addition dialog). ref_add(?FILE_PATH, "."), %% Disable save as (we don't have any results yet) wxMenuItem:enable(ref_lookup(?SAVEAS_MENU_ITEM), [{enable, false}]), ref_add(?ANALYSIS_RET, undef), %% Set files case lists:keyfind('files', 1, Options) of false -> continue; {'files', Files} -> AbsFiles = [filename:absname(F) || F <- Files], ErlFiles = [F || F <- AbsFiles, concuerror_util:is_erl_source(F)], addListItems(?MODULE_LIST, ErlFiles) end, %% Set include_dirs case lists:keyfind('include', 1, Options) of false -> ref_add(?PREF_INCLUDE, ?DEFAULT_INCLUDE); {'include', Include} -> ref_add(?PREF_INCLUDE, Include) end, %% Set defined macros case lists:keyfind('define', 1, Options) of false -> ref_add(?PREF_DEFINE, ?DEFAULT_DEFINE); {'define', Define} -> ref_add(?PREF_DEFINE, Define) end, %% Set preemption bound case lists:keyfind('preb', 1, Options) of false -> ref_add(?PREF_PREB_ENABLED, true), ref_add(?PREF_PREB_BOUND, ?DEFAULT_PREB); {'preb', inf} -> ref_add(?PREF_PREB_ENABLED, false), ref_add(?PREF_PREB_BOUND, ?DEFAULT_PREB); {'preb', Preb} -> ref_add(?PREF_PREB_ENABLED, true), ref_add(?PREF_PREB_BOUND, Preb) end. %% Do nothing for now. savePrefs() -> ok. %%clearAll() -> %% clearMods(), %% clearFuns(), %% clearSrc(), %% clearLog(), %% clearProbs(), %% clearErrors(), %% clearIleaves(). clearErrors() -> ErrorList = ref_lookup(?ERROR_LIST), wxListBox:setSelection(ErrorList, ?wxNOT_FOUND), wxControlWithItems:clear(ErrorList). clearFuns() -> FunctionList = ref_lookup(?FUNCTION_LIST), wxListBox:setSelection(FunctionList, ?wxNOT_FOUND), wxControlWithItems:clear(FunctionList). clearIleaves() -> IleaveList = ref_lookup(?ILEAVE_LIST), wxListBox:setSelection(IleaveList, ?wxNOT_FOUND), wxControlWithItems:clear(IleaveList). %%clearLog() -> %% LogText = ref_lookup(?LOG_TEXT), %% wxTextCtrl:clear(LogText). clearMods() -> ModuleList = ref_lookup(?MODULE_LIST), wxListBox:setSelection(ModuleList, ?wxNOT_FOUND), wxControlWithItems:clear(ModuleList). clearProbs() -> ErrorText = ref_lookup(?ERROR_TEXT), wxTextCtrl:clear(ErrorText). clearSrc() -> SourceText = ref_lookup(?SOURCE_TEXT), wxStyledTextCtrl:setReadOnly(SourceText, false), wxStyledTextCtrl:clearAll(SourceText), wxStyledTextCtrl:setReadOnly(SourceText, true). disableMenuItems() -> Opts = [{enable, false}], wxMenuItem:enable(ref_lookup(?ANALYZE_MENU_ITEM), Opts), wxMenuItem:enable(ref_lookup(?SAVEAS_MENU_ITEM), Opts). enableMenuItems() -> wxMenuItem:enable(ref_lookup(?ANALYZE_MENU_ITEM)), wxMenuItem:enable(ref_lookup(?SAVEAS_MENU_ITEM)). %% Export dialog exportDialog(Parent) -> Caption = "Save to " ++ ?APP_STRING ++ " file", Wildcard = "Text files |*" ++ ?EXPORT_EXT, DefaultDir = ".", DefaultFile = "", Dialog = wxFileDialog:new(Parent, [{message, Caption}, {defaultDir, DefaultDir}, {defaultFile, DefaultFile}, {wildCard, Wildcard}, {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]), wxFileDialog:setFilename(Dialog, ?EXPORT_FILE), case wxDialog:showModal(Dialog) of ?wxID_OK -> AnalysisRet = ref_lookup(?ANALYSIS_RET), Output = wxFileDialog:getPath(Dialog), concuerror_log:log(0, "Writing output to file ~s... ", [Output]), case concuerror:export(AnalysisRet, Output) of {'error', Msg} -> concuerror_log:log(0, "~s\n", [file:format_error(Msg)]); ok -> concuerror_log:log(0, "done\n") end; _Other -> continue end, wxDialog:destroy(Dialog). %% Return the directory path of selected module. getDirectory() -> ModuleList = ref_lookup(?MODULE_LIST), Path = wxControlWithItems:getStringSelection(ModuleList), Match = re:run(Path, "(?.*)/*?\.erl\$", [dotall, {capture, ['PATH'], list}]), case Match of {match, [Dir]} -> Dir; nomatch -> "" end. %% Return the selected function and arity from the function list. %% The result is returned in the form {Function, Arity}, where Function %% is an atom and Arity is an integer. getFunction() -> FunctionList = ref_lookup(?FUNCTION_LIST), Expr = wxControlWithItems:getStringSelection(FunctionList), Match = re:run(Expr, "(?.*)/(?.*)\$", [dotall, {capture, ['FUN', 'ARITY'], list}]), case Match of {match, [Fun, Arity]} -> {list_to_atom(Fun), list_to_integer(Arity)}; nomatch -> {'', 0} end. %% Return the selected module from the module list. %% The result is an atom. getModule() -> ModuleList = ref_lookup(?MODULE_LIST), Path = wxControlWithItems:getStringSelection(ModuleList), Match = re:run(Path, ".*/(?.*?)\.erl\$", [dotall, {capture, ['MODULE'], list}]), case Match of {match, [Module]} -> list_to_atom(Module); nomatch -> '' end. %% wxControlWithItems:getStrings (function missing from wxErlang lib). getStrings(Ref) -> Count = wxControlWithItems:getCount(Ref), if Count > 0 -> getStrings(Ref, 0, Count, []); true -> [] end. %% Auxiliary function to the above. getStrings(_Ref, Count, Count, Strings) -> Strings; getStrings(Ref, N, Count, Strings) -> String = wxControlWithItems:getString(Ref, N), getStrings(Ref, N + 1, Count, [String|Strings]). %% Refresh selected module (reload source code from disk). %% NOTE: When switching selected modules, no explicit refresh %% by the user is required, because the `command_listbox_selected` %% event for the module-list uses this function. refresh() -> ModuleList = ref_lookup(?MODULE_LIST), case wxListBox:getSelection(ModuleList) of ?wxNOT_FOUND -> continue; _Other -> Module = wxListBox:getStringSelection(ModuleList), %% Scan selected module for exported functions. Funs = concuerror_util:funs(Module, string), setListItems(?FUNCTION_LIST, Funs), %% Update source viewer. SourceText = ref_lookup(?SOURCE_TEXT), wxStyledTextCtrl:setReadOnly(SourceText, false), wxStyledTextCtrl:loadFile(SourceText, Module), wxStyledTextCtrl:setReadOnly(SourceText, true) end. %% Refresh source code (show selected function). refreshFun() -> Module = getModule(), {Function, Arity} = getFunction(), %% Check if a module and function is selected. if Module =/= '', Function =/= '' -> ModuleList = ref_lookup(?MODULE_LIST), case wxListBox:getSelection(ModuleList) of ?wxNOT_FOUND -> continue; _Other -> Path = wxListBox:getStringSelection(ModuleList), %% Scan selected module for selected function line number. Line = concuerror_util:funLine(Path, Function, Arity), %% Update source viewer. SourceText = ref_lookup(?SOURCE_TEXT), wxStyledTextCtrl:gotoLine(SourceText, Line), wxStyledTextCtrl:lineUpExtend(SourceText) end; true -> continue end. %% Remove selected module from module list. remove() -> ModuleList = ref_lookup(?MODULE_LIST), Selection = wxListBox:getSelection(ModuleList), SourceText = ref_lookup(?SOURCE_TEXT), if Selection =:= ?wxNOT_FOUND -> continue; true -> wxControlWithItems:delete(ModuleList, Selection), Count = wxControlWithItems:getCount(ModuleList), if Count =:= 0 -> clearFuns(), wxStyledTextCtrl:setReadOnly(SourceText, false), wxStyledTextCtrl:clearAll(SourceText), wxStyledTextCtrl:setReadOnly(SourceText, true); Selection =:= Count -> wxControlWithItems:setSelection(ModuleList, Selection - 1); true -> wxControlWithItems:setSelection(ModuleList, Selection) end end. %% Kill the analysis process. stop() -> try ?RP_SCHED ! stop_analysis %% wxMenuItem:enable(ref_lookup(?STOP_MENU_ITEM), [{enable, false}]), %% StopButton = ref_lookup(?STOP), %% Parent = wxWindow:getParent(StopButton), %% StopGauge = wxGauge:new(Parent, ?wxID_ANY, 100, %% [{style, ?wxGA_HORIZONTAL}]), %% ref_add(?STOP_GAUGE, StopGauge), %% AnalStopSizer = ref_lookup(?ANAL_STOP_SIZER), %% wxSizer:replace(AnalStopSizer, StopButton, StopGauge), %% wxWindow:destroy(StopButton), %% wxSizer:layout(AnalStopSizer), %% start_pulsing(StopGauge) catch error:badarg -> continue end. %% XXX: hack (send event message to self) send_event_msg_to_self(Id) -> Cmd = #wxCommand{type = command_listbox_selected}, ?RP_GUI ! #wx{id = Id, event = Cmd}, ok. %% Set ListBox (Id) data (remove existing). %%setListData(Id, DataList) -> %% List = ref_lookup(Id), %% setListData_aux(List, DataList, 0). setListData_aux(_List, [], _N) -> ok; setListData_aux(List, [Data|Rest], N) -> wxControlWithItems:setClientData(List, N, Data), setListData_aux(List, Rest, N + 1). %% Set ListBox (Id) items (remove existing). setListItems(Id, Items) -> if Items =/= [], Items =/= [[]] -> List = ref_lookup(Id), wxListBox:set(List, Items), wxControlWithItems:setSelection(List, 0); true -> continue end. %% Show detailed interleaving information about the selected error. show_details() -> ErrorList = ref_lookup(?ERROR_LIST), IleaveList = ref_lookup(?ILEAVE_LIST), case wxControlWithItems:getSelection(ErrorList) of ?wxNOT_FOUND -> continue; Id -> wxControlWithItems:clear(IleaveList), Ticket = wxControlWithItems:getClientData(ErrorList, Id), setListItems(?ILEAVE_LIST, concuerror_ticket:details_to_strings(Ticket)), clearProbs(), Error = concuerror_ticket:get_error(Ticket), wxTextCtrl:appendText(ref_lookup(?ERROR_TEXT), concuerror_error:long(Error)) end. %% Validate user provided function arguments. %% The arguments are first scanned and then parsed to ensure that they %% represent valid erlang terms. %% Returns {ok, ListOfArgs} if everything is valid, else 'error' is returned %% and error messages are written to the log. validateArgs(_I, [], Args, _ErrorId) -> {ok, lists:reverse(Args)}; validateArgs(I, [Ref|Refs], Args, ErrorId) -> String = wxTextCtrl:getValue(Ref) ++ ".", case erl_scan:string(String) of {ok, T, _} -> case erl_parse:parse_term(T) of {ok, Arg} -> validateArgs(I + 1, Refs, [Arg|Args], ErrorId); {error, {_, _, Info}} -> wxTextCtrl:appendText(ref_lookup(?ERROR_TEXT), io_lib:format("Arg ~p - ~s~n", [I + 1, Info])), error end; {error, {_, _, Info}, _} -> wxTextCtrl:appendText(ref_lookup(?ERROR_TEXT), Info ++ "\n"), error end. %%%---------------------------------------------------------------------- %%% Main event loop %%%---------------------------------------------------------------------- loop() -> receive %% -------------------- Button handlers -------------------- %% #wx{id = ?ADD, event = #wxCommand{type = command_button_clicked}} -> Frame = ref_lookup(?FRAME), addDialog(Frame), send_event_msg_to_self(?MODULE_LIST), loop(); #wx{id = ?ANALYZE, event = #wxCommand{type = command_button_clicked}} -> analyze_proc(), loop(); #wx{id = ?CLEAR, event = #wxCommand{type = command_button_clicked}} -> clearMods(), clearFuns(), clearSrc(), loop(); #wx{id = ?REMOVE, event = #wxCommand{type = command_button_clicked}} -> remove(), loop(); #wx{id = ?STOP, event = #wxCommand{type = command_button_clicked}} -> stop(), loop(); #wx{id = ?REFRESH, event = #wxCommand{type = command_button_clicked}} -> refresh(), loop(); %% -------------------- Listbox handlers --------------------- %% #wx{id = ?ERROR_LIST, event = #wxCommand{type = command_listbox_doubleclicked}} -> %% do nothing loop(); #wx{id = ?ERROR_LIST, event = #wxCommand{type = command_listbox_selected}} -> show_details(), send_event_msg_to_self(?ILEAVE_LIST), loop(); #wx{id = ?ILEAVE_LIST, event = #wxCommand{type = command_listbox_doubleclicked}} -> %% do nothing loop(); #wx{id = ?ILEAVE_LIST, event = #wxCommand{type = command_listbox_selected}} -> %% do nothing loop(); #wx{id = ?FUNCTION_LIST, event = #wxCommand{type = command_listbox_doubleclicked}} -> analyze_proc(), loop(); #wx{id = ?FUNCTION_LIST, event = #wxCommand{type = command_listbox_selected}} -> refreshFun(), loop(); #wx{id = ?MODULE_LIST, event = #wxCommand{type = command_listbox_doubleclicked}} -> %% do nothing loop(); #wx{id = ?MODULE_LIST, event = #wxCommand{type = command_listbox_selected}} -> refresh(), send_event_msg_to_self(?FUNCTION_LIST), loop(); %% -------------------- Menu handlers -------------------- %% #wx{id = ?ABOUT, event = #wxCommand{type = command_menu_selected}} -> Caption = "About" ++ ?APP_STRING, Frame = ref_lookup(?FRAME), Dialog = wxMessageDialog:new(Frame, ?INFO_MSG, [{style, ?wxOK}, {caption, Caption}]), wxDialog:showModal(Dialog), wxWindow:destroy(Dialog), loop(); #wx{id = ?ADD, event = #wxCommand{type = command_menu_selected}} -> Frame = ref_lookup(?FRAME), addDialog(Frame), send_event_msg_to_self(?MODULE_LIST), loop(); #wx{id = ?ANALYZE, event = #wxCommand{type = command_menu_selected}} -> analyze_proc(), loop(); #wx{id = ?CLEAR, event = #wxCommand{type = command_menu_selected}} -> clearMods(), clearFuns(), clearSrc(), loop(); #wx{id = ?EXIT, event = #wxCommand{type = command_menu_selected}} -> ok; #wx{id = ?SAVEAS, event = #wxCommand{type = command_menu_selected}} -> Frame = ref_lookup(?FRAME), exportDialog(Frame), loop(); #wx{id = ?PREFS, event = #wxCommand{type = command_menu_selected}} -> Frame = ref_lookup(?FRAME), prefsDialog(Frame), loop(); #wx{id = ?REFRESH, event = #wxCommand{type = command_menu_selected}} -> refresh(), send_event_msg_to_self(?FUNCTION_LIST), loop(); #wx{id = ?REMOVE, event = #wxCommand{type = command_menu_selected}} -> remove(), loop(); #wx{id = ?STOP, event = #wxCommand{type = command_menu_selected}} -> stop(), loop(); #wx{id = ?THEME_LIGHT, event = #wxCommand{type = command_menu_selected}} -> SourceText = ref_lookup(?SOURCE_TEXT), setupSourceText(SourceText, light), loop(); #wx{id = ?THEME_DARK, event = #wxCommand{type = command_menu_selected}} -> SourceText = ref_lookup(?SOURCE_TEXT), setupSourceText(SourceText, dark), loop(); %% -------------------- Misc handlers -------------------- %% %% Every time a splitter sash changes its position, refresh the whole %% window to avoid annoying artifacts from the previous position of the %% sash. #wx{event = #wxSplitter{type = command_splitter_sash_pos_changed}} -> Frame = ref_lookup(?FRAME), wxWindow:refresh(Frame), loop(); #wx{event = #wxClose{type = close_window}} -> ok; %% Ignore normal 'EXIT' messages from linked processes. %% (Added to ignore exit messages coming from calls to compile:file %% and compile:forms) {'EXIT', _Pid, normal} -> loop(); %% -------------------- Catchall -------------------- %% Other -> io:format("main loop unimplemented: ~p~n", [Other]), loop() end. ================================================ FILE: resources/old_source/concuerror_instr.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Instrumenter %%%---------------------------------------------------------------------- -module(concuerror_instr). -export([delete_and_purge/1, instrument_and_compile/2, load/1, new_module_name/1, check_module_name/3, old_module_name/1, delete_temp_files/1]). -export_type([macros/0]). -include("gen.hrl"). -include("instr.hrl"). %%%---------------------------------------------------------------------- %%% Debug %%%---------------------------------------------------------------------- %%-define(PRINT, true). -ifdef(PRINT). -define(print(S_), io:put_chars(erl_prettypr:format(S_))). -else. -define(print(S_), ok). -endif. %%%---------------------------------------------------------------------- %%% Types %%%---------------------------------------------------------------------- -type mfb() :: {module(), file:filename(), binary()}. -type macros() :: [{atom(), term()}]. %%%---------------------------------------------------------------------- %%% Instrumentation utilities %%%---------------------------------------------------------------------- %% --------------------------- %% Delete and purge all modules. -spec delete_and_purge(concuerror:options()) -> 'ok'. delete_and_purge(_Options) -> %% Unload and purge modules. ModsToPurge = [check_module_name(IM, none, 0) || {IM}<-ets:tab2list(?NT_INSTR_MODS)], Fun = fun (M) -> code:purge(M), code:delete(M) end, lists:foreach(Fun, ModsToPurge), %% Delete ?NT_INSTR_MODS, ?NT_INSTR_BIFS, %% ?NT_INSTR_IGNORED and ?NT_INSTR tables. ets:delete(?NT_INSTR_MODS), ets:delete(?NT_INSTR_BIFS), ets:delete(?NT_INSTR_IGNORED), ets:delete(?NT_INSTR), ok. %% --------------------------- %% Rename a module for the instrumentation. %% 1. Don't rename `concuerror_*' modules %% 2. Don't rename `ignored' modules %% 3. Don't rename `BIFS'. %% 4. If module is instrumented rename it. %% 5. If we are in `fail_uninstrumented' mode rename all modules. -spec check_module_name(module() | {module(),term()}, atom(), non_neg_integer()) -> module() | {module(), term()}. check_module_name({Module, Term}, Function, Arity) -> {check_module_name(Module, Function, Arity), Term}; check_module_name(Module, Function, Arity) -> Conc_Module = try atom_to_list(Module) of ("concuerror_" ++ _Rest) -> true; _Other -> false catch %% In case atom_to_list fail, we don't want to rename the module error:badarg -> true end, Rename = (not Conc_Module) andalso (not ets:member(?NT_INSTR_IGNORED, Module)) andalso (not ets:member(?NT_INSTR_BIFS, {Module, Function, Arity})) andalso (ets:member(?NT_INSTR_MODS, Module) orelse ets:lookup_element(?NT_INSTR, ?FAIL_BB, 2)), case Rename of true -> new_module_name(Module); false -> Module end. -spec new_module_name(atom() | string()) -> atom(). new_module_name(StrModule) when is_list(StrModule) -> %% Check that module is not already renamed. case StrModule of (?INSTR_PREFIX ++ _OldModule) -> %% No need to rename it list_to_atom(StrModule); _OldModule -> list_to_atom(?INSTR_PREFIX ++ StrModule) end; new_module_name(Module) -> new_module_name(atom_to_list(Module)). -spec old_module_name(atom()) -> atom(). old_module_name(NewModule) -> case atom_to_list(NewModule) of (?INSTR_PREFIX ++ OldModule) -> list_to_atom(OldModule); _Module -> NewModule end. %% --------------------------- %% @spec instrument_and_compile(Files::[file:filename()], concuerror:options()) %% -> {'ok', [mfb()]} | 'error' %% @doc: Instrument and compile a list of files. %% %% Each file is first validated (i.e. checked whether it will compile %% successfully). If no errors are encountered, the file gets instrumented and %% compiled. If these actions are successfull, the function returns `{ok, Bin}', %% otherwise `error' is returned. No `.beam' files are produced. -spec instrument_and_compile([file:filename()], concuerror:options()) -> {'ok', [mfb()]} | 'error'. instrument_and_compile(Files, Options) -> Includes = case lists:keyfind('include', 1, Options) of {'include', I} -> I; false -> ?DEFAULT_INCLUDE end, Defines = case lists:keyfind('define', 1, Options) of {'define', D} -> D; false -> ?DEFAULT_DEFINE end, Verbosity = case lists:keyfind('verbose', 1, Options) of {'verbose', V} -> V; false -> ?DEFAULT_VERBOSITY end, FailBB = lists:keymember('fail_uninstrumented', 1, Options), Ignores = case lists:keyfind('ignore', 1, Options) of {'ignore', Igns} -> [{Ign} || Ign <- Igns]; false -> [] end, %% Initialize tables EtsNewOpts = [named_table, public, set, {read_concurrency, true}], ?NT_INSTR_MODS = ets:new(?NT_INSTR_MODS, EtsNewOpts), InstrModules = [{concuerror_util:get_module_name(F)} || F <- Files], ets:insert(?NT_INSTR_MODS, InstrModules), ?NT_INSTR_BIFS = ets:new(?NT_INSTR_BIFS, EtsNewOpts), PredefBifs = [{PBif} || PBif <- ?PREDEF_BIFS], ets:insert(?NT_INSTR_BIFS, PredefBifs), ?NT_INSTR_IGNORED = ets:new(?NT_INSTR_IGNORED, EtsNewOpts), ets:insert(?NT_INSTR_IGNORED, [{erlang},{ets}] ++ Ignores), ?NT_INSTR = ets:new(?NT_INSTR, EtsNewOpts), ets:insert(?NT_INSTR, {?FAIL_BB, FailBB}), %% Create a temp dir to save renamed code case create_tmp_dir() of {ok, DirName} -> ets:insert(?NT_INSTR, {?INSTR_TEMP_DIR, DirName}), concuerror_log:log(0, "Instrumenting files..."), InstrOne = fun(File) -> instrument_and_compile_one(File, Includes, Defines, Verbosity) end, Instrumented = concuerror_util:pmap(InstrOne, Files), MFBs = [ F || {F,_S} <- Instrumented], NumOfLines = lists:sum([S || {_F,S} <- Instrumented]), delete_temp_files(Options), case lists:member('error', MFBs) of true -> concuerror_log:log(0, "\nInstrumenting files... failed\n"), error; false -> case Verbosity of 0 -> concuerror_log:log(0, " done\n"); _ -> concuerror_log:log(0, "\nInstrumenting files (~p total lines of code)" "... done\n", [NumOfLines]) end, {ok, MFBs} end; error -> error end. %% Instrument and compile a single file. instrument_and_compile_one(File, Includes, Defines, Verbosity) -> %% Compilation of original file without emitting code, just to show %% warnings or stop if an error is found, before instrumenting it. concuerror_log:log(1, "\nValidating file ~p...", [File]), OptIncludes = [{i, I} || I <- Includes], OptDefines = [{d, M, V} || {M, V} <- Defines], OptRest = case Verbosity >= 2 of true -> [strong_validation, return, verbose]; false -> [strong_validation, return] end, PreOptions = OptIncludes ++ OptDefines ++ OptRest, %% Compile module. case compile:file(File, PreOptions) of {ok, OldModule, Warnings} -> %% Log warning messages. log_warning_list(Warnings), %% Instrument given source file. concuerror_log:log(1, "\nInstrumenting file ~p... ", [File]), case instrument(OldModule, File, Includes, Defines) of {ok, NewFile, NewForms, NumOfLines} -> concuerror_log:log(1, "\nFile ~p successfully instrumented " "(~p total lines of code).", [File, NumOfLines]), %% Compile instrumented code. %% TODO: More compile options? CompOptions = case Verbosity >= 2 of true -> [binary, report_errors, verbose]; false -> [binary, report_errors] end, case compile:forms(NewForms, CompOptions) of {ok, NewModule, Binary} -> {{NewModule, NewFile, Binary}, NumOfLines}; error -> concuerror_log:log(0, "\nFailed to compile " "instrumented file ~p.", [NewFile]), {error, 0} end; {error, Error} -> concuerror_log:log(0, "\nFailed to instrument " "file ~p: ~p", [File, Error]), {error, 0} end; {error, Errors, Warnings} -> log_error_list(Errors), log_warning_list(Warnings), {error, 0} end. %% --------------------------- -spec load([mfb()]) -> 'ok' | 'error'. load([]) -> ok; load([MFB|Rest]) -> case load_one(MFB) of ok -> load(Rest); error -> error end. load_one({Module, File, Binary}) -> case code:load_binary(Module, File, Binary) of {module, Module} -> ok; {error, Error} -> concuerror_log:log(0, "\nerror\n~p\n", [Error]), error end. %% --------------------------- -spec delete_temp_files(concuerror:options()) -> 'ok'. delete_temp_files(Options) -> %% Delete temp directory (ignore errors). case lists:keymember('keep_temp', 1, Options) of true -> %% Retain temporary files. ok; false -> TmpDir = ets:lookup_element(?NT_INSTR, ?INSTR_TEMP_DIR, 2), {ok, TmpFiles} = file:list_dir(TmpDir), DelFile = fun(F) -> _ = file:delete(filename:join(TmpDir, F)) end, lists:foreach(DelFile, TmpFiles), _ = file:del_dir(TmpDir), ok end. %% --------------------------- instrument(Module, File, Includes, Defines) -> NewIncludes = [filename:dirname(File) | Includes], %% Rename module case rename_module(Module, File) of {ok, NewFile, NumOfLines} -> case epp:parse_file(NewFile, NewIncludes, Defines) of {ok, OldForms} -> %% Remove `type` and `spec` attributes to avoid %% errors due to record expansion below. %% Also rename our module. StrippedForms = strip_attributes(OldForms, []), ExpRecForms = erl_expand_records:module(StrippedForms, []), %% Convert `erl_parse tree` to `abstract syntax tree`. Tree = erl_recomment:recomment_forms(ExpRecForms, []), MapFun = fun(T) -> instrument_toplevel(T) end, Transformed = erl_syntax_lib:map_subtrees(MapFun, Tree), %% Return an `erl_parse-compatible` representation. Abstract = erl_syntax:revert(Transformed), ?print(Abstract), NewForms = erl_syntax:form_list_elements(Abstract), {ok, NewFile, NewForms, NumOfLines}; {error, _} = Error -> Error end; {error, _} = Error -> Error end. %% --------------------------- rename_module(Module, File) -> ModuleStr = atom_to_list(Module), NewModuleStr = atom_to_list(new_module_name(Module)), TmpDir = ets:lookup_element(?NT_INSTR, ?INSTR_TEMP_DIR, 2), NewFile = filename:join(TmpDir, NewModuleStr ++ ".erl"), case file:read_file(File) of {ok, Binary} -> %% Replace the first occurrence of `-module(Module).' Pattern = binary:list_to_bin( "-module(" ++ ModuleStr ++ ")."), Replacement = binary:list_to_bin( "-module(" ++ NewModuleStr ++ ")."), NewBinary = binary:replace(Binary, Pattern, Replacement), %% Count lines of code NewLine = binary:list_to_bin("\n"), Lines = length(binary:matches(NewBinary, NewLine)), %% Write new file in temp directory case file:write_file(NewFile, NewBinary) of ok -> {ok, NewFile, Lines}; Error -> Error end; Error -> Error end. %% --------------------------- %% Create an unique temp directory based on (starting with) Stem. %% A directory name of the form is generated. %% We use this directory to save our renamed code. create_tmp_dir() -> DirName = temp_name("./.conc_temp_"), concuerror_log:log(1, "Create temp dir ~p..\n", [DirName]), case file:make_dir(DirName) of ok -> {ok, DirName}; {error, eexist} -> %% Directory exists, try again create_tmp_dir(); {error, Reason} -> concuerror_log:log(0, "\nerror: ~p\n", [Reason]), error end. temp_name(Stem) -> {A, B, C} = erlang:now(), RandomNum = A bxor B bxor C, Stem ++ integer_to_list(RandomNum). %% --------------------------- %% XXX: Implementation dependent. strip_attributes([], Acc) -> lists:reverse(Acc); strip_attributes([{attribute, _Line, Name, _Misc}=Head | Rest], Acc) -> case lists:member(Name, ?ATTR_STRIP) of true -> strip_attributes(Rest, Acc); false -> strip_attributes(Rest, [Head|Acc]) end; strip_attributes([Head|Rest], Acc) -> strip_attributes(Rest, [Head|Acc]). %% --------------------------- %% Instrument a "top-level" element. %% Of the "top-level" elements, i.e. functions, specs, etc., only functions are %% transformed, so leave everything else as is. instrument_toplevel(Tree) -> case erl_syntax:type(Tree) of function -> instrument_function(Tree); _Other -> Tree end. %% Instrument a function. instrument_function(Tree) -> %% A set of all variables used in the function. Used = erl_syntax_lib:variables(Tree), %% Insert the used set into `used` dictionary. put(?NT_USED, Used), instrument_tree(Tree). %% Instrument a Tree. instrument_tree(Tree) -> MapFun = fun(T) -> instrument_term(T) end, erl_syntax_lib:map(MapFun, Tree). %% Instrument a term. instrument_term(Tree) -> case erl_syntax:type(Tree) of application -> case get_mfa(Tree) of no_instr -> Tree; {rename, Mfa} -> instrument_rename(Mfa); {normal, Mfa} -> instrument_application(Mfa); {var, Mfa} -> instrument_var_application(Mfa) end; infix_expr -> Operator = erl_syntax:infix_expr_operator(Tree), case erl_syntax:operator_name(Operator) of '!' -> instrument_send(Tree); _Other -> Tree end; receive_expr -> instrument_receive(Tree); underscore -> new_underscore_variable(); _Other -> Tree end. %% Return {ModuleAtom, FunctionAtom, [ArgTree]} for a function call that %% is going to be instrumented or 'no_instr' otherwise. get_mfa(Tree) -> Qualifier = erl_syntax:application_operator(Tree), ArgTrees = erl_syntax:application_arguments(Tree), case erl_syntax:type(Qualifier) of atom -> Function = erl_syntax:atom_value(Qualifier), needs_instrument(Function, ArgTrees); module_qualifier -> ModTree = erl_syntax:module_qualifier_argument(Qualifier), FunTree = erl_syntax:module_qualifier_body(Qualifier), case erl_syntax:type(ModTree) =:= atom andalso erl_syntax:type(FunTree) =:= atom of true -> Module = erl_syntax:atom_value(ModTree), Function = erl_syntax:atom_value(FunTree), needs_instrument(Module, Function, ArgTrees); false -> {var, {ModTree, FunTree, ArgTrees}} end; _Other -> no_instr end. %% Determine whether an auto-exported BIF call needs instrumentation. needs_instrument(Function, ArgTrees) -> Arity = length(ArgTrees), case lists:member({Function, Arity}, ?INSTR_ERL_FUN) of true -> {normal, {erlang, Function, ArgTrees}}; false -> no_instr end. %% Determine whether a `foo:bar(...)` call needs instrumentation. needs_instrument(Module, Function, ArgTrees) -> Arity = length(ArgTrees), case lists:member({Module, Function, Arity}, ?INSTR_MOD_FUN) of true -> {normal, {Module, Function, ArgTrees}}; false -> {rename, {Module, Function, ArgTrees}} end. instrument_application({erlang, Function, ArgTrees}) -> RepMod = erl_syntax:atom(?REP_MOD), RepFun = erl_syntax:atom(list_to_atom("rep_" ++ atom_to_list(Function))), erl_syntax:application(RepMod, RepFun, ArgTrees); instrument_application({Module, Function, ArgTrees}) -> RepMod = erl_syntax:atom(?REP_MOD), RepFun = erl_syntax:atom(list_to_atom("rep_" ++ atom_to_list(Module) ++ "_" ++ atom_to_list(Function))), erl_syntax:application(RepMod, RepFun, ArgTrees). instrument_var_application({ModTree, FunTree, ArgTrees}) -> RepMod = erl_syntax:atom(?REP_MOD), RepFun = erl_syntax:atom(rep_var), ArgList = erl_syntax:list(ArgTrees), erl_syntax:application(RepMod, RepFun, [ModTree, FunTree, ArgList]). instrument_rename({Module, Function, ArgTrees}) -> Arity = length(ArgTrees), RepMod = erl_syntax:atom(check_module_name(Module, Function, Arity)), RepFun = erl_syntax:atom(Function), erl_syntax:application(RepMod, RepFun, ArgTrees). %% Instrument a receive expression. %% ---------------------------------------------------------------------- %% receive %% Patterns -> Actions %% end %% %% is transformed into %% %% ?REP_MOD:rep_receive(Fun), %% receive %% NewPatterns -> NewActions %% end %% %% where Fun = fun(Aux) -> %% receive %% NewPatterns -> continue %% [_Fresh -> block] %% after 0 -> %% Aux() %% end %% end %% %% The additional _Fresh -> block pattern is only added, if there %% is no catch-all pattern among the original receive patterns. %% %% For each Pattern-Action pair two new pairs are added: %% - The first pair is added to handle instrumented messages: %% {?INSTR_MSG, Fresh, Pattern} -> %% ?REP_MOD:rep_receive_notify(Fresh, Pattern), %% Action %% %% - The second pair is added to handle uninstrumented messages: %% Pattern -> %% ?REP_MOD:rep_receive_notify(Pattern), %% Action %% ---------------------------------------------------------------------- %% receive %% Patterns -> Actions %% after N -> AfterAction %% end %% %% is transformed into %% %% case N of %% infinity -> ?REP_MOD:rep_receive(Fun), %% receive %% NewPatterns -> NewActions %% end; %% Fresh -> receive %% NewPatterns -> NewActions %% after 0 -> NewAfterAction %% end %% %% That is, if the timeout equals infinity then the expression is %% equivalent to a normal receive expression as above. Otherwise, %% any positive timeout is transformed into 0. %% Pattens and Actions are mapped into NewPatterns and NewActions %% as described previously for the case of a `receive' expression %% with no `after' clause. AfterAction is transformed into %% `?REP_MOD:rep_after_notify(), AfterAction'. %% ---------------------------------------------------------------------- %% receive %% after N -> AfterActions %% end %% %% is transformed into %% %% case N of %% infinity -> ?REP_MOD:rep_receive_block(); %% Fresh -> AfterActions %% end %% ---------------------------------------------------------------------- instrument_receive(Tree) -> %% Get old receive expression's clauses. OldClauses = erl_syntax:receive_expr_clauses(Tree), case OldClauses of [] -> Timeout = erl_syntax:receive_expr_timeout(Tree), Action = erl_syntax:receive_expr_action(Tree), AfterBlock = erl_syntax:block_expr(Action), ModTree = erl_syntax:atom(?REP_MOD), FunTree = erl_syntax:atom(rep_receive_block), Fun = erl_syntax:application(ModTree, FunTree, []), transform_receive_timeout(Fun, AfterBlock, Timeout); _Other -> NewClauses = transform_receive_clauses(OldClauses), %% Create fun(X) -> case X of ... end end. FunVar = new_variable(), CaseClauses = transform_receive_case(NewClauses), Case = erl_syntax:case_expr(FunVar, CaseClauses), FunClause = erl_syntax:clause([FunVar], [], [Case]), FunExpr = erl_syntax:fun_expr([FunClause]), %% Create ?REP_MOD:rep_receive(fun(X) -> ...). Module = erl_syntax:atom(?REP_MOD), Function = erl_syntax:atom(rep_receive), Timeout = erl_syntax:receive_expr_timeout(Tree), HasNoTimeout = Timeout =:= none, HasTimeoutExpr = case HasNoTimeout of true -> erl_syntax:atom(infinity); false -> Timeout end, IgnoreTimeout = case ets:lookup(?NT_OPTIONS, 'ignore_timeout') of [{'ignore_timeout', ITValue}] -> erl_syntax:integer(ITValue); _ -> erl_syntax:atom(infinity) end, RepReceive = erl_syntax:application( Module, Function, [FunExpr, HasTimeoutExpr, IgnoreTimeout]), %% Create new receive expression. NewReceive = erl_syntax:receive_expr(NewClauses), %% Result is begin rep_receive(...), NewReceive end. Block = erl_syntax:block_expr([RepReceive, NewReceive]), case HasNoTimeout of %% Instrument `receive` without `after` part. true -> Block; %% Instrument `receive` with `after` part. false -> Action = erl_syntax:receive_expr_action(Tree), RepMod = erl_syntax:atom(?REP_MOD), RepFun = erl_syntax:atom(rep_after_notify), RepApp = erl_syntax:application(RepMod, RepFun, []), NewAction = [RepApp|Action], %% receive NewPatterns -> NewActions after 0 -> NewAfter end ZeroTimeout = erl_syntax:integer(0), AfterExpr = erl_syntax:receive_expr(NewClauses, ZeroTimeout, NewAction), AfterBlock = erl_syntax:block_expr([RepReceive,AfterExpr]), transform_receive_timeout(Block, AfterBlock, Timeout) end end. transform_receive_case(Clauses) -> Fun = fun(Clause) -> [Pattern] = erl_syntax:clause_patterns(Clause), Guard = erl_syntax:clause_guard(Clause), NewBody = erl_syntax:atom(continue), erl_syntax:clause([Pattern], Guard, [NewBody]) end, NewClauses = lists:map(Fun, Clauses), Pattern = new_underscore_variable(), Body = erl_syntax:atom(block), CatchallClause = erl_syntax:clause([Pattern], [], [Body]), NewClauses ++ [CatchallClause]. transform_receive_clauses(Clauses) -> Trans = fun(P) -> [transform_receive_clause_regular(P), transform_receive_clause_special(P)] end, Fold = fun(Clause, Acc) -> Trans(Clause) ++ Acc end, lists:foldr(Fold, [], Clauses). %% Tranform a clause %% Pattern -> Action %% into %% {Fresh, Pattern} -> ?REP_MOD:rep_receive_notify(Fresh, Pattern), Action transform_receive_clause_regular(Clause) -> [OldPattern] = erl_syntax:clause_patterns(Clause), OldGuard = erl_syntax:clause_guard(Clause), OldBody = erl_syntax:clause_body(Clause), InstrAtom = erl_syntax:atom(?INSTR_MSG), PidVar = new_variable(), CV = new_variable(), NewPattern = [erl_syntax:tuple([InstrAtom, PidVar, CV, OldPattern])], Module = erl_syntax:atom(?REP_MOD), Function = erl_syntax:atom(rep_receive_notify), Arguments = [PidVar, CV, OldPattern], Notify = erl_syntax:application(Module, Function, Arguments), NewBody = [Notify|OldBody], erl_syntax:clause(NewPattern, OldGuard, NewBody). %% Transform a clause %% Pattern -> Action %% into %% Pattern -> ?REP_MOD:rep_receive_notify(Pattern), Action transform_receive_clause_special(Clause) -> [OldPattern] = erl_syntax:clause_patterns(Clause), OldGuard = erl_syntax:clause_guard(Clause), OldBody = erl_syntax:clause_body(Clause), Module = erl_syntax:atom(?REP_MOD), Function = erl_syntax:atom(rep_receive_notify), Arguments = [OldPattern], Notify = erl_syntax:application(Module, Function, Arguments), NewBody = [Notify|OldBody], erl_syntax:clause([OldPattern], OldGuard, NewBody). transform_receive_timeout(InfBlock, FrBlock, Timeout) -> %% Create 'infinity -> ...' clause. InfPattern = erl_syntax:atom(infinity), InfClause = erl_syntax:clause([InfPattern], [], [InfBlock]), %% Create 'Fresh -> ...' clause. FrPattern = new_underscore_variable(), FrClause = erl_syntax:clause([FrPattern], [], [FrBlock]), %% Create 'case Timeout of ...' expression. AfterCaseClauses = [InfClause, FrClause], erl_syntax:case_expr(Timeout, AfterCaseClauses). %% Instrument a Pid ! Msg expression. %% Pid ! Msg is transformed into ?REP_MOD:rep_send(Pid, Msg). instrument_send(Tree) -> Dest = erl_syntax:infix_expr_left(Tree), Msg = erl_syntax:infix_expr_right(Tree), instrument_application({erlang, send, [Dest, Msg]}). %%%---------------------------------------------------------------------- %%% Helper functions %%%---------------------------------------------------------------------- new_variable() -> Used = get(?NT_USED), Fresh = erl_syntax_lib:new_variable_name(Used), put(?NT_USED, sets:add_element(Fresh, Used)), erl_syntax:variable(Fresh). new_underscore_variable() -> Used = get(?NT_USED), new_underscore_variable(Used). new_underscore_variable(Used) -> Fresh1 = erl_syntax_lib:new_variable_name(Used), String = "_" ++ atom_to_list(Fresh1), Fresh2 = list_to_atom(String), case is_fresh(Fresh2, Used) of true -> put(?NT_USED, sets:add_element(Fresh2, Used)), erl_syntax:variable(Fresh2); false -> new_underscore_variable(Used) end. is_fresh(Atom, Set) -> not sets:is_element(Atom, Set). %%%---------------------------------------------------------------------- %%% Logging %%%---------------------------------------------------------------------- %% Log a list of errors, as returned by compile:file/2. log_error_list(List) -> log_list(List, "", 0). %% Log a list of warnings, as returned by compile:file/2. log_warning_list(List) -> log_list(List, "Warning:", 1). %% Log a list of error or warning descriptors, as returned by compile:file/2. log_list(List, Pre, Verbosity) -> Strings = [io_lib:format("\n~s:~p: ~s ~s", [File, Line, Pre, Mod:format_error(Descr)]) || {File, Info} <- List, {Line, Mod, Descr} <- Info], concuerror_log:log(Verbosity, lists:flatten(Strings)), ok. ================================================ FILE: resources/old_source/concuerror_io_server.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2012, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Ilias Tsitsimpis %%% Description : An I/O server %%%---------------------------------------------------------------------- -module(concuerror_io_server). -export([new_group_leader/1, group_leader_sync/1]). %% @spec: new_group_leader(pid()) -> pid() %% @doc: Create a new process to act as group leader. -spec new_group_leader(pid()) -> pid(). new_group_leader(Runner) -> process_flag(trap_exit, true), spawn_link(fun() -> group_leader_process(Runner) end). group_leader_process(Runner) -> group_leader_loop(Runner, infinity, []). group_leader_loop(Runner, Wait, Buf) -> receive {io_request, From, ReplyAs, Req} -> P = process_flag(priority, normal), %% run this part under normal priority always Buf1 = io_request(From, ReplyAs, Req, Buf), process_flag(priority, P), group_leader_loop(Runner, Wait, Buf1); stop -> %% quitting time: make a minimal pause, go low on priority, %% set receive-timeout to zero and schedule out again receive after 2 -> ok end, process_flag(priority, low), group_leader_loop(Runner, 0, Buf); _ -> %% discard any other messages group_leader_loop(Runner, Wait, Buf) after Wait -> %% no more messages and nothing to wait for; we ought to %% have collected all immediately pending output now process_flag(priority, normal), Runner ! {self(), buffer_to_binary(Buf)} end. buffer_to_binary([B]) when is_binary(B) -> B; % avoid unnecessary copying buffer_to_binary(Buf) -> list_to_binary(lists:reverse(Buf)). %% @spec: group_leader_sync(pid()) -> unicode:chardata() %% @doc: Stop the group leader and return it's buffer -spec group_leader_sync(pid()) -> unicode:chardata(). group_leader_sync(G) -> G ! stop, receive {'EXIT', G, normal} -> ok end, receive {G, Buf} -> Buf end. %% Implementation of buffering I/O for group leader processes. (Note that %% each batch of characters is just pushed on the buffer, so it needs to %% be reversed when it is flushed.) io_request(From, ReplyAs, Req, Buf) -> {Reply, Buf1} = io_request(Req, Buf), io_reply(From, ReplyAs, Reply), Buf1. io_reply(From, ReplyAs, Reply) -> From ! {io_reply, ReplyAs, Reply}, ok. io_request({put_chars, Chars}, Buf) -> {ok, [Chars | Buf]}; io_request({put_chars, M, F, As}, Buf) -> try apply(M, F, As) of Chars -> {ok, [Chars | Buf]} catch C:T -> {{error, {C,T,erlang:get_stacktrace()}}, Buf} end; io_request({put_chars, _Enc, Chars}, Buf) -> io_request({put_chars, Chars}, Buf); io_request({put_chars, _Enc, Mod, Func, Args}, Buf) -> io_request({put_chars, Mod, Func, Args}, Buf); io_request({get_chars, _Enc, _Prompt, _N}, Buf) -> {eof, Buf}; io_request({get_chars, _Prompt, _N}, Buf) -> {eof, Buf}; io_request({get_line, _Prompt}, Buf) -> {eof, Buf}; io_request({get_line, _Enc, _Prompt}, Buf) -> {eof, Buf}; io_request({get_until, _Prompt, _M, _F, _As}, Buf) -> {eof, Buf}; io_request({setopts, _Opts}, Buf) -> {ok, Buf}; io_request(getopts, Buf) -> {error, {error, enotsup}, Buf}; io_request({get_geometry,columns}, Buf) -> {error, {error, enotsup}, Buf}; io_request({get_geometry,rows}, Buf) -> {error, {error, enotsup}, Buf}; io_request({requests, Reqs}, Buf) -> io_requests(Reqs, {ok, Buf}); io_request(_, Buf) -> {{error, request}, Buf}. io_requests([R | Rs], {ok, Buf}) -> io_requests(Rs, io_request(R, Buf)); io_requests(_, Result) -> Result. ================================================ FILE: resources/old_source/concuerror_lid.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : LID interface %%%---------------------------------------------------------------------- -module(concuerror_lid). -export([cleanup/1, from_pid/1, fold_pids/2, get_pid/1, mock/1, new/2, start/0, stop/0, to_string/1, root_lid/0, ets_new/1, ref_new/2, lookup_ref_lid/1]). -export_type([lid/0, maybe_lid/0, ets_lid/0, ref_lid/0]). -include("gen.hrl"). %%%---------------------------------------------------------------------- %%% Definitions %%%---------------------------------------------------------------------- %% Information kept in the NT_LID table %% %% lid : The logical identifier of a process. %% pid : The process identifier of a process. %% nch : The number of processes spawned by this process. -record(info, {lid :: lid(), pid :: pid(), nch :: non_neg_integer()}). %% Record element positions, only to be used by ets:update_element/3. -define(POS_LID, 2). -define(POS_PID, 3). -define(POS_NCH, 4). %%%---------------------------------------------------------------------- %%% Types %%%---------------------------------------------------------------------- %% The logical id (LID) for each process reflects the process' logical %% position in the program's "process creation tree" and doesn't change %% between different runs of the same program (as opposed to erlang pids). -type lid() :: string(). -type ets_lid() :: integer(). -type ref_lid() :: integer(). -type maybe_lid() :: lid() | 'not_found'. %%%---------------------------------------------------------------------- %%% User interface %%%---------------------------------------------------------------------- %% Cleanup all information of a process. -spec cleanup(lid()) -> 'ok'. cleanup(Lid) -> [#info{pid = Pid}] = ets:lookup(?NT_LID, Lid), %% Delete LID table entry of Lid. ets:delete(?NT_LID, Lid), %% Delete pid table entry. ets:delete(?NT_PID, Pid), ok. %% Return the LID of process Pid or 'not_found' if mapping not in table. -spec from_pid(term()) -> lid() | 'not_found'. from_pid(Pid) when is_pid(Pid); is_integer(Pid); is_atom(Pid); is_reference(Pid) -> case ets:lookup(?NT_PID, Pid) of [{Pid, Lid}] -> Lid; [{Pid, Lid, _}] -> Lid; [] -> not_found end; from_pid(_Other) -> not_found. %% Fold function Fun over all known processes (by Pid). -spec fold_pids(fun(), term()) -> term(). fold_pids(Fun, InitAcc) -> NewFun = fun(A, Acc) -> case A of {P, _L} -> case is_pid(P) andalso is_process_alive(P) of true -> Fun(P, Acc); false -> Acc end; _ -> Acc end end, ets:foldl(NewFun, InitAcc, ?NT_PID). %% Return a mock LID (only to be used with to_string for now). -spec mock(integer()) -> lid(). mock(Seed) -> lists:flatten(io_lib:format("~p", [Seed])). %% "Register" a new process using its pid (Pid) and its parent's LID (Parent). %% If called without a `noparent' argument, "register" the first process. %% Return the LID of the newly "registered" process. -spec new(pid(), lid() | 'noparent') -> lid(). new(Pid, Parent) -> Lid = case Parent of noparent -> root_lid(); _Other -> Children = get_children(Parent), set_children(Parent, Children + 1), next_lid(Parent, Children) end, ets:insert(?NT_LID, #info{lid = Lid, pid = Pid, nch = 0}), ets:insert(?NT_PID, {Pid, Lid}), Lid. -spec ets_new(ets:tid()) -> ets_lid(). ets_new(Tid) -> N = ets:update_counter(?NT_PID, ets_counter, 1), true = ets:insert(?NT_PID, {Tid, N}), N. -spec ref_new(lid(), reference()) -> ref_lid(). ref_new(Lid, Ref) -> N = ets:update_counter(?NT_PID, ref_counter, 1), true = ets:insert(?NT_PID, {Ref, N, Lid}), N. -spec lookup_ref_lid(reference()) -> maybe_lid(). lookup_ref_lid(RefLid) -> case catch ets:lookup_element(?NT_PID, RefLid, 3) of {'EXIT', {badarg, _Exception}} -> not_found; Lid -> Lid end. %% Initialize LID tables. %% Must be called before any other call to lid interface functions. -spec start() -> 'ok'. start() -> %% Table for storing process info. ?NT_LID = ets:new(?NT_LID, [named_table, {keypos, 2}]), %% Table for reverse lookup (Pid -> Lid) purposes. ?NT_PID = ets:new(?NT_PID, [named_table]), true = ets:insert(?NT_PID, {ets_counter, 0}), true = ets:insert(?NT_PID, {ref_counter, 0}), ok. %% Clean up LID tables. -spec stop() -> 'ok'. stop() -> ets:delete(?NT_LID), ets:delete(?NT_PID), ok. %%%---------------------------------------------------------------------- %%% Getter and setter functions %%%---------------------------------------------------------------------- %% Return the erlang pid of the process Lid. -spec get_pid(lid()) -> pid() | 'not_found'. get_pid(Lid) -> case ets:lookup(?NT_LID, Lid) of [] -> not_found; [#info{pid = Pid}] -> Pid end. get_children(Lid) -> [#info{nch = Children}] = ets:lookup(?NT_LID, Lid), Children. set_children(Lid, Children) -> ets:update_element(?NT_LID, Lid, {?POS_NCH, Children}). %%%---------------------------------------------------------------------- %%% Helper functions %%%---------------------------------------------------------------------- -spec root_lid() -> lid(). root_lid() -> "1". %% Create new lid from parent and its number of children. next_lid(ParentLid, Children) -> lists:flatten(io_lib:format("~s.~p", [ParentLid, Children+1])). -spec to_string(lid() | {dead, lid()}) -> string(). to_string({dead, Lid}) -> lists:flatten(io_lib:format("~s (dead)",[to_string(Lid)])); to_string({name, Name}) when is_atom(Name)-> lists:flatten(io_lib:format("named '~p'", [Name])); to_string(Lid) -> "P" ++ Lid. ================================================ FILE: resources/old_source/concuerror_log.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Logging and error reporting interface %%%---------------------------------------------------------------------- -module(concuerror_log). %% Non gen_evt exports. -export([internal/1, internal/2]). %% Log API exports. -export([attach/2, detach/2, start/0, stop/0, log/2, log/3, progress/1, reset/0]). %% Log callback exports. -export([init/1, terminate/2, handle_call/2, handle_info/2, handle_event/2, code_change/3]). -behaviour(gen_event). -include("gen.hrl"). %%%---------------------------------------------------------------------- %%% Callback types %%%---------------------------------------------------------------------- -type progress_type() :: {'new', pos_integer(), non_neg_integer()} | {'error', concuerror_ticket:ticket()}. -type event() :: {'msg', string(), non_neg_integer()} | {'progress', progress_type()}. -type state() :: []. -export_type([progress_type/0, event/0, state/0]). %%%---------------------------------------------------------------------- %%% Non gen_evt functions. %%%---------------------------------------------------------------------- %% @spec internal(string()) -> no_return() %% @doc: Print an internal error message and halt. -spec internal(string()) -> no_return(). internal(String) -> internal(String, []). %% @spec internal(string(), [term()]) -> no_return() %% @doc: Like `internal/1', but prints a formatted message using arguments. -spec internal(string(), [term()]) -> no_return(). internal(String, Args) -> InitPid = whereis(init), group_leader(InitPid, self()), io:format("(Internal) " ++ String, Args), halt(?RET_INTERNAL_ERROR). %%%---------------------------------------------------------------------- %%% API functions %%%---------------------------------------------------------------------- %% Attach an event handler module. -spec attach(module(), term()) -> 'ok' | {'EXIT', term()}. attach(Mod, Args) -> gen_event:add_handler(concuerror_log, Mod, Args). %% Detach an event handler module. -spec detach(module(), term()) -> 'ok' | {'error', 'module_not_found'}. detach(Mod, Args) -> gen_event:delete_handler(concuerror_log, Mod, Args). %% @spec start(atom(), term()) -> {'ok', pid()} | %% {'error', {'already_started', pid()}} %% @doc: Starts the log event manager. %% %% `Mod' is the module containing the callback functions. %% `Args' are the arguments given to the callback function `Mod:init/1'. -spec start() -> {'ok', pid()} | {'error', {'already_started', pid()}}. start() -> gen_event:start({local, concuerror_log}). %% @spec stop() -> 'ok' %% @doc: Terminates the log event manager. -spec stop() -> 'ok'. stop() -> gen_event:stop(concuerror_log). %% @spec log(non_neg_integer(), string()) -> 'ok' %% @doc: Logs a string. -spec log(non_neg_integer(), string()) -> 'ok'. log(Verbosity, String) when is_list(String) -> log(Verbosity, String, []). %% @spec log(non_neg_integer(), string(), [term()]) -> 'ok' %% @doc: Logs a formatted string. -spec log(non_neg_integer(), string(), [term()]) -> 'ok'. log(Verbosity, String, Args) when is_list(String), is_list(Args) -> LogMsg = io_lib:format(String, Args), gen_event:notify(concuerror_log, {msg, LogMsg, Verbosity}). %% @spec progress({new, pos_integer(), non_neg_integer() %% | {error, concuerror_ticket:ticket()}) -> 'ok' %% @doc: Shows analysis progress. -spec progress({new, pos_integer(), non_neg_integer()} | {error, concuerror_ticket:ticket()}) -> ok. progress({new, _RunCnt, _SBlocked}=New) -> %% Start a new interleaving gen_event:notify(concuerror_log, {progress, New}); progress({error, _Ticket}=Error) -> %% Encountered error (Ticket) gen_event:notify(concuerror_log, {progress, Error}). %% @spec reset() -> 'ok' %% @doc: Reset logger's internal state. -spec reset() -> 'ok'. reset() -> gen_event:notify(concuerror_log, 'reset'). %%%---------------------------------------------------------------------- %%% Callback functions %%%---------------------------------------------------------------------- -spec init(term()) -> {'ok', state()}. init(_State) -> {ok, []}. -spec terminate(term(), state()) -> 'ok'. terminate(_Reason, _State) -> ok. -spec handle_event(event(), state()) -> {'ok', state()}. handle_event({msg, String, _MsgVerb}, State) -> io:format("~s", [String]), {ok, State}; handle_event({progress, _Progress}, State) -> {ok, State}; handle_event('reset', _State) -> {ok, []}. -spec code_change(term(), term(), term()) -> no_return(). code_change(_OldVsn, _State, _Extra) -> internal("~p:~p: code_change~n", [?MODULE, ?LINE]). -spec handle_info(term(), term()) -> no_return(). handle_info(_Info, _State) -> internal("~p:~p: handle_info~n", [?MODULE, ?LINE]). -spec handle_call(term(), term()) -> no_return(). handle_call(_Request, _State) -> internal("~p:~p: handle_call~n", [?MODULE, ?LINE]). ================================================ FILE: resources/old_source/concuerror_proc_action.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Author : Alkis Gotovos %%% Description : Process action interface %%%---------------------------------------------------------------------- -module(concuerror_proc_action). -export([to_string/1]). -export_type([proc_action/0]). %%%---------------------------------------------------------------------- %%% Definitions %%%---------------------------------------------------------------------- %% Printing depth of terms like messages or exit reasons. -define(PRINT_DEPTH, 4). -define(PRINT_DEPTH_EXIT, 10). %%%---------------------------------------------------------------------- %%% Types %%%---------------------------------------------------------------------- -type spawn_opt_opts() :: ['link' | 'monitor']. %% Tuples providing information about a process' action. -type proc_action() :: {'after', concuerror_lid:lid()} | {'block', concuerror_lid:lid()} | {'demonitor', concuerror_lid:lid(), concuerror_lid:maybe_lid()} | {'exit', concuerror_lid:lid(), term()} | {'exit_2', concuerror_lid:lid(), concuerror_lid:lid(), term()} | {'fun_exit', concuerror_lid:lid(), concuerror_lid:maybe_lid(), term()} | {'halt', concuerror_lid:lid()} | {'halt', concuerror_lid:lid(), non_neg_integer() | string()} | {'is_process_alive', concuerror_lid:lid(), concuerror_lid:maybe_lid()} | {'link', concuerror_lid:lid(), concuerror_lid:maybe_lid()} | {'monitor', concuerror_lid:lid(), concuerror_lid:maybe_lid()} | {'process_flag', concuerror_lid:lid(), 'trap_exit', boolean()} | {'receive', concuerror_lid:lid(), concuerror_lid:lid(), term()} | {'receive_no_instr', concuerror_lid:lid(), term()} | {'register', concuerror_lid:lid(), atom(), concuerror_lid:lid()} | {'send', concuerror_lid:lid(), concuerror_lid:maybe_lid(), term()} | {'send_after', concuerror_lid:lid(), concuerror_lid:maybe_lid(), term()} | {'start_timer', concuerror_lid:lid(), concuerror_lid:maybe_lid(), term()} | {'spawn', concuerror_lid:maybe_lid(), concuerror_lid:lid()} | {'spawn_link', concuerror_lid:maybe_lid(), concuerror_lid:lid()} | {'spawn_monitor', concuerror_lid:maybe_lid(), concuerror_lid:lid()} | {'spawn_opt', concuerror_lid:maybe_lid(), concuerror_lid:lid(), spawn_opt_opts()} | {'unlink', concuerror_lid:lid(), concuerror_lid:maybe_lid()} | {'unregister', concuerror_lid:lid(), atom()} | {'port_command', concuerror_lid:lid(), port()} | {'port_control', concuerror_lid:lid(), port()} | {'whereis', concuerror_lid:lid(), atom(), concuerror_lid:maybe_lid()}. %%%---------------------------------------------------------------------- %%% User interface %%%---------------------------------------------------------------------- -spec to_string(proc_action()) -> string(). to_string({'after', Proc}) -> io_lib:format("Process ~s receives no matching messages", [concuerror_lid:to_string(Proc)]); to_string({block, Proc}) -> io_lib:format("Process ~s blocks", [concuerror_lid:to_string(Proc)]); to_string({demonitor, Proc, not_found}) -> io_lib:format("Process ~s demonitors nonexisting process", [concuerror_lid:to_string(Proc)]); to_string({demonitor, Proc1, Proc2}) -> io_lib:format("Process ~s demonitors process ~s", [concuerror_lid:to_string(Proc1), concuerror_lid:to_string(Proc2)]); to_string({exit, Proc, Reason}) -> io_lib:format("Process ~s exits (~P)", [concuerror_lid:to_string(Proc), Reason, ?PRINT_DEPTH_EXIT]); to_string({exit_2, From, To, Reason}) -> io_lib:format("Process ~s sends an exit signal to ~p (~P)", [concuerror_lid:to_string(From), concuerror_lid:to_string(To), Reason, ?PRINT_DEPTH_EXIT]); to_string({fun_exit, Proc, not_found, Reason}) -> io_lib:format("Process ~s sends exit signal (~W) to nonexisting process", [concuerror_lid:to_string(Proc), Reason, ?PRINT_DEPTH]); to_string({fun_exit, Proc, Target, Reason}) -> io_lib:format("Process ~s sends exit signal (~p) to process ~s", [concuerror_lid:to_string(Proc), Reason, concuerror_lid:to_string(Target)]); to_string({halt, Proc}) -> io_lib:format("Process ~s halts the system", [concuerror_lid:to_string(Proc)]); to_string({halt, Proc, Status}) -> io_lib:format("Process ~s halts the system with status ~p", [concuerror_lid:to_string(Proc), Status]); to_string({is_process_alive, Proc, not_found}) -> io_lib:format("Process ~s checks if nonexisting process is alive", [concuerror_lid:to_string(Proc)]); to_string({is_process_alive, Proc1, Proc2}) -> io_lib:format("Process ~s checks if process ~s is alive", [concuerror_lid:to_string(Proc1), concuerror_lid:to_string(Proc2)]); to_string({link, Proc, not_found}) -> io_lib:format("Process ~s links to nonexisting process", [concuerror_lid:to_string(Proc)]); to_string({link, Proc1, Proc2}) -> io_lib:format("Process ~s links to process ~s", [concuerror_lid:to_string(Proc1), concuerror_lid:to_string(Proc2)]); to_string({monitor, Proc, not_found}) -> io_lib:format("Process ~s monitors nonexisting process", [concuerror_lid:to_string(Proc)]); to_string({monitor, Proc1, Proc2}) -> io_lib:format("Process ~s monitors process ~s", [concuerror_lid:to_string(Proc1), concuerror_lid:to_string(Proc2)]); to_string({'process_flag', Proc, Flag, Value}) -> io_lib:format("Process ~s sets flag `~p` to `~p`", [concuerror_lid:to_string(Proc), Flag, Value]); to_string({'receive', Receiver, Sender, Msg}) -> io_lib:format("Process ~s receives message `~W` from process ~s", [concuerror_lid:to_string(Receiver), Msg, ?PRINT_DEPTH, concuerror_lid:to_string(Sender)]); to_string({'receive_no_instr', Receiver, Msg}) -> io_lib:format("Process ~s receives message `~W` from unknown process", [concuerror_lid:to_string(Receiver), Msg, ?PRINT_DEPTH]); to_string({register, Proc, RegName, RegLid}) -> io_lib:format("Process ~s registers process ~s as `~p`", [concuerror_lid:to_string(Proc), concuerror_lid:to_string(RegLid), RegName]); to_string({send, Sender, not_found, Msg}) -> io_lib:format("Process ~s sends message `~W` to nonexisting process", [concuerror_lid:to_string(Sender), Msg, ?PRINT_DEPTH]); to_string({send, Sender, Receiver, Msg}) -> io_lib:format("Process ~s sends message `~W` to process ~s", [concuerror_lid:to_string(Sender), Msg, ?PRINT_DEPTH, concuerror_lid:to_string(Receiver)]); to_string({send_after, Sender, Receiver, Msg}) -> io_lib:format("Process ~s sends message `~W` to process ~s (send_after emulated as send)", [concuerror_lid:to_string(Sender), Msg, ?PRINT_DEPTH, concuerror_lid:to_string(Receiver)]); to_string({start_timer, Sender, Receiver, Msg}) -> io_lib:format("Process ~s sets a timer, with message `~W` to process ~s (expires immediately)", [concuerror_lid:to_string(Sender), Msg, ?PRINT_DEPTH, concuerror_lid:to_string(Receiver)]); to_string({spawn, not_found, Child}) -> io_lib:format("Unknown process spawns process ~s", [concuerror_lid:to_string(Child)]); to_string({spawn, Parent, Child}) -> io_lib:format("Process ~s spawns process ~s", [concuerror_lid:to_string(Parent), concuerror_lid:to_string(Child)]); to_string({spawn_link, not_found, Child}) -> io_lib:format("Unknown process spawns and links to process ~s", [concuerror_lid:to_string(Child)]); to_string({spawn_link, Parent, Child}) -> io_lib:format("Process ~s spawns and links to process ~s", [concuerror_lid:to_string(Parent), concuerror_lid:to_string(Child)]); to_string({spawn_monitor, not_found, Child}) -> io_lib:format("Unknown process spawns and monitors process ~s", [concuerror_lid:to_string(Child)]); to_string({spawn_monitor, Parent, Child}) -> io_lib:format("Process ~s spawns and monitors process ~s", [concuerror_lid:to_string(Parent), concuerror_lid:to_string(Child)]); to_string({spawn_opt, not_found, Child}) -> io_lib:format("Unknown process spawns process ~s with opts", [concuerror_lid:to_string(Child)]); to_string({spawn_opt, Parent, {Child, _Ref}}) -> io_lib:format("Process ~s spawns process ~s with opts (and monitors)", [concuerror_lid:to_string(Parent), concuerror_lid:to_string(Child)]); to_string({spawn_opt, Parent, Child}) -> io_lib:format("Process ~s spawns process ~s with opts", [concuerror_lid:to_string(Parent), concuerror_lid:to_string(Child)]); to_string({unlink, Proc, not_found}) -> io_lib:format("Process ~s unlinks from nonexisting process", [concuerror_lid:to_string(Proc)]); to_string({unlink, Proc1, Proc2}) -> io_lib:format("Process ~s unlinks from process ~s", [concuerror_lid:to_string(Proc1), concuerror_lid:to_string(Proc2)]); to_string({unregister, Proc, RegName}) -> io_lib:format("Process ~s unregisters process `~p`", [concuerror_lid:to_string(Proc), RegName]); to_string({port_command, Proc, Port}) -> io_lib:format("Process ~s sends data to port ~w", [concuerror_lid:to_string(Proc), Port]); to_string({port_control, Proc, Port}) -> io_lib:format("Process ~s performs control operation on port ~w", [concuerror_lid:to_string(Proc), Port]); to_string({whereis, Proc, RegName, not_found}) -> io_lib:format("Process ~s requests the pid of unregistered " "process `~p` (undefined)", [concuerror_lid:to_string(Proc), RegName]); to_string({whereis, Proc, RegName, RegLid}) -> io_lib:format("Process ~s requests the pid of process `~p` (~s)", [concuerror_lid:to_string(Proc), RegName, concuerror_lid:to_string(RegLid)]); to_string({CallMsg, Proc, Args}) -> io_lib:format("Process ~s: ~p ~p", [concuerror_lid:to_string(Proc), CallMsg, Args]). ================================================ FILE: resources/old_source/concuerror_rep.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Replacement BIFs %%%---------------------------------------------------------------------- -module(concuerror_rep). -export([spawn_fun_wrapper/1, start_target/3, find_my_links/0, find_my_monitors/0]). -export([rep_var/3, rep_apply/3, rep_send/2, rep_send/3]). -export([rep_port_command/2, rep_port_command/3, rep_port_control/3]). -export([rep_spawn/1, rep_spawn/3, rep_spawn_link/1, rep_spawn_link/3, rep_spawn_opt/2, rep_spawn_opt/4]). -export([rep_link/1, rep_unlink/1, rep_spawn_monitor/1, rep_spawn_monitor/3, rep_process_flag/2]). -export([rep_receive/3, rep_receive_block/0, rep_after_notify/0, rep_receive_notify/3, rep_receive_notify/1]). -export([rep_ets_insert_new/2, rep_ets_lookup/2, rep_ets_select_delete/2, rep_ets_insert/2, rep_ets_delete/1, rep_ets_delete/2, rep_ets_match/2, rep_ets_match/3, rep_ets_match_object/2, rep_ets_match_object/3, rep_ets_info/1, rep_ets_info/2, rep_ets_filter/3, rep_ets_match_delete/2, rep_ets_new/2, rep_ets_foldl/3]). -export([rep_register/2, rep_is_process_alive/1, rep_unregister/1, rep_whereis/1]). -export([rep_monitor/2, rep_demonitor/1, rep_demonitor/2]). -export([rep_halt/0, rep_halt/1]). -export([rep_start_timer/3, rep_send_after/3]). -export([rep_exit/2]). -export([rep_eunit/1]). -export([debug_print/1, debug_print/2, debug_apply/3]). -export([uninstrumented_send/2]). -export_type([dest/0]). -include("gen.hrl"). %%%---------------------------------------------------------------------- %%% Definitions and Types %%%---------------------------------------------------------------------- %% Return the calling process' LID. -define(LID_FROM_PID(Pid), concuerror_lid:from_pid(Pid)). %% The destination of a `send' operation. -type dest() :: pid() | port() | atom() | {atom(), node()}. %% Callback function mapping. %% TODO: Automatically generate this? -define(INSTR_MOD_FUN, [{{erlang, demonitor, 1}, fun rep_demonitor/1}, {{erlang, demonitor, 2}, fun rep_demonitor/2}, {{erlang, exit, 2}, fun rep_exit/2}, {{erlang, halt, 0}, fun rep_halt/0}, {{erlang, halt, 1}, fun rep_halt/1}, {{erlang, is_process_alive, 1}, fun rep_is_process_alive/1}, {{erlang, link, 1}, fun rep_link/1}, {{erlang, monitor, 2}, fun rep_monitor/2}, {{erlang, process_flag, 2}, fun rep_process_flag/2}, {{erlang, register, 2}, fun rep_register/2}, {{erlang, send, 2}, fun rep_send/2}, {{erlang, send, 3}, fun rep_send/3}, {{erlang, send_after, 3}, fun rep_send_after/3}, {{erlang, spawn, 1}, fun rep_spawn/1}, {{erlang, spawn, 3}, fun rep_spawn/3}, {{erlang, spawn_link, 1}, fun rep_spawn_link/1}, {{erlang, spawn_link, 3}, fun rep_spawn_link/3}, {{erlang, spawn_monitor, 1}, fun rep_spawn_monitor/1}, {{erlang, spawn_monitor, 3}, fun rep_spawn_monitor/3}, {{erlang, spawn_opt, 2}, fun rep_spawn_opt/2}, {{erlang, spawn_opt, 4}, fun rep_spawn_opt/4}, {{erlang, start_timer, 3}, fun rep_start_timer/3}, {{erlang, unlink, 1}, fun rep_unlink/1}, {{erlang, unregister, 1}, fun rep_unregister/1}, {{erlang, whereis, 1}, fun rep_whereis/1}, {{erlang, apply, 3}, fun rep_apply/3}, {{ets, insert_new, 2}, fun rep_ets_insert_new/2}, {{ets, lookup, 2}, fun rep_ets_lookup/2}, {{ets, select_delete, 2}, fun rep_ets_select_delete/2}, {{ets, insert, 2}, fun rep_ets_insert/2}, {{ets, delete, 1}, fun rep_ets_delete/1}, {{ets, delete, 2}, fun rep_ets_delete/2}, {{ets, match, 2}, fun rep_ets_match/2}, {{ets, match, 3}, fun rep_ets_match/3}, {{ets, match_object, 2}, fun rep_ets_match_object/2}, {{ets, match_object, 3}, fun rep_ets_match_object/3}, {{ets, match_delete, 2}, fun rep_ets_match_delete/2}, {{ets, new, 2}, fun rep_ets_new/2}, {{ets, filter, 3}, fun rep_ets_filter/3}, {{ets, info, 1}, fun rep_ets_info/1}, {{ets, info, 2}, fun rep_ets_info/2}, {{ets, foldl, 3}, fun rep_ets_foldl/3}]). %%%---------------------------------------------------------------------- %%% Start analysis target module/function %%%---------------------------------------------------------------------- -spec start_target(module(), term(), [term()]) -> ok. start_target(Mod, Fun, Args) -> InstrAppController = ets:member(?NT_OPTIONS, 'app_controller'), AppConModule = concuerror_instr:check_module_name(application_controller, none, 0), AppModule = concuerror_instr:check_module_name(application, none, 0), case InstrAppController of true -> AppConModule:start({application, kernel, []}), AppModule:start(kernel), AppModule:start(stdlib), ok; false -> ok end, apply(Mod, Fun, Args), case InstrAppController of true -> _ = [AppModule:stop(App) || {App, _, _} <- AppModule:loaded_applications()], ok; false -> ok end. %%%---------------------------------------------------------------------- %%% Callbacks %%%---------------------------------------------------------------------- %% Handle Mod:Fun(Args) calls. -spec rep_var(module(), atom(), [term()]) -> term(). rep_var(Mod, Fun, Args) -> check_unknown_process(), LenArgs = length(Args), Key = {Mod, Fun, LenArgs}, case lists:keyfind(Key, 1, ?INSTR_MOD_FUN) of {Key, Callback} -> apply(Callback, Args); false -> %% Rename module RenameMod = concuerror_instr:check_module_name(Mod, Fun, LenArgs), apply(RenameMod, Fun, Args) end. %% Handle apply/3 -spec rep_apply(module(), atom(), [term()]) -> term(). rep_apply(Mod, Fun, Args) -> rep_var(Mod, Fun, Args). %% @spec: rep_demonitor(reference()) -> 'true' %% @doc: Replacement for `demonitor/1'. %% %% Just yield before demonitoring. -spec rep_demonitor(reference()) -> 'true'. rep_demonitor(Ref) -> check_unknown_process(), concuerror_sched:notify(demonitor, concuerror_lid:lookup_ref_lid(Ref)), demonitor(Ref). %% @spec: rep_demonitor(reference(), ['flush' | 'info']) -> 'true' %% @doc: Replacement for `demonitor/2'. %% %% Just yield before demonitoring. -spec rep_demonitor(reference(), ['flush' | 'info']) -> 'true'. rep_demonitor(Ref, Opts) -> check_unknown_process(), concuerror_sched:notify(demonitor, concuerror_lid:lookup_ref_lid(Ref)), case lists:member(flush, Opts) of true -> receive {?INSTR_MSG, _, _, {_, Ref, _, _, _}} -> true after 0 -> true end; false -> true end, demonitor(Ref, Opts). %% @spec: rep_halt() -> no_return() %% @doc: Replacement for `halt/0'. %% %% Just send halt message and yield. -spec rep_halt() -> no_return(). rep_halt() -> check_unknown_process(), concuerror_sched:notify(halt, empty). %% @spec: rep_halt() -> no_return() %% @doc: Replacement for `halt/1'. %% %% Just send halt message and yield. -spec rep_halt(non_neg_integer() | string()) -> no_return(). rep_halt(Status) -> check_unknown_process(), concuerror_sched:notify(halt, Status). %% @spec: rep_is_process_alive(pid()) -> boolean() %% @doc: Replacement for `is_process_alive/1'. -spec rep_is_process_alive(pid()) -> boolean(). rep_is_process_alive(Pid) -> check_unknown_process(), case ?LID_FROM_PID(Pid) of not_found -> ok; PLid -> concuerror_sched:notify(is_process_alive, PLid) end, is_process_alive(Pid). %% @spec: rep_link(pid() | port()) -> 'true' %% @doc: Replacement for `link/1'. %% %% Just yield before linking. -spec rep_link(pid() | port()) -> 'true'. rep_link(Pid) -> check_unknown_process(), case ?LID_FROM_PID(Pid) of not_found -> ok; PLid -> concuerror_sched:notify(link, PLid) end, link(Pid). %% @spec: rep_monitor('process', pid() | {atom(), node()} | atom()) -> %% reference() %% @doc: Replacement for `monitor/2'. %% %% Just yield before monitoring. -spec rep_monitor('process', pid() | {atom(), node()} | atom()) -> reference(). rep_monitor(Type, Item) -> check_unknown_process(), case ?LID_FROM_PID(find_pid(Item)) of not_found -> monitor(Type, Item); Lid -> concuerror_sched:notify(monitor, {Lid, unknown}), Ref = monitor(Type, Item), concuerror_sched:notify(monitor, {Lid, Ref}, prev), concuerror_sched:wait(), Ref end. %% @spec: rep_process_flag('trap_exit', boolean()) -> boolean(); %% ('error_handler', atom()) -> atom(); %% ('min_heap_size', non_neg_integer()) -> %% non_neg_integer(); %% ('min_bin_vheap_size', non_neg_integer()) -> %% non_neg_integer(); %% ('priority', process_priority_level()) -> %% process_priority_level(); %% ('save_calls', non_neg_integer()) -> %% non_neg_integer(); %% ('sensitive', boolean()) -> boolean() %% @doc: Replacement for `process_flag/2'. %% %% Just yield before altering the process flag. -type process_priority_level() :: 'max' | 'high' | 'normal' | 'low'. -spec rep_process_flag('trap_exit', boolean()) -> boolean(); ('error_handler', atom()) -> atom(); ('min_heap_size', non_neg_integer()) -> non_neg_integer(); ('min_bin_vheap_size', non_neg_integer()) -> non_neg_integer(); ('priority', process_priority_level()) -> process_priority_level(); ('save_calls', non_neg_integer()) -> non_neg_integer(); ('sensitive', boolean()) -> boolean(). rep_process_flag(trap_exit = Flag, Value) -> check_unknown_process(), {trap_exit, OldValue} = process_info(self(), trap_exit), case Value =:= OldValue of true -> ok; false -> PlannedLinks = find_my_links(), concuerror_sched:notify(process_flag, {Flag, Value, PlannedLinks}), Links = find_my_links(), concuerror_sched:notify(process_flag, {Flag, Value, Links}, prev) end, process_flag(Flag, Value); rep_process_flag(Flag, Value) -> check_unknown_process(), process_flag(Flag, Value). -spec find_my_links() -> [concuerror_lid:lid()]. find_my_links() -> find_my_links_or_monitors(links). find_my_monitored() -> find_my_links_or_monitors(monitored_by). find_my_links_or_monitors(Type) -> PPid = self(), {Type, AllPids} = process_info(PPid, Type), AllLids = [?LID_FROM_PID(Pid) || Pid <- AllPids], [KnownLid || KnownLid <- AllLids, KnownLid =/= not_found]. -spec find_my_monitors() -> [concuerror_lid:lid()]. find_my_monitors() -> PPid = self(), {monitors, AllPids} = process_info(PPid, monitors), AllLids = [?LID_FROM_PID(Pid) || {process, Pid} <- AllPids], [KnownLid || KnownLid <- AllLids, KnownLid =/= not_found]. %% @spec rep_receive( %% fun((term()) -> 'block' | 'continue'), %% integer() | 'infinity', %% integer() | 'infinity') -> 'ok'. %% @doc: Function called right before a receive statement. %% %% If a matching message is found in the process' message queue, continue %% to actual receive statement, else block and when unblocked do the same. -spec rep_receive( fun((term()) -> 'block' | 'continue'), integer() | 'infinity', integer() | 'infinity') -> 'ok'. rep_receive(Fun, HasTimeout, IgnoreTimeout) -> check_unknown_process(), rep_receive_loop(poll, Fun, HasTimeout, IgnoreTimeout). -define(IGNORE_TIMEOUT(T, B), B =/= 'infinity' andalso T >= B). rep_receive_loop(Act, Fun, HasTimeout, Bound) -> case Act of ok -> ok; poll -> {messages, Mailbox} = process_info(self(), messages), case rep_receive_match(Fun, Mailbox) of block -> NewAct = case HasTimeout of infinity -> concuerror_sched:notify('receive', blocked); Timeout when ?IGNORE_TIMEOUT(Timeout, Bound) -> concuerror_sched:notify('receive', blocked); _ -> NewFun = fun(Msg) -> case rep_receive_match(Fun, [Msg]) of block -> false; continue -> true end end, Links = find_my_trappable_links(), Monitors = find_my_monitors(), Info = {NewFun, Links, Monitors}, concuerror_sched:notify('after', Info) end, rep_receive_loop(NewAct, Fun, HasTimeout, Bound); continue -> Tag = case HasTimeout of infinity -> unblocked; Timeout when ?IGNORE_TIMEOUT(Timeout, Bound) -> unblocked; _ -> had_after end, ok = concuerror_sched:notify('receive', Tag) end end. find_my_trappable_links() -> try {trap_exit, true} = erlang:process_info(self(), trap_exit) of _ -> find_my_links() catch _:_ -> [] end. rep_receive_match(_Fun, []) -> block; rep_receive_match(Fun, [H|T]) -> case Fun(H) of block -> rep_receive_match(Fun, T); continue -> continue end. %% Blocks forever (used for 'receive after infinity -> ...' expressions). -spec rep_receive_block() -> no_return(). rep_receive_block() -> Fun = fun(_Message) -> block end, rep_receive(Fun, infinity, infinity). %% @spec rep_after_notify() -> 'ok' %% @doc: Auxiliary function used in the `receive..after' statement %% instrumentation. %% %% Called first thing after an `after' clause has been entered. -spec rep_after_notify() -> 'ok'. rep_after_notify() -> check_unknown_process(), Info = {find_my_trappable_links(), find_my_monitors()}, concuerror_sched:notify('after', Info, prev), ok. %% @spec rep_receive_notify(pid(), term()) -> 'ok' %% @doc: Auxiliary function used in the `receive' statement instrumentation. %% %% Called first thing after a message has been received, to inform the scheduler %% about the message received and the sender. -spec rep_receive_notify(pid(), dict(), term()) -> 'ok'. rep_receive_notify(From, CV, Msg) -> check_unknown_process(), concuerror_sched:notify('receive', {From, CV, Msg}, prev), ok. %% @spec rep_receive_notify(term()) -> 'ok' %% @doc: Auxiliary function used in the `receive' statement instrumentation. %% %% Similar to rep_receive/2, but used to handle 'EXIT' and 'DOWN' messages. -spec rep_receive_notify(term()) -> no_return(). rep_receive_notify(_Msg) -> check_unknown_process(), %% XXX: Received uninstrumented message ok. %% @spec rep_register(atom(), pid() | port()) -> 'true' %% @doc: Replacement for `register/2'. %% %% Just yield after registering. -spec rep_register(atom(), pid() | port()) -> 'true'. rep_register(RegName, P) -> check_unknown_process(), case ?LID_FROM_PID(P) of not_found -> ok; PLid -> concuerror_sched:notify(register, {RegName, PLid}) end, register(RegName, P). %% @spec rep_send(dest(), term()) -> term() %% @doc: Replacement for `send/2' (and the equivalent `!' operator). %% %% If the target has a registered LID then instrument the message %% and yield after sending. Otherwise, send the original message %% and continue without yielding. -spec rep_send(dest(), term()) -> term(). rep_send(Dest, Msg) -> check_unknown_process(), send_center(Dest, Msg), Result = Dest ! Msg, concuerror_util:wait_messages(find_pid(Dest)), Result. %% @spec rep_send(dest(), term(), ['nosuspend' | 'noconnect']) -> %% 'ok' | 'nosuspend' | 'noconnect' %% @doc: Replacement for `send/3'. %% %% For now, call erlang:send/3, but ignore options in internal handling. -spec rep_send(dest(), term(), ['nosuspend' | 'noconnect']) -> 'ok' | 'nosuspend' | 'noconnect'. rep_send(Dest, Msg, Opt) -> check_unknown_process(), send_center(Dest, Msg), Result = erlang:send(Dest, Msg, Opt), concuerror_util:wait_messages(find_pid(Dest)), Result. send_center(Dest, Msg) -> PlanLid = ?LID_FROM_PID(find_pid(Dest)), concuerror_sched:notify(send, {Dest, PlanLid, Msg}), SendLid = ?LID_FROM_PID(find_pid(Dest)), concuerror_sched:notify(send, {Dest, SendLid, Msg}, prev), ok. %% @spec rep_spawn(function()) -> pid() %% @doc: Replacement for `spawn/1'. %% %% The argument provided is the argument of the original spawn call. %% Before spawned, the new process has to yield. -spec rep_spawn(function()) -> pid(). rep_spawn(Fun) -> spawn_center(spawn, Fun). spawn_center(Kind, Fun) -> check_unknown_process(), Spawner = case Kind of {spawn_opt, Opt} -> fun(F) -> spawn_opt(F, Opt) end; spawn -> fun spawn/1; spawn_link -> fun spawn_link/1; spawn_monitor -> fun spawn_monitor/1 end, {Tag, Info} = case Kind of {spawn_opt, _} = S -> S; _ -> {Kind, unknown} end, concuerror_sched:notify(Tag, Info), Result = Spawner(fun() -> spawn_fun_wrapper(Fun) end), concuerror_sched:notify(Tag, Result, prev), %% Wait before using the PID to be sure that an LID is assigned concuerror_sched:wait(), Result. -spec spawn_fun_wrapper(function()) -> term(). spawn_fun_wrapper(Fun) -> try ok = concuerror_sched:wait(), Fun(), exit(normal) catch exit:Normal when (Normal=:=normal orelse Normal=:=shutdown orelse Normal=:={shutdown, peer_close}) -> MyInfo = find_my_info(), concuerror_sched:notify(exit, {normal, MyInfo}), MyRealInfo = find_my_info(), concuerror_sched:notify(exit, {normal, MyRealInfo}, prev); Class:Type -> concuerror_sched:notify(error,[Class,Type,erlang:get_stacktrace()]) end. find_my_info() -> MyEts = find_my_ets_tables(), MyName = find_my_registered_name(), MyLinks = find_my_links(), MyMonitors = find_my_monitored(), {MyEts, MyName, MyLinks, MyMonitors}. find_my_ets_tables() -> Self = self(), MyTIDs = [TID || TID <- ets:all(), Self =:= ets:info(TID, owner)], Fold = fun(TID, {HeirsAcc, TablesAcc}) -> Survives = case ets:info(TID, heir) of none -> false; Self -> false; Pid -> case is_process_alive(Pid) of false -> false; true -> case ?LID_FROM_PID(Pid) of not_found -> false; HeirLid0 -> {true, HeirLid0} end end end, case Survives of false -> T = {?LID_FROM_PID(TID), case ets:info(TID, named_table) of true -> {ok, ets:info(TID, name)}; false -> none end}, {HeirsAcc, [T|TablesAcc]}; {true, HeirLid} -> {[HeirLid|HeirsAcc], TablesAcc} end end, lists:foldl(Fold, {[], []}, MyTIDs). find_my_registered_name() -> case process_info(self(), registered_name) of [] -> none; {registered_name, Name} -> {ok, Name} end. %% @spec rep_spawn(atom(), atom(), [term()]) -> pid() %% @doc: Replacement for `spawn/3'. %% %% See `rep_spawn/1'. -spec rep_spawn(atom(), atom(), [term()]) -> pid(). rep_spawn(Module, Function, Args) -> %% Rename module LenArgs = length(Args), NewModule = concuerror_instr:check_module_name(Module, Function, LenArgs), Fun = fun() -> apply(NewModule, Function, Args) end, rep_spawn(Fun). %% @spec rep_spawn_link(function()) -> pid() %% @doc: Replacement for `spawn_link/1'. %% %% Before spawned, the new process has to yield. -spec rep_spawn_link(function()) -> pid(). rep_spawn_link(Fun) -> spawn_center(spawn_link, Fun). %% @spec rep_spawn_link(atom(), atom(), [term()]) -> pid() %% @doc: Replacement for `spawn_link/3'. %% %% See `rep_spawn_link/1'. -spec rep_spawn_link(atom(), atom(), [term()]) -> pid(). rep_spawn_link(Module, Function, Args) -> %% Rename module LenArgs = length(Args), NewModule = concuerror_instr:check_module_name(Module, Function, LenArgs), Fun = fun() -> apply(NewModule, Function, Args) end, rep_spawn_link(Fun). %% @spec rep_spawn_monitor(function()) -> {pid(), reference()} %% @doc: Replacement for `spawn_monitor/1'. %% %% Before spawned, the new process has to yield. -spec rep_spawn_monitor(function()) -> {pid(), reference()}. rep_spawn_monitor(Fun) -> spawn_center(spawn_monitor, Fun). %% @spec rep_spawn_monitor(atom(), atom(), [term()]) -> {pid(), reference()} %% @doc: Replacement for `spawn_monitor/3'. %% %% See rep_spawn_monitor/1. -spec rep_spawn_monitor(atom(), atom(), [term()]) -> {pid(), reference()}. rep_spawn_monitor(Module, Function, Args) -> %% Rename module LenArgs = length(Args), NewModule = concuerror_instr:check_module_name(Module, Function, LenArgs), Fun = fun() -> apply(NewModule, Function, Args) end, rep_spawn_monitor(Fun). %% @spec rep_spawn_opt(function(), %% ['link' | 'monitor' | %% {'priority', process_priority_level()} | %% {'fullsweep_after', integer()} | %% {'min_heap_size', integer()} | %% {'min_bin_vheap_size', integer()}]) -> %% pid() | {pid(), reference()} %% @doc: Replacement for `spawn_opt/2'. %% %% Before spawned, the new process has to yield. -spec rep_spawn_opt(function(), ['link' | 'monitor' | {'priority', process_priority_level()} | {'fullsweep_after', integer()} | {'min_heap_size', integer()} | {'min_bin_vheap_size', integer()}]) -> pid() | {pid(), reference()}. rep_spawn_opt(Fun, Opt) -> spawn_center({spawn_opt, Opt}, Fun). %% @spec rep_spawn_opt(atom(), atom(), [term()], %% ['link' | 'monitor' | %% {'priority', process_priority_level()} | %% {'fullsweep_after', integer()} | %% {'min_heap_size', integer()} | %% {'min_bin_vheap_size', integer()}]) -> %% pid() | {pid(), reference()} %% @doc: Replacement for `spawn_opt/4'. %% %% Before spawned, the new process has to yield. -spec rep_spawn_opt(atom(), atom(), [term()], ['link' | 'monitor' | {'priority', process_priority_level()} | {'fullsweep_after', integer()} | {'min_heap_size', integer()} | {'min_bin_vheap_size', integer()}]) -> pid() | {pid(), reference()}. rep_spawn_opt(Module, Function, Args, Opt) -> %% Rename module LenArgs = length(Args), NewModule = concuerror_instr:check_module_name(Module, Function, LenArgs), Fun = fun() -> apply(NewModule, Function, Args) end, rep_spawn_opt(Fun, Opt). %% @spec: rep_start_timer(non_neg_integer(), pid() | atom(), term()) -> %% reference(). %% @doc: Replacement for `start_timer/3'. %% %% TODO: Currently it sends the message immediately and returns a random ref. -spec rep_start_timer(non_neg_integer(), pid() | atom(), term()) -> reference(). rep_start_timer(Time, Dest, Msg) -> check_unknown_process(), Ref = make_ref(), case ets:lookup(?NT_OPTIONS, 'ignore_timeout') of [{'ignore_timeout', ITValue}] when ITValue =< Time -> %% Ignore this start_timer operation ok; _ -> concuerror_sched:notify(start_timer, {?LID_FROM_PID(Dest), Msg}), Dest ! {timeout, Ref, Msg}, ok end, Ref. %% @spec: rep_send_after(non_neg_integer(), pid() | atom(), term()) -> %% reference(). %% @doc: Replacement for `send_after/3'. %% %% TODO: Currently it sends the message immediately and returns a random ref. -spec rep_send_after(non_neg_integer(), pid() | atom(), term()) -> reference(). rep_send_after(Time, Dest, Msg) -> check_unknown_process(), case ets:lookup(?NT_OPTIONS, 'ignore_timeout') of [{'ignore_timeout', ITValue}] when ITValue =< Time -> %% Ignore this send_after operation ok; _ -> concuerror_sched:notify(send_after, {?LID_FROM_PID(Dest), Msg}), Dest ! Msg, ok end, make_ref(). %% @spec: rep_exit(pid() | port(), term()) -> 'true'. %% @doc: Replacement for `exit/2'. -spec rep_exit(pid() | port(), term()) -> 'true'. rep_exit(Pid, Reason) -> check_unknown_process(), concuerror_sched:notify(exit_2, {?LID_FROM_PID(Pid), Reason}), exit(Pid, Reason), concuerror_util:wait_messages(find_pid(Pid)), true. %% @spec: rep_unlink(pid() | port()) -> 'true' %% @doc: Replacement for `unlink/1'. %% %% Just yield before unlinking. -spec rep_unlink(pid() | port()) -> 'true'. rep_unlink(Pid) -> check_unknown_process(), case ?LID_FROM_PID(Pid) of not_found -> ok; PLid -> concuerror_sched:notify(unlink, PLid) end, unlink(Pid). %% @spec rep_unregister(atom()) -> 'true' %% @doc: Replacement for `unregister/1'. %% %% Just yield before unregistering. -spec rep_unregister(atom()) -> 'true'. rep_unregister(RegName) -> check_unknown_process(), concuerror_sched:notify(unregister, RegName), unregister(RegName). %% @spec rep_whereis(atom()) -> pid() | port() | 'undefined' %% @doc: Replacement for `whereis/1'. %% %% Just yield before calling whereis/1. -spec rep_whereis(atom()) -> pid() | port() | 'undefined'. rep_whereis(RegName) -> check_unknown_process(), concuerror_sched:notify(whereis, {RegName, unknown}), R = whereis(RegName), Value = case R =:= undefined of true -> not_found; false -> ?LID_FROM_PID(R) end, concuerror_sched:notify(whereis, {RegName, Value}, prev), R. %% @spec rep_port_command(port(), term()) -> true %% @doc: Replacement for `port_command/2'. %% %% Just yield before calling port_command/2. -spec rep_port_command(port, term()) -> true. rep_port_command(Port, Data) -> check_unknown_process(), %concuerror_sched:notify(port_command, Port), port_command(Port, Data), concuerror_util:wait_messages(not_found), true. %% @spec rep_port_command(port(), term(), [force | nosuspend]) -> boolean() %% @doc: Replacement for `port_command/3'. %% %% Just yield before calling port_command/3. -spec rep_port_command(port, term(), [force | nosuspend]) -> boolean(). rep_port_command(Port, Data, OptionList) -> check_unknown_process(), %concuerror_sched:notify(port_command, Port), Result = port_command(Port, Data, OptionList), concuerror_util:wait_messages(not_found), Result. %% @spec rep_port_control(port(), integer(), term()) -> term() %% @doc: Replacement for `port_control/3'. %% %% Just yield before calling port_control/3. -spec rep_port_control(port, integer(), term()) -> term(). rep_port_control(Port, Operation, Data) -> check_unknown_process(), %concuerror_sched:notify(port_control, Port), Result = port_control(Port, Operation, Data), concuerror_util:wait_messages(not_found), Result. %%%---------------------------------------------------------------------- %%% ETS replacements %%%---------------------------------------------------------------------- -type ets_new_option() :: ets_new_type() | ets_new_access() | named_table | {keypos,integer()} | {heir,pid(),term()} | {heir,none} | ets_new_tweaks(). -type ets_new_type() :: set | ordered_set | bag | duplicate_bag. -type ets_new_access() :: public | protected | private. -type ets_new_tweaks() :: {write_concurrency,boolean()} | {read_concurrency,boolean()} | compressed. -spec rep_ets_new(atom(), [ets_new_option()]) -> ets:tab(). rep_ets_new(Name, Options) -> check_unknown_process(), NewName = rename_ets_table(Name), concuerror_sched:notify(ets, {new, [unknown, NewName, Options]}), try Tid = ets:new(NewName, Options), concuerror_sched:notify(ets, {new, [Tid, NewName, Options]}, prev), concuerror_sched:wait(), Tid catch _:_ -> %% Report a fake tid... concuerror_sched:notify(ets, {new, [-1, NewName, Options]}, prev), concuerror_sched:wait(), %% And throw the error again... ets:new(NewName, Options) end. -spec rep_ets_insert(ets:tab(), tuple() | [tuple()]) -> true. rep_ets_insert(Tab, Obj) -> check_unknown_process(), NewTab = rename_ets_table(Tab), ets_insert_center(insert, NewTab, Obj). -spec rep_ets_insert_new(ets:tab(), tuple()|[tuple()]) -> boolean(). rep_ets_insert_new(Tab, Obj) -> check_unknown_process(), NewTab = rename_ets_table(Tab), ets_insert_center(insert_new, NewTab, Obj). ets_insert_center(Type, Tab, Obj) -> KeyPos = ets:info(Tab, keypos), Lid = ?LID_FROM_PID(Tab), ConvObj = case is_tuple(Obj) of true -> [Obj]; false -> Obj end, Keys = ordsets:from_list([element(KeyPos, O) || O <- ConvObj]), concuerror_sched:notify(ets, {Type, [Lid, Tab, Keys, KeyPos, ConvObj, true]}), Fun = case Type of insert -> fun ets:insert/2; insert_new -> fun ets:insert_new/2 end, try Ret = Fun(Tab, Obj), %% XXX: Hardcoded true to avoid sleep set blocking. Info = {Type, [Lid, Tab, Keys, KeyPos, ConvObj, true]}, %Ret]}, concuerror_sched:notify(ets, Info, prev), Ret catch _:_ -> %% Report a fake result... FailInfo = {Type, [Lid, Tab, Keys, KeyPos, ConvObj, false]}, concuerror_sched:notify(ets, FailInfo, prev), %% And throw the error again... Fun(Tab, Obj) end. -spec rep_ets_lookup(ets:tab(), term()) -> [tuple()]. rep_ets_lookup(Tab, Key) -> check_unknown_process(), NewTab = rename_ets_table(Tab), Lid = ?LID_FROM_PID(NewTab), concuerror_sched:notify(ets, {lookup, [Lid, NewTab, Key]}), ets:lookup(NewTab, Key). -spec rep_ets_delete(ets:tab()) -> true. rep_ets_delete(Tab) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {delete, [?LID_FROM_PID(NewTab), NewTab]}), ets:delete(NewTab). -spec rep_ets_delete(ets:tab(), term()) -> true. rep_ets_delete(Tab, Key) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {delete, [?LID_FROM_PID(NewTab), NewTab, Key]}), ets:delete(NewTab, Key). -type match_spec() :: [{match_pattern(), [term()], [term()]}]. -type match_pattern() :: atom() | tuple(). -spec rep_ets_select_delete(ets:tab(), match_spec()) -> non_neg_integer(). rep_ets_select_delete(Tab, MatchSpec) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {select_delete, [?LID_FROM_PID(NewTab), NewTab, MatchSpec]}), ets:select_delete(NewTab, MatchSpec). -spec rep_ets_match_delete(ets:tab(), match_pattern()) -> true. rep_ets_match_delete(Tab, Pattern) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {match_delete, [?LID_FROM_PID(NewTab), NewTab, Pattern]}), ets:match_delete(NewTab, Pattern). -spec rep_ets_match_object(ets:tab(), tuple()) -> [tuple()]. rep_ets_match_object(Tab, Pattern) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {match_object, [?LID_FROM_PID(NewTab), NewTab, Pattern]}), ets:match_object(NewTab, Pattern). -spec rep_ets_match_object(ets:tab(), tuple(), integer()) -> {[[term()]],term()} | '$end_of_table'. rep_ets_match_object(Tab, Pattern, Limit) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {match_object, [?LID_FROM_PID(NewTab), NewTab, Pattern, Limit]}), ets:match_object(NewTab, Pattern, Limit). -spec rep_ets_foldl(fun((term(), term()) -> term()), term(), ets:tab()) -> term(). rep_ets_foldl(Function, Acc, Tab) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {foldl, [?LID_FROM_PID(NewTab), Function, Acc, NewTab]}), ets:foldl(Function, Acc, NewTab). -spec rep_ets_info(ets:tab()) -> [{atom(), term()}] | undefined. rep_ets_info(Tab) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {info, [?LID_FROM_PID(NewTab), NewTab]}), ets:info(NewTab). -spec rep_ets_info(ets:tab(), atom()) -> term() | undefined. rep_ets_info(Tab, Item) -> check_unknown_process(), NewTab = rename_ets_table(Tab), concuerror_sched:notify(ets, {info, [?LID_FROM_PID(NewTab), NewTab, Item]}), ets:info(NewTab, Item). -spec rep_ets_filter(ets:tab(), fun((term()) -> term()), term()) -> term(). %% XXX: no preemption point for now. rep_ets_filter(Tab, Fun, Args) -> check_unknown_process(), NewTab = rename_ets_table(Tab), ets:filter(NewTab, Fun, Args). -spec rep_ets_match(ets:tab(), term()) -> term(). %%XXX: no preemption point for now. rep_ets_match(Tab, Pattern) -> check_unknown_process(), NewTab = rename_ets_table(Tab), ets:match(NewTab, Pattern). -spec rep_ets_match(ets:tab(), term(), integer()) -> term(). %%XXX: no preemption point for now. rep_ets_match(Tab, Pattern, Limit) -> check_unknown_process(), NewTab = rename_ets_table(Tab), ets:match(NewTab, Pattern, Limit). %%%---------------------------------------------------------------------- %%% Helper functions %%%---------------------------------------------------------------------- find_pid(Pid) when is_pid(Pid) -> Pid; find_pid(Atom) when is_atom(Atom) -> whereis(Atom); find_pid(Other) -> Other. check_unknown_process() -> %% Check if an unknown (not registered) %% process is trying to run instrumented code. case ?LID_FROM_PID(self()) of not_found -> Trace = (catch error('Unregistered')), concuerror_log:internal("Unregistered process is trying " "to run instrumented code\n~p\n", [Trace]); _Else -> ok end. %% When instrumenting the application controller rename %% ac_tab ets table. rename_ets_table(ac_tab) -> InstrAppController = ets:member(?NT_OPTIONS, 'app_controller'), case InstrAppController of true -> concuerror_instr:check_module_name(ac_tab, none, 0); false -> ac_tab end; rename_ets_table(Tab) -> Tab. %%%---------------------------------------------------------------------- %%% Run eunit tests using concuerror %%%---------------------------------------------------------------------- -spec rep_eunit(module()) -> ok. rep_eunit(Module) -> ReModule = concuerror_instr:check_module_name(Module,none,0), rep_apply(eunit, start, []), rep_apply(eunit, test, [[{module, ReModule}], [no_tty]]), rep_apply(eunit, stop, []). %%%---------------------------------------------------------------------- %%% For debugging purposes %%% This functions can be executed from inside %%% instrumented code and will behave as expected %%% by bypassing Concuerror's instrumenter and scheduler. %%%---------------------------------------------------------------------- -spec debug_print(io:format()) -> true. debug_print(Format) -> debug_print(Format, []). -spec debug_print(io:format(), [term()]) -> true. debug_print(Format, Data) -> G = group_leader(), InitPid = whereis(init), group_leader(InitPid, self()), io:format(Format, Data), group_leader(G, self()). -spec debug_apply(module(), atom(), [term()]) -> term(). debug_apply(Mod, Fun, Args) -> apply(Mod, Fun, Args). -spec uninstrumented_send(pid(), term()) -> term(). uninstrumented_send(Pid, Term) -> Pid ! Term. ================================================ FILE: resources/old_source/concuerror_sched.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Scheduler %%%---------------------------------------------------------------------- -module(concuerror_sched). %% UI related exports -export([analyze/3]). %% Internal exports -export([notify/2, notify/3, wait/0]). -export_type([analysis_target/0, analysis_ret/0, bound/0, transition/0]). %%-define(COND_DEBUG, true). %%-define(DEBUG, true). -define(CLASSIC, classic). -define(SOURCE, source). -define(FULL, full). -include("gen.hrl"). %%%---------------------------------------------------------------------- %%% Definitions %%%---------------------------------------------------------------------- -define(INFINITY, infinity). -define(NO_ERROR, undef). -define(TIME_LIMIT, 20*1000). % (in ms) %%%---------------------------------------------------------------------- %%% Records %%%---------------------------------------------------------------------- %% 'next' messages are about next instrumented instruction not yet dispatched %% 'prev' messages are about additional effects of a dispatched instruction %% 'async' messages are about receives which have become enabled -type sched_msg_type() :: 'next' | 'prev' | 'async'. %% Internal message format %% %% msg : An atom describing the type of the message. %% pid : The sender's LID. %% misc : Optional arguments, depending on the message type. -record(sched, {msg :: atom(), lid :: concuerror_lid:lid(), misc = empty :: term(), type = next :: sched_msg_type()}). %%%---------------------------------------------------------------------- %%% Types %%%---------------------------------------------------------------------- -type analysis_info() :: {analysis_target(), non_neg_integer(), %% Number of interleavings non_neg_integer()}. %% Sleep-Set blocked traces %% Analysis result tuple. -type analysis_ret() :: {'ok', analysis_info()} | {'error', 'instr', analysis_info()} | {'error', 'analysis', analysis_info(), [concuerror_ticket:ticket()]}. %% Module-Function-Arguments tuple. -type analysis_target() :: {module(), atom(), [term()]}. -type bound() :: 'inf' | non_neg_integer(). %% Scheduler notification. -type notification() :: 'after' | 'block' | 'demonitor' | 'ets_delete' | 'ets_foldl' | 'ets_insert' | 'ets_insert_new' | 'ets_lookup' | 'ets_match_delete' | 'ets_match_object' | 'ets_select_delete' | 'fun_exit' | 'halt' | 'is_process_alive' | 'link' | 'monitor' | 'process_flag' | 'receive' | 'receive_no_instr' | 'register' | 'send' | 'send_after' | 'spawn' | 'spawn_link' | 'spawn_monitor' | 'spawn_opt' | 'start_timer' | 'unlink' | 'unregister' | 'whereis' | 'port_command' | 'port_control'. %%%---------------------------------------------------------------------- %%% User interface %%%---------------------------------------------------------------------- %% @spec: analyze(analysis_target(), [file:filename()], concuerror:options()) -> %% analysis_ret() %% @doc: Produce all interleavings of running `Target'. -spec analyze(analysis_target(), [file:filename()], concuerror:options()) -> analysis_ret(). analyze({Mod,Fun,Args}=Target, Files, Options) -> PreBound = case lists:keyfind(preb, 1, Options) of {preb, inf} -> ?INFINITY; {preb, Bound} -> Bound; false -> ?DEFAULT_PREB end, Dpor = case lists:keyfind(dpor, 1, Options) of {dpor, Flavor} -> Flavor; false -> 'none' end, Ret = case concuerror_instr:instrument_and_compile(Files, Options) of {ok, Bin} -> %% Note: No error checking for load ok = concuerror_instr:load(Bin), %% Rename Target's module NewMod = concuerror_instr:check_module_name(Mod, Fun, 0), NewTarget = {NewMod, Fun, Args}, concuerror_log:log(0, "\nRunning analysis with preemption " "bound ~p... \n", [PreBound]), %% Reset the internal state for the progress logger concuerror_log:reset(), %% Unregister some key processes %% inet_gethos_native will run without supervisor %% (Icky fallback) catch unregister(kernel_safe_sup), catch unregister(net_sup), %% Run all possible interleavings {T1, _} = statistics(wall_clock), Result = do_analysis(NewTarget, PreBound, Dpor, Options), {T2, _} = statistics(wall_clock), {Mins, Secs} = concuerror_util:to_elapsed_time(T1, T2), ?debug("Done in ~wm~.2fs\n", [Mins, Secs]), %% Print analysis summary {Tickets, RunCount, SBlocked, Trans, STrans} = Result, StrB = case SBlocked of 0 -> " "; _ -> io_lib:format( " (encountered ~w sleep-set blocked traces (~w transitions)) ", [SBlocked, STrans]) end, MemoryStr = try erlang:memory(maximum) of N -> MB = N / (1024*1024), io_lib:format(" (using ~.2fMB)",[MB]) catch _:_ -> "" end, concuerror_log:log(0, "\n\nAnalysis complete. Checked " "~w interleaving(s) (~w transitions)~sin ~wm~.2fs~s:\n", [RunCount, Trans, StrB, Mins, Secs, MemoryStr]), case Tickets =:= [] of true -> concuerror_log:log(0, "No errors found.~n"), {ok, {Target, RunCount, SBlocked}}; false -> TicketCount = length(Tickets), concuerror_log:log(0, "Found ~p erroneous interleaving(s).~n", [TicketCount]), {error, analysis, {Target, RunCount, SBlocked}, Tickets} end; error -> {error, instr, {Target, 0, 0}} end, concuerror_instr:delete_and_purge(Options), Ret. %% Produce all possible process interleavings of (Mod, Fun, Args). do_analysis(Target, PreBound, Dpor, Options) -> Self = self(), Fun = fun() -> scheduler_main(Target, PreBound, Self, Dpor, Options) end, process_flag(trap_exit, true), Backend = spawn_link(Fun), receive {interleave_result, Result} -> Result; {'EXIT', Backend, Reason} -> Msg = io_lib:format("Backend exited with reason:\n ~p\n", [Reason]), concuerror_log:internal(Msg) end. %% ============================================================================= %% DATA STRUCTURES %% ============================================================================= -type s_i() :: non_neg_integer(). -type instr() :: term(). -type transition() :: {concuerror_lid:lid(), instr(), list()}. -type lid_trace() :: [{concuerror_lid:lid(), transition(), lid_trace()}]. -type clock_map() :: dict(). %% dict(concuerror_lid:lid(), clock_vector()). -type clock_vector() :: orddict:orddict(). %% dict(concuerror_lid:lid(), s_i()). -record(trace_state, { i = 0 :: s_i(), last = init_tr() :: transition(), last_blocked = false :: boolean(), enabled = ordsets:new() :: ordsets:ordset(concuerror_lid:lid()), blocked = ordsets:new() :: ordsets:ordset(concuerror_lid:lid()), pollable = ordsets:new() :: ordsets:ordset(concuerror_lid:lid()), backtrack = [] :: lid_trace(), done = ordsets:new() :: ordsets:ordset(concuerror_lid:lid()), sleep_set = ordsets:new() :: ordsets:ordset(concuerror_lid:lid()), nexts = dict:new() :: dict(), % dict(lid(), instr()), clock_map = empty_clock_map() :: clock_map(), preemptions = 0 :: non_neg_integer(), race_checked = false :: boolean(), receives_vector = [] :: clock_vector() }). -type trace_state() :: #trace_state{}. -record(dpor_state, { target :: analysis_target(), run_count = 1 :: pos_integer(), sleep_blocked_count = 0 :: non_neg_integer(), total_trans = 0 :: non_neg_integer(), sleep_trans = 0 :: non_neg_integer(), show_output = false :: boolean(), tickets = [] :: [concuerror_ticket:ticket()], trace = [] :: [trace_state()], must_replay = false :: boolean(), bound_flag = false :: boolean(), proc_before = [] :: [pid()], dpor_flavor = 'none' :: 'full' | 'classic' | 'source' | 'none', preemption_bound = inf :: non_neg_integer() | 'inf', group_leader :: pid() }). %% ============================================================================= %% LOGIC (high level description of the exploration algorithm) %% ============================================================================= scheduler_main(Target, PreBound, Parent, Dpor, Options) -> %% fprof:trace(start), ?debug("Scheduler spawned\n"), register(?RP_SCHED, self()), Procs = processes(), %% To be able to clean up we need to be trapping exits... process_flag(trap_exit, true), %% Get `show_output' flag from options ShowOutput = lists:keymember('show_output', 1, Options), {Trace, GroupLeader} = start_target(Target), ?debug("Initial target spawned\n"), NewState = #dpor_state{trace = Trace, target = Target, proc_before = Procs, dpor_flavor = Dpor, preemption_bound = PreBound, show_output = ShowOutput, group_leader = GroupLeader}, Result = explore(NewState), %% fprof:trace(stop), unregister(?RP_SCHED), Parent ! {interleave_result, Result}. init_tr() -> {concuerror_lid:root_lid(), init, []}. start_target(Target) -> {FirstLid, GroupLeader} = start_target_op(Target), Next = wait_next(FirstLid, init), New = ordsets:new(), MaybeEnabled = ordsets:add_element(FirstLid, New), {Pollable, Enabled, Blocked} = update_lid_enabled(FirstLid, Next, New, MaybeEnabled, New), %% FIXME: check_messages and poll should also be called here for %% instrumenting "black" initial messages. TraceTop = #trace_state{nexts = dict:store(FirstLid, Next, dict:new()), enabled = Enabled, blocked = Blocked, backtrack = new_backtrack([{P, Next} || P <- Enabled]), pollable = Pollable}, {[TraceTop], GroupLeader}. new_backtrack(LidList) -> [{Lid, Tr, []} || {Lid, Tr} <- LidList]. %% ----------------------------------------------------------------------------- %% The main explore routine %% ----------------------------------------------------------------------------- explore(State) -> receive stop_analysis -> dpor_return(State) after 0 -> case select_from_backtrack(State) of {ok, {Lid, Cmd, _} = Selected, UpdState} -> case Cmd of {error, _ErrorInfo} -> RaceCheckState = race_check(UpdState), ErrorState = report_error(Selected, RaceCheckState), NewState = drop_last_frame(ErrorState), explore(NewState); _Else -> ?debug("Plan: ~p\n",[Selected]), Next = wait_next(Lid, Cmd), NextState = update_trace(Selected, Next, UpdState), NewState = add_some_next_to_backtrack(NextState), explore(NewState) end; none -> UpdState = case State#dpor_state.must_replay of true -> %% Already backtracking State; false -> RaceCheckState = race_check(State), report_possible_deadlock(RaceCheckState) end, NewState = drop_last_frame(UpdState), case finished(NewState) of false -> explore(NewState); true -> dpor_return(NewState) end end end. %% ----------------------------------------------------------------------------- select_from_backtrack(#dpor_state{trace = []}) -> none; select_from_backtrack(#dpor_state{must_replay = MustReplay, trace = Trace, bound_flag = BoundFlag} = State) -> [#trace_state{backtrack = Backtrack, done = Done, sleep_set = SleepSet} = _TraceTop|_] = Trace, ?debug("------------\nExplore ~p\n------------\n", [_TraceTop#trace_state.i + 1]), Sleepers = ordsets:union(SleepSet, Done), case pick_from_backtrack(BoundFlag, Backtrack, Sleepers) of none -> ?debug("Backtrack set explored\n",[]), none; {true, SelectedLid} -> UpdState = case MustReplay of true -> replay_trace(State); false -> State end, [NewTraceTop|RestTrace] = UpdState#dpor_state.trace, Instruction = dict:fetch(SelectedLid, NewTraceTop#trace_state.nexts), NewDone = ordsets:add_element(SelectedLid, Done), FinalTraceTop = NewTraceTop#trace_state{done = NewDone}, FinalState = UpdState#dpor_state{trace = [FinalTraceTop|RestTrace]}, {ok, Instruction, FinalState} end. pick_from_backtrack(true, _Backtrack, _Sleepers) -> none; pick_from_backtrack(_BoundFlag, Backtrack, Sleepers) -> pick_from_backtrack(Backtrack, Sleepers). pick_from_backtrack([], _) -> none; pick_from_backtrack([{B, _, _}|Rest], Done) -> case ordsets:is_element(B, Done) of true -> pick_from_backtrack(Rest, Done); false -> {true, B} end. %% ----------------------------------------------------------------------------- %% - add new entry with new entry %% - wait any possible additional messages %% - check for async update_trace(Selected, Next, State) -> #dpor_state{trace = Trace, dpor_flavor = Flavor} = State, NewTrace = update_trace(Selected, Next, Trace, Flavor, false), State#dpor_state{trace = NewTrace}. update_trace({Lid, _, _} = Selected, Next, [PrevTraceTop|_] = Trace, Flavor, Replaying) -> #trace_state{i = I, enabled = Enabled, blocked = Blocked, pollable = Pollable, done = Done, backtrack = Backtrack, nexts = Nexts, clock_map = ClockMap, sleep_set = SleepSet, preemptions = Preemptions, last = {LLid,_,_}} = PrevTraceTop, Expected = dict:fetch(Lid, Nexts), NewNexts = dict:store(Lid, Next, Nexts), ClockVector = lookup_clock(Lid, ClockMap), MaybeNotPollable = ordsets:del_element(Lid, Pollable), {NewPollable, NewEnabled, NewBlocked} = update_lid_enabled(Lid, Next, MaybeNotPollable, Enabled, Blocked), CommonNewTraceTop = case Replaying of false -> NewN = I+1, BaseClockVector = orddict:store(Lid, NewN, ClockVector), LidsClockVector = recent_dependency_cv(Selected, BaseClockVector, Trace), NewClockMap = dict:store(Lid, LidsClockVector, ClockMap), NewPreemptions = case ordsets:is_element(LLid, Enabled) of true -> case Lid =:= LLid of false -> Preemptions + 1; true -> Preemptions end; false -> Preemptions end, NewSleepSetCandidates = ordsets:union(ordsets:del_element(Lid, Done), SleepSet), #trace_state{ i = NewN, last = Selected, nexts = NewNexts, enabled = NewEnabled, sleep_set = NewSleepSetCandidates, blocked = NewBlocked, clock_map = NewClockMap, pollable = NewPollable, preemptions = NewPreemptions}; {true, ReplayTop} -> ReplayTop#trace_state{ last = Selected, nexts = NewNexts, pollable = NewPollable} end, InstrNewTraceTop = check_external_changes(CommonNewTraceTop), UpdTraceTop = #trace_state{last = UpdSelected, sleep_set = UpdSleepSet, nexts = UpdNexts, backtrack = TempBacktrack} = update_instr_info(Lid, Selected, InstrNewTraceTop), PrevTrace = case UpdSelected =:= Expected of true -> Trace; false -> rewrite_while_awaked(UpdSelected, Expected, Trace) end, FinalTraceTop = case Replaying of false -> ?debug("Selected: ~P\n", [UpdSelected, ?DEBUG_DEPTH]), ?debug("Happened before: ~p\n", [orddict:to_list( begin CMM = UpdTraceTop#trace_state.clock_map, CCC = lookup_clock(Lid, CMM), case lookup_clock_value(Lid, ClockVector) of 0 -> orddict:erase(Lid, CCC); QQ -> orddict:store(Lid, QQ, CCC) end end)]), {NewSleepSet, _NewAwaked} = case Flavor =:= 'none' of true -> {[], []}; false -> filter_awaked(UpdSleepSet, UpdNexts, UpdSelected) end, NewBacktrack = case TempBacktrack =:= [] of true -> case {Next, Selected} of {_, {_, { halt, _}, _}} -> []; {{_, {error, _}, _}, _} -> new_backtrack([{Lid, Next}]); _ -> element(3, lists:keyfind(Lid, 1, Backtrack)) end; false -> TempBacktrack end, NewLastBlocked = ordsets:is_element(Lid, NewBlocked), UpdTraceTop#trace_state{ last_blocked = NewLastBlocked, backtrack = NewBacktrack, sleep_set = NewSleepSet}; {true, _ReplayTop} -> UpdTraceTop end, [FinalTraceTop|PrevTrace]. update_lid_enabled(Lid, {_, Next, _}, Pollable, Enabled, Blocked) -> {NewEnabled, NewBlocked} = case is_enabled(Next) of true -> {Enabled, Blocked}; false -> {ordsets:del_element(Lid, Enabled), ordsets:add_element(Lid, Blocked)} end, NewPollable = case is_pollable(Next) of false -> Pollable; true -> ordsets:add_element(Lid, Pollable) end, {NewPollable, NewEnabled, NewBlocked}. is_enabled({'receive', blocked}) -> false; is_enabled(_Else) -> true. is_pollable({'receive', blocked}) -> true; is_pollable({'after', _Info}) -> true; is_pollable(_Else) -> false. recent_dependency_cv(Transition, ClockVector, Trace) -> Fun = fun(#trace_state{i = I, last = {Lid, _, _} = Transition2, clock_map = CM}, CVAcc) -> case lookup_clock_value(Lid, CVAcc) < I andalso concuerror_deps:dependent(Transition, Transition2) of true -> CV = lookup_clock(Lid, CM), max_cv(CVAcc, CV); false -> CVAcc end end, lists:foldl(Fun, ClockVector, Trace). %% Handle instruction is broken in two parts to reuse code in replay. handle_instruction(Transition, TraceTop) -> {NewTransition, Extra} = handle_instruction_op(Transition), handle_instruction_al(NewTransition, TraceTop, Extra). check_external_changes(TraceTop) -> case unexpected_exits(TraceTop) of {true, NewTraceTop} -> NewTraceTop; none -> #trace_state{pollable = Pollable} = TraceTop, PollableList = ordsets:to_list(Pollable), lists:foldl(fun poll_all/2, TraceTop, PollableList) end. unexpected_exits(#trace_state{nexts = Nexts} = TraceTop) -> receive {'DOWN', _, process, _, normal} -> unexpected_exits(TraceTop); {'DOWN', _, process, Pid, Reason} -> ?debug("Unexpected exit: ~p ~p\n", [Pid, Reason]), Lid = lid_from_pid(Pid), Entry = {Lid, {error, [exit, Reason, []]}, []}, NewNexts = dict:store(Lid, Entry, Nexts), {true, TraceTop#trace_state{nexts = NewNexts, backtrack = [{Lid, Entry, []}] }} after 0 -> none end. poll_all(Lid, TraceTop) -> case poll(Lid) of {'receive', Info} = Res when Info =:= unblocked; Info =:= had_after -> #trace_state{pollable = Pollable, blocked = Blocked, enabled = Enabled, %% TODO: Remove next line sleep_set = SleepSet, nexts = Nexts} = TraceTop, NewPollable = ordsets:del_element(Lid, Pollable), NewBlocked = ordsets:del_element(Lid, Blocked), NewSleepSet = ordsets:del_element(Lid, SleepSet), NewEnabled = ordsets:add_element(Lid, Enabled), {Lid, _Old, Msgs} = dict:fetch(Lid, Nexts), NewNexts = dict:store(Lid, {Lid, Res, Msgs}, Nexts), TraceTop#trace_state{pollable = NewPollable, blocked = NewBlocked, enabled = NewEnabled, sleep_set = NewSleepSet, nexts = NewNexts}; _Else -> TraceTop end. update_instr_info(Lid, Selected, CommonNewTraceTop) -> IntermediateTraceTop = handle_instruction(Selected, CommonNewTraceTop), UpdatedClockVector = lookup_clock(Lid, IntermediateTraceTop#trace_state.clock_map), {Lid, RewrittenInstr, _Msgs} = IntermediateTraceTop#trace_state.last, Messages = orddict:from_list(replace_messages(Lid, UpdatedClockVector)), IntermediateTraceTop#trace_state{last = {Lid, RewrittenInstr, Messages}}. filter_awaked(SleepSet, Nexts, Selected) -> Filter = fun(Lid) -> Instr = dict:fetch(Lid, Nexts), Dep = concuerror_deps:dependent(Instr, Selected), ?debug(" vs ~p: ~p\n",[Instr, Dep]), not Dep end, {A, NA} = lists:partition(Filter, SleepSet), {ordsets:from_list(A), ordsets:from_list(NA)}. rewrite_while_awaked(Transition, Original, Trace) -> rewrite_while_awaked(Transition, Original, Trace, []). rewrite_while_awaked(_Transition, _Original, [], Acc) -> lists:reverse(Acc); rewrite_while_awaked({P, _, _} = Transition, Original, [TraceTop|Rest] = Trace, Acc) -> #trace_state{sleep_set = SleepSet, nexts = Nexts} = TraceTop, case not ordsets:is_element(P, SleepSet) andalso {ok, Original} =:= dict:find(P, Nexts) of true -> NewNexts = dict:store(P, Transition, Nexts), NewTraceTop = TraceTop#trace_state{nexts = NewNexts}, rewrite_while_awaked(Transition, Original, Rest, [NewTraceTop|Acc]); false -> lists:reverse(Acc, Trace) end. %% ----------------------------------------------------------------------------- %% Dpor logic %% ----------------------------------------------------------------------------- race_check(#dpor_state{preemption_bound = Bound, trace = Trace, dpor_flavor = Flavor, bound_flag = BoundFlag} = State) -> case Flavor =:= 'none' of true -> %% add_some_next will take care of all the backtracks. State; false -> case BoundFlag of true -> State; false -> NewTrace = race_check(Trace, Bound, Flavor), State#dpor_state{trace = NewTrace} end end. race_check(Trace, PreBound, Flavor) -> race_check(Trace, [], PreBound, Flavor). race_check([_] = Trace, _Rest, _PreBound, _Flavor) -> Trace; race_check([#trace_state{race_checked = true}|_] = Trace, _Rest, _PreBound, Flavor) when Flavor =:= ?CLASSIC; Flavor =:= ?SOURCE -> Trace; race_check(Trace, Rest, PreBound, Flavor) -> [#trace_state{i = I, last = {Lid, {Tag, Info}, _} = Transition} = Top| [#trace_state{clock_map = ClockMap}|_] = PTrace] = Trace, NewPTrace = race_check(PTrace, [Top|Rest], PreBound, Flavor), NewTop = Top#trace_state{race_checked = true}, ?debug("Race check: ~p ~p\n", [I, Transition]), case concuerror_deps:may_have_dependencies(Transition) of true -> BasicClock = orddict:store(Lid, I, lookup_clock(Lid, ClockMap)), InescapableReceive = case Tag =:= 'receive' of false -> false; true -> element(1, Info) =:= unblocked end, ClockVector = case InescapableReceive of true -> MsgVector = Top#trace_state.receives_vector, max_cv(BasicClock, MsgVector); false -> BasicClock end, add_all_backtracks_trace(Transition, Lid, ClockVector, PreBound, Flavor, NewPTrace, [NewTop], Rest); false -> [NewTop|NewPTrace] end. add_all_backtracks_trace(_Transition, _Lid, _ClockVector, _PreBound, _Flavor, [_] = Init, Acc, _Rest) -> lists:reverse(Acc, Init); add_all_backtracks_trace(Transition, Lid, ClockVector, PreBound, Flavor, [#trace_state{preemptions = Preempt} = StateI|Trace], Acc, Rest) when Preempt + 1 > PreBound, PreBound =/= ?INFINITY -> add_all_backtracks_trace(Transition, Lid, ClockVector, PreBound, Flavor, Trace, [StateI|Acc], Rest); add_all_backtracks_trace(Transition, Lid, ClockVector, PreBound, Flavor, [StateI,PreSI|Rest], Acc, AccRest) -> #trace_state{i = I, last = {ProcSI, _, _} = SI} = StateI, Clock = lookup_clock_value(ProcSI, ClockVector), Action = case I > Clock andalso concuerror_deps:dependent(Transition, SI) of false -> {continue, PreSI, ClockVector}; true -> ?debug("~4w: ~P Clock ~p\n", [I, SI, ?DEBUG_DEPTH, Clock]), #trace_state{enabled = Enabled, backtrack = Backtrack, done = Done, sleep_set = SleepSet} = PreSI, Sleepers = ordsets:union(SleepSet, Done), case Flavor of ?FULL -> Result = find_path([StateI|Acc], AccRest, ProcSI, I, Sleepers, Backtrack), NewClockVector = orddict:store(ProcSI, I, ClockVector), case Result of inversion_explored -> ?debug(" Inversion is explored...\n"), {continue, PreSI, NewClockVector}; equivalent_scheduled -> ?debug(" Equivalent is scheduled...\n"), {continue, PreSI, NewClockVector}; {replace, NewBacktrack} -> ?debug(" NewBacktrack: ~p\n",[NewBacktrack]), {continue, PreSI#trace_state{backtrack = NewBacktrack}, NewClockVector} end; ?SOURCE -> NewClockVector = orddict:store(ProcSI, I, ClockVector), Candidates = ordsets:subtract(Enabled, Sleepers), {Predecessor, Initial} = find_preds_and_initials(Lid, ProcSI, Candidates, I, ClockVector, Acc), BacktrackSet = ordsets:from_list([B || {B,_,_} <- Backtrack]), case Predecessor of [] -> ?debug(" All sleeping...\n"), {continue_source, ProcSI, NewClockVector}; [P] -> Intersection = ordsets:intersection(BacktrackSet, Initial), NewBacktrack = case Intersection =/= [] of true -> ?debug(" Init in backtrack\n"), Backtrack; false -> ?debug(" Add: ~p\n", [P]), Backtrack ++ [{P, dummy, []}] end, ?debug(" NewBacktrack: ~p\n",[NewBacktrack]), {continue, PreSI#trace_state{backtrack = NewBacktrack}, NewClockVector} end; ?CLASSIC -> Candidates = ordsets:subtract(Enabled, Sleepers), {Predecessor, _Initial} = find_preds_and_initials(Lid, ProcSI, Candidates, I, ClockVector, Acc), decide_classic(Predecessor, Backtrack, Candidates, PreSI) end end, case Action of {continue, NewPreSI, UpdClockVector} -> add_all_backtracks_trace(Transition, Lid, UpdClockVector, PreBound, Flavor, [NewPreSI|Rest], [StateI|Acc], AccRest); {continue_source, UpdProcSI, UpdClockVector} -> add_all_backtracks_trace(Transition, UpdProcSI, UpdClockVector, PreBound, Flavor, [PreSI|Rest], [StateI|Acc], AccRest); {done, NewPreSI} -> lists:reverse([StateI|Acc], [NewPreSI|Rest]) end. %% ----------------- %% For optimal DPOR: %% ----------------- find_path([#trace_state{nexts = Nexts}|Acc], Rest, ProcSI, I, Sleepers, Backtrack) -> SleepSet = ordsets:del_element(ProcSI, Sleepers), Annotated = [{P, dict:fetch(P, Nexts)} || P <- SleepSet], NotDeps = not_deps(Acc, Rest, ProcSI, I), ?debug(" NotDeps:~p\n",[NotDeps]), try case find_weak_initial(NotDeps, Annotated) of {true, _, _} -> throw(inversion_explored); false -> Pred = fun({P,_,_}) -> P =/= ProcSI end, {Done, [{ProcSI,_,_} = Current|Future]} = lists:splitwith(Pred, Backtrack), ?debug(" Future:~p\n",[Future]), NewFuture = insert_best_match(Future, NotDeps), {replace, Done ++ [Current|NewFuture]} end catch throw:Reason -> Reason end. find_weak_initial( _, []) -> false; find_weak_initial(Trace, [C|R]) -> case find_weak_initial(Trace, [C], []) of false -> find_weak_initial(Trace, R); True -> True end. find_weak_initial(_, [], _) -> false; find_weak_initial([], [{Lid, _}|_], Acc) -> {true, Lid, lists:reverse(Acc)}; find_weak_initial([{Lid, _, _} = Tr|Rest], Candidates, Acc) -> case lists:keyfind(Lid, 1, Candidates) of {Lid, _} -> {true, Lid, lists:reverse(Acc, Rest)}; false -> Pred = fun({_, Tr2}) -> not concuerror_deps:dependent(Tr, Tr2) end, NewCandidates = lists:filter(Pred, Candidates), find_weak_initial(Rest, NewCandidates, [Tr|Acc]) end. insert_best_match([], Trace) -> Fold = fun({P, _,_} = Tr, PAcc) -> [{P, Tr, PAcc}] end, lists:foldl(Fold, [], lists:reverse(Trace)); insert_best_match(Backtrack, Trace) -> ?debug(" Trace:~p\n", [Trace]), Ps = [{P,Tr} || {P,Tr,_} <- Backtrack], case find_weak_initial(Trace, Ps) of {true, Lid, NewTrace} -> ?debug(" WeakIni:~p\n", [Lid]), {Lid, Tr, Further} = lists:keyfind(Lid, 1, Backtrack), ?debug(" Further:~p\n",[Further]), case Further =:= [] of true -> throw(equivalent_scheduled); false -> NewFuther = insert_best_match(Further, NewTrace), lists:keyreplace(Lid, 1, Backtrack, {Lid, Tr, NewFuther}) end; false -> Backtrack ++ insert_best_match([], Trace) end. not_deps(Acc, Rest, ProcSI, I) -> not_deps(Acc, Rest, ProcSI, I, []). not_deps([], no_rest, _, _, Path) -> lists:reverse(Path); not_deps([#trace_state{last = Last}], Rest, ProcSI, I, Path) when Rest =/= no_rest -> not_deps(Rest, no_rest, ProcSI, I, [Last|Path]); not_deps([#trace_state{last = {Lid, _, _} = Last, clock_map = ClockMap}|Acc], Rest, ProcSI, I, Path) -> Lid2Clock = lookup_clock(Lid, ClockMap), NewPath = case lookup_clock_value(ProcSI, Lid2Clock) >= I of true -> Path; false -> [Last|Path] end, not_deps(Acc, Rest, ProcSI, I, NewPath). %% -------------------------------------- %% For source set based and classic DPOR: %% -------------------------------------- find_preds_and_initials(Lid, ProcSI, Candidates, I, ClockVector, RevTrace) -> {Racing, NonRacing} = find_initials(Candidates, ProcSI, I, RevTrace), Initial = case not ordsets:is_element(Lid, Candidates) orelse ordsets:is_element(Lid, Racing) of true -> NonRacing; false -> ordsets:add_element(Lid, NonRacing) end, {predecessor(Initial, I, ClockVector), Initial}. find_initials(Candidates, ProcSI, I, RevTrace) -> RealCandidates = ordsets:del_element(ProcSI, Candidates), find_initials(RealCandidates, I, RevTrace, [ProcSI], []). find_initials(Candidates, _I, [_], Racing, NonRacing) -> {Racing,ordsets:union(Candidates, NonRacing)}; find_initials( [], _I, _Trace, Racing, NonRacing) -> {Racing, NonRacing}; find_initials(Candidates, I, [Top|Rest], Racing, NonRacing) -> #trace_state{last = {P,_,_}, clock_map = CM} = Top, ClockVector = lookup_clock(P, CM), case ordsets:is_element(P, Candidates) of false -> find_initials(Candidates, I, Rest, Racing, NonRacing); true -> Fun2 = fun(K, V, A) -> A andalso (K =:= P orelse V < I) end, {NewRacing, NewNonRacing} = case orddict:fold(Fun2, true, ClockVector) of false -> {ordsets:add_element(P, Racing), NonRacing}; true -> {Racing, ordsets:add_element(P, NonRacing)} end, NewCandidates = ordsets:del_element(P, Candidates), find_initials(NewCandidates, I, Rest, NewRacing, NewNonRacing) end. predecessor(Initial, I, ClockVector) -> DropWhile = fun(Lid) -> lookup_clock_value(Lid, ClockVector) < I end, case lists:dropwhile(DropWhile, Initial) of [] -> []; [H|_] -> [H] end. decide_classic(Predecessor, Backtrack, Candidates, PreSI) -> BacktrackSet = ordsets:from_list([B || {B,_,_} <- Backtrack]), case ordsets:intersection(BacktrackSet, Predecessor) =/= [] of true -> ?debug("One pred already in backtrack.\n"), {done, PreSI}; false -> Added = case Predecessor of [OneP] -> ?debug(" Add as 'choose-one': ~p\n", [OneP]), [{OneP, dummy, []}]; [] -> ?debug(" Add as 'choose every': ~p\n", [Candidates]), New = ordsets:subtract(Candidates, BacktrackSet), [{P, dummy, []} || P <- New] end, ?debug(" Added: ~p\n",[Added]), {done, PreSI#trace_state{backtrack = Backtrack ++ Added}} end. %% -------------------------------------- %% Manipulating the clock vectors / maps: %% -------------------------------------- empty_clock_map() -> dict:new(). lookup_clock(P, ClockMap) -> case dict:find(P, ClockMap) of {ok, Clock} -> Clock; error -> orddict:new() end. lookup_clock_value(P, CV) -> case orddict:find(P, CV) of {ok, Value} -> Value; error -> 0 end. max_cv(D1, D2) -> Merger = fun(_Key, V1, V2) -> max(V1, V2) end, orddict:merge(Merger, D1, D2). %% ----------------------------------------------------------------------------- add_some_next_to_backtrack(State) -> #dpor_state{trace = [TraceTop|Rest], dpor_flavor = Flavor, preemption_bound = PreBound} = State, #trace_state{enabled = Enabled, sleep_set = SleepSet, backtrack = OldBacktrack, last = {Lid, _, _}, preemptions = Preemptions, nexts = Nexts} = TraceTop, ?debug("Pick next: Enabled: ~p Sleeping: ~p\n", [Enabled, SleepSet]), Backtrack = case OldBacktrack =:= [] of true -> Set = case Flavor =:= 'none' of true -> case ordsets:is_element(Lid, Enabled) of true when Preemptions =:= PreBound -> [Lid]; _Else -> Enabled end; false -> case ordsets:subtract(Enabled, SleepSet) of [] -> []; [H|_] = Candidates -> case ordsets:is_element(Lid, Candidates) of true -> [Lid]; false -> [H] end end end, Annotated = [{P, dict:fetch(P, Nexts)} || P <- Set], new_backtrack(Annotated); false -> ?debug("Predefined: ~p\n", [OldBacktrack]), [A || {K, _, _} = A <- OldBacktrack, not ordsets:is_element(K, SleepSet)] end, BoundFlag = Flavor =:= 'full' andalso PreBound =/= ?INFINITY andalso Preemptions > PreBound, ?debug("Picked: ~p\n",[Backtrack]), NewTraceTop = TraceTop#trace_state{backtrack = Backtrack}, State#dpor_state{trace = [NewTraceTop|Rest], bound_flag = BoundFlag}. %% ----------------------------------------------------------------------------- report_error(Transition, State) -> #dpor_state{trace = [TraceTop|_] = Trace, tickets = Tickets, total_trans = TotalTrans} = State, #trace_state{i = Steps} = TraceTop, NewTotalTrans = TotalTrans + Steps, ?debug("ERROR!\n~P\n",[Transition, ?DEBUG_DEPTH]), Error = convert_error_info(Transition), LidTrace = convert_trace_to_error_trace(Trace, [Transition]), Ticket = create_ticket(Error, LidTrace), %% Report the error to the progress logger. concuerror_log:progress({'error', Ticket}), State#dpor_state{must_replay = true, tickets = [Ticket|Tickets], total_trans = NewTotalTrans}. report_possible_deadlock(#dpor_state{trace = []} = State) -> State; report_possible_deadlock(State) -> #dpor_state{trace = [TraceTop|_] = Trace, tickets = Tickets, sleep_blocked_count = SBlocked, bound_flag = BoundFlag, total_trans = TotalTrans, sleep_trans = SleepTrans} = State, #trace_state{i = Steps} = TraceTop, NewTotalTrans = TotalTrans + Steps, {NewTickets, NewSBlocked, NewSleepTrans} = case TraceTop#trace_state.enabled of [] -> case TraceTop#trace_state.blocked of [] -> ?debug("NORMAL!\n"), {Tickets, SBlocked, SleepTrans}; Blocked -> ?debug("DEADLOCK!\n"), Error = {deadlock, Blocked}, LidTrace = convert_trace_to_error_trace(Trace, []), Ticket = create_ticket(Error, LidTrace), %% Report error concuerror_log:progress({'error', Ticket}), {[Ticket|Tickets], SBlocked, SleepTrans} end; _Else -> case TraceTop#trace_state.sleep_set =/= [] andalso TraceTop#trace_state.done =:= [] andalso not BoundFlag of false -> {Tickets, SBlocked, SleepTrans}; true -> ?debug("SLEEP SET BLOCK\n"), %% debug_trace(lists:reverse(Trace)), %% exit(sleep_set_block), {Tickets, SBlocked+1, SleepTrans+Steps} end end, State#dpor_state{must_replay = true, tickets = NewTickets, sleep_blocked_count = NewSBlocked, total_trans = NewTotalTrans, sleep_trans = NewSleepTrans}. %% debug_trace([]) -> ok; %% debug_trace([Top|Rest]) -> %% #trace_state{ %% i = I, %% last = Last, %% backtrack = Backtrack, %% sleep_set = SleepSet, %% done = Done, %% nexts = Nexts} = Top, %% BT = [K || {K, _, _} <- Backtrack], %% Keys = ordsets:union(Done, SleepSet), %% Pred = fun(K,_) -> ordsets:is_element(K, Keys) end, %% FilteredNexts = dict:filter(Pred, Nexts), %% io:format("~p: ~p\nBacktrack:~p\nSleep Set:~p\n" %% "FilteredNexts:~p\n~p\n---\n", %% [I, Last, BT, ordsets:union(Done,SleepSet), %% dict:to_list(FilteredNexts)]), %% debug_trace(Rest). convert_trace_to_error_trace([], Acc) -> Acc; convert_trace_to_error_trace([#trace_state{ last = {Lid, _, _} = Entry, last_blocked = Blocked}|Rest], Acc) -> NewAcc = [Entry| case Blocked of false -> Acc; true -> [{Lid, block, []}|Acc] end], convert_trace_to_error_trace(Rest, NewAcc). create_ticket(Error, LidTrace) -> InitTr = init_tr(), [{P1, init, []} = InitTr|Trace] = LidTrace, InitSet = sets:add_element(P1, sets:new()), {ErrorState, _Procs} = lists:mapfoldl(fun convert_error_trace/2, InitSet, Trace), concuerror_ticket:new(Error, ErrorState). convert_error_trace({Lid, {error, [ErrorOrThrow,Kind|_]}, _Msgs}, Procs) when ErrorOrThrow =:= error; ErrorOrThrow =:= throw -> Msg = concuerror_error:type(concuerror_error:new({Kind, foo})), {{exit, Lid, Msg}, Procs}; convert_error_trace({Lid, block, []}, Procs) -> {{block, Lid}, Procs}; convert_error_trace({Lid, {Instr, Extra}, _Msgs}, Procs) -> NewProcs = case Instr of Spawn when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_monitor; Spawn =:= spawn_opt -> NewLid = case Extra of {Lid0, _MonLid} -> Lid0; Lid0 -> Lid0 end, sets:add_element(NewLid, Procs); exit -> sets:del_element(Lid, Procs); _ -> Procs end, NewInstr = case Instr of send -> {Orig, Dest, Msg} = Extra, NewDest = case is_atom(Orig) of true -> {name, Orig}; false -> check_lid_liveness(Dest, NewProcs) end, {send, Lid, NewDest, Msg}; 'receive' -> {_Tag, Origin, Msg} = Extra, {'receive', Lid, Origin, Msg}; 'after' -> {'after', Lid}; is_process_alive -> {is_process_alive, Lid, check_lid_liveness(Extra, NewProcs)}; TwoArg when TwoArg =:= register; TwoArg =:= whereis -> {Name, TLid} = Extra, {TwoArg, Lid, Name, check_lid_liveness(TLid, NewProcs)}; process_flag -> {trap_exit, Value, _Links} = Extra, {process_flag, Lid, trap_exit, Value}; exit -> {exit, Lid, normal}; exit_2 -> {Lid2, Reason} = Extra, {exit_2, Lid, Lid2, Reason}; send_after -> {Lid2, Msg} = Extra, {send_after, Lid, Lid2, Msg}; start_timer -> {Lid2, Msg} = Extra, {start_timer, Lid, Lid2, Msg}; PortOp when PortOp =:= port_command; PortOp =:= port_control -> {PortOp, Lid, Extra}; Monitor when Monitor =:= monitor; Monitor =:= spawn_monitor -> {TLid, _RefLid} = Extra, {Monitor, Lid, check_lid_liveness(TLid, NewProcs)}; ets -> case Extra of {insert, [_EtsLid, Tid, _K, _KP, Objects, _Status]} -> {ets_insert, Lid, {Tid, Objects}}; {insert_new, [_EtsLid, Tid, _K, _KP, Objects, _Status]} -> {ets_insert_new, Lid, {Tid, Objects}}; {delete, [_EtsLid, Tid]} -> {ets_delete, Lid, Tid}; {C, [_EtsLid | Options]} -> ListC = atom_to_list(C), AtomC = list_to_atom("ets_" ++ ListC), {AtomC, Lid, list_to_tuple(Options)} end; _ -> {Instr, Lid, Extra} end, {NewInstr, NewProcs}. check_lid_liveness(not_found, _Live) -> not_found; check_lid_liveness(Lid, Live) -> case sets:is_element(Lid, Live) of true -> Lid; false -> {dead, Lid} end. convert_error_info({_Lid, {error, [Kind, Type, Stacktrace]}, _Msgs})-> NewType = case Kind of error -> Type; throw -> {nocatch, Type}; exit -> Type end, {Tag, Details} = concuerror_error:new({NewType, foo}), Info = case Tag of exception -> {NewType, Stacktrace}; assertion_violation -> Details end, {Tag, Info}. %% ----------------------------------------------------------------------------- drop_last_frame(#dpor_state{trace = []} = State) -> State; drop_last_frame(#dpor_state{trace = [_|Trace]} = State) -> State#dpor_state{trace = Trace}. finished(#dpor_state{trace = Trace}) -> Trace =:= []. dpor_return(State) -> %% First clean up the last interleaving GroupLeader = State#dpor_state.group_leader, Output = concuerror_io_server:group_leader_sync(GroupLeader), case State#dpor_state.show_output of true -> io:put_chars(Output); false -> ok end, ProcBefore = State#dpor_state.proc_before, proc_cleanup(processes() -- ProcBefore), %% Return the analysis result RunCnt = State#dpor_state.run_count, SBlocked = State#dpor_state.sleep_blocked_count, Transitions = State#dpor_state.total_trans, STransitions = State#dpor_state.sleep_trans, Tickets = State#dpor_state.tickets, {Tickets, RunCnt, SBlocked, Transitions, STransitions}. %% ============================================================================= %% ENGINE (manipulation of the Erlang processes under the scheduler) %% ============================================================================= start_target_op(Target) -> concuerror_lid:start(), %% Initialize a new group leader GroupLeader = concuerror_io_server:new_group_leader(self()), {Mod, Fun, Args} = Target, NewFun = fun() -> ?REP_MOD:start_target(Mod, Fun, Args) end, SpawnFun = fun() -> concuerror_rep:spawn_fun_wrapper(NewFun) end, FirstPid = spawn(SpawnFun), %% Set our io_server as the group leader group_leader(GroupLeader, FirstPid), {concuerror_lid:new(FirstPid, noparent), GroupLeader}. %% ----------------------------------------------------------------------------- replay_trace(#dpor_state{proc_before = ProcBefore, run_count = RunCnt, sleep_blocked_count = SBlocked, group_leader = GroupLeader, target = Target, trace = Trace, show_output = ShowOutput} = State) -> NewRunCnt = RunCnt + 1, ?debug("\nReplay (~p) is required...\n", [NewRunCnt]), concuerror_lid:stop(), %% Get buffered output from group leader %% TODO: For now just ignore it. Maybe we can print it %% only when we have an error (after the backtrace?) Output = concuerror_io_server:group_leader_sync(GroupLeader), case ShowOutput of true -> io:put_chars(Output); false -> ok end, proc_cleanup(processes() -- ProcBefore), concuerror_util:flush_mailbox(), {FirstLid, NewGroupLeader} = start_target_op(Target), _ = wait_next(FirstLid, init), NewTrace = replay_trace_aux(Trace), ?debug("Done replaying...\n\n"), %% Report the start of a new interleaving concuerror_log:progress({'new', NewRunCnt, SBlocked}), State#dpor_state{run_count = NewRunCnt, must_replay = false, bound_flag = false, group_leader = NewGroupLeader, trace = NewTrace}. replay_trace_aux(Trace) -> [Init|Rest] = lists:reverse(Trace), replay_trace_aux(Rest, [Init]). replay_trace_aux([], Acc) -> Acc; replay_trace_aux([TraceState|Rest], Acc) -> #trace_state{i = _I, last = {Lid, Cmd, _} = Last} = TraceState, %% ?debug(" ~-4w: ~P.",[_I, Last, ?DEBUG_DEPTH]), Next = wait_next(Lid, Cmd), %% ?debug("."), UpdAcc = update_trace(Last, Next, Acc, irrelevant, {true, TraceState}), %% [#trace_state{last = _NewLast}|_] = UpdAcc, %% ?debug("O:~p\nN:~p\n",[Last, _NewLast]), %% ?debug(".\n"), replay_trace_aux(Rest, UpdAcc). %% ----------------------------------------------------------------------------- wait_next(Lid, {exit, {normal, _Info}} = Arg2) -> Pid = concuerror_lid:get_pid(Lid), Ref = monitor(process, Pid), continue(Lid), receive {'DOWN', Ref, process, Pid, normal} -> {Lid, exited, []} after ?TIME_LIMIT -> error(time_limit, [Lid, Arg2]) end; wait_next(Lid, Plan) -> DebugArgs = [Lid, Plan], continue(Lid), Replace = case Plan of {Spawn, _Info} when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_monitor; Spawn =:= spawn_opt -> {true, %% This interruption happens to make sure that a child has an %% LID before the parent wants to do any operation with its PID. receive #sched{msg = Spawn, lid = Lid, misc = Info, type = prev} = Msg -> case Info of {Pid, Ref} -> monitor(process, Pid), ChildLid = concuerror_lid:new(Pid, Lid), continue(ChildLid), MonRef = concuerror_lid:ref_new(ChildLid, Ref), Msg#sched{misc = {ChildLid, MonRef}}; Pid -> monitor(process, Pid), ChildLid = concuerror_lid:new(Pid, Lid), continue(ChildLid), Msg#sched{misc = ChildLid} end after ?TIME_LIMIT -> error(time_limit, DebugArgs) end}; {ets, {new, _Info}} -> {true, receive #sched{msg = ets, lid = Lid, misc = {new, [Tid|Rest]}, type = prev} = Msg -> NewMisc = {new, [concuerror_lid:ets_new(Tid)|Rest]}, Msg#sched{misc = NewMisc} after ?TIME_LIMIT -> error(time_limit, DebugArgs) end}; {monitor, _Info} -> {true, receive #sched{msg = monitor, lid = Lid, misc = {TLid, Ref}, type = prev} = Msg -> NewMisc = {TLid, concuerror_lid:ref_new(TLid, Ref)}, Msg#sched{misc = NewMisc} after ?TIME_LIMIT -> error(time_limit, DebugArgs) end}; _Other -> false end, case Replace of {true, NewMsg} -> continue(Lid), self() ! NewMsg, get_next(Lid); false -> get_next(Lid) end. get_next(Lid) -> receive #sched{msg = Type, lid = Lid, misc = Misc, type = next} -> {Lid, {Type, Misc}, []} after ?TIME_LIMIT -> error(time_limit, [Lid]) end. %% ============================================================================= %% INSTRUCTIONS (logic and engine for Erlang instructions) %% ============================================================================= handle_instruction_op({Lid, {Spawn, _Info}, Msgs} = DebugArg) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_monitor; Spawn =:= spawn_opt -> ParentLid = Lid, Info = receive %% This is the replaced message #sched{msg = Spawn, lid = ParentLid, misc = Info0, type = prev} -> Info0 after ?TIME_LIMIT -> error(time_limit, [DebugArg]) end, ChildLid = case Info of {Lid0, _MonLid} -> Lid0; Lid0 -> Lid0 end, ChildNextInstr = get_next(ChildLid), {{Lid, {Spawn, Info}, Msgs}, ChildNextInstr}; handle_instruction_op({Lid, {ets, {Updatable, _Info}}, Msgs} = DebugArg) when Updatable =:= new; Updatable =:= insert_new; Updatable =:= insert -> receive %% This is the replaced message #sched{msg = ets, lid = Lid, misc = {Updatable, Info}, type = prev} -> {{Lid, {ets, {Updatable, Info}}, Msgs}, {}} after ?TIME_LIMIT -> error(time_limit, [DebugArg]) end; handle_instruction_op({Lid, {'receive', Tag}, Msgs} = DebugArg) -> NewTag = case Tag of {T, _, _} -> T; T -> T end, receive #sched{msg = 'receive', lid = Lid, misc = {From, CV, Msg}, type = prev} -> {{Lid, {'receive', {NewTag, From, Msg}}, Msgs}, CV} after ?TIME_LIMIT -> error(time_limit, [DebugArg]) end; handle_instruction_op({Lid, {'after', {Fun, _, _}}, Msgs} = DebugArg) -> receive #sched{msg = 'after', lid = Lid, misc = {Links, Monitors}, type = prev} -> {{Lid, {'after', {Fun, Links, Monitors}}, Msgs}, {}} after ?TIME_LIMIT -> error(time_limit, [DebugArg]) end; handle_instruction_op({Lid, {Updatable, _Info}, Msgs} = DebugArg) when Updatable =:= exit; Updatable =:= send; Updatable =:= whereis; Updatable =:= monitor; Updatable =:= process_flag -> receive #sched{msg = Updatable, lid = Lid, misc = Info, type = prev} -> {{Lid, {Updatable, Info}, Msgs}, {}} after ?TIME_LIMIT -> error(time_limit, [DebugArg]) end; handle_instruction_op(Instr) -> {Instr, {}}. handle_instruction_al({Lid, {exit, _Info}, _Msgs} = Trans, TraceTop, {}) -> #trace_state{enabled = Enabled, nexts = Nexts} = TraceTop, NewEnabled = ordsets:del_element(Lid, Enabled), NewNexts = dict:erase(Lid, Nexts), TraceTop#trace_state{enabled = NewEnabled, nexts = NewNexts, last = Trans}; handle_instruction_al({Lid, {Spawn, Info}, _Msgs} = Trans, TraceTop, ChildNextInstr) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_monitor; Spawn =:= spawn_opt -> ChildLid = case Info of {Lid0, _MonLid} -> Lid0; Lid0 -> Lid0 end, #trace_state{enabled = Enabled, blocked = Blocked, nexts = Nexts, pollable = Pollable, clock_map = ClockMap} = TraceTop, NewNexts = dict:store(ChildLid, ChildNextInstr, Nexts), ClockVector = lookup_clock(Lid, ClockMap), NewClockMap = dict:store(ChildLid, ClockVector, ClockMap), MaybeEnabled = ordsets:add_element(ChildLid, Enabled), {NewPollable, NewEnabled, NewBlocked} = update_lid_enabled(ChildLid, ChildNextInstr, Pollable, MaybeEnabled, Blocked), TraceTop#trace_state{last = Trans, clock_map = NewClockMap, enabled = NewEnabled, blocked = NewBlocked, pollable = NewPollable, nexts = NewNexts}; handle_instruction_al({Lid, {'receive', _Info}, _Msgs} = Trans, TraceTop, CV) -> #trace_state{clock_map = ClockMap} = TraceTop, Vector = lookup_clock(Lid, ClockMap), NewVector = max_cv(Vector, CV), NewClockMap = dict:store(Lid, NewVector, ClockMap), TraceTop#trace_state{last = Trans, clock_map = NewClockMap, receives_vector = CV}; handle_instruction_al({_Lid, {ets, {Updatable, _Info}}, _Msgs} = Trans, TraceTop, {}) when Updatable =:= new; Updatable =:= insert_new; Updatable =:= insert -> TraceTop#trace_state{last = Trans}; handle_instruction_al({_Lid, {Updatable, _Info}, _Msgs} = Trans, TraceTop, {}) when Updatable =:= send; Updatable =:= whereis; Updatable =:= monitor; Updatable =:= process_flag; Updatable =:= 'after' -> TraceTop#trace_state{last = Trans}; handle_instruction_al({_Lid, {register, {Name, PLid}}, _Msgs}, #trace_state{nexts = Nexts} = TraceTop, {}) -> TraceTop#trace_state{nexts = update_named_sends(Name, PLid, Nexts)}; handle_instruction_al({_Lid, {halt, _Status}, _Msgs}, TraceTop, {}) -> TraceTop#trace_state{enabled = [], blocked = []}; handle_instruction_al(_Transition, TraceTop, {}) -> TraceTop. update_named_sends(Name, PLid, Nexts) -> Map = fun(Lid, Instr) -> case Instr of {Lid, {send, {Name, _OldPLid, Msg}}, Msgs} -> {Lid, {send, {Name, PLid, Msg}}, Msgs}; _Other -> Instr end end, dict:map(Map, Nexts). %%%---------------------------------------------------------------------- %%% Helper functions %%%---------------------------------------------------------------------- %% Kill any remaining processes. %% If the run was terminated by an exception, processes linked to %% the one where the exception occurred could have been killed by the %% exit signal of the latter without having been deleted from the pid/lid %% tables. Thus, 'EXIT' messages with any reason are accepted. proc_cleanup(ProcList) -> Link_and_kill = fun(P) -> link(P), exit(P, kill) end, lists:foreach(Link_and_kill, ProcList), wait_for_exit(ProcList). wait_for_exit([]) -> ok; wait_for_exit([P|Rest]) -> receive {'EXIT', P, _Reason} -> wait_for_exit(Rest) end. %%%---------------------------------------------------------------------- %%% Instrumentation interface %%%---------------------------------------------------------------------- %% Prompt process Pid to continue running. continue(LidOrPid) -> send_message(LidOrPid, continue). poll(Lid) -> send_message(Lid, poll), {Lid, Res, []} = get_next(Lid), Res. send_message(Pid, Message) when is_pid(Pid) -> Pid ! #sched{msg = Message}, ok; send_message(Lid, Message) -> Pid = concuerror_lid:get_pid(Lid), send_message(Pid, Message). %% Notify the scheduler of an event. %% If the calling user process has an associated LID, then send %% a notification and yield. Otherwise, for an unknown process %% running instrumented code completely ignore this call. -spec notify(notification(), any()) -> 'ok' | 'poll' | 'ignore'. notify(Msg, Misc) -> notify(Msg, Misc, next). -spec notify(notification(), any(), sched_msg_type()) -> 'ok' | 'poll' | 'ignore'. notify(Msg, Misc, Type) -> case lid_from_pid(self()) of not_found -> ok; Lid -> Rec = #sched{msg = Msg, lid = Lid, misc = Misc, type = Type}, ?RP_SCHED_SEND ! Rec, case Type of next -> wait(); _Else -> ignore end end. -spec lid_from_pid(pid()) -> concuerror_lid:lid() | 'not_found'. lid_from_pid(Pid) -> concuerror_lid:from_pid(Pid). -define(VECTOR_MSG(LID, VC), #sched{msg = vector, lid = LID, misc = VC, type = async}). %% Wait until the scheduler prompts to continue. -spec wait() -> 'ok' | 'poll'. wait() -> receive #sched{msg = continue} -> ok; #sched{msg = poll} -> poll; ?VECTOR_MSG(Lid, VC) -> Msgs = instrument_my_messages(Lid, VC), ignore = notify(vector, Msgs, async), wait() end. replace_messages(Lid, VC) -> Fun = fun(Pid, MsgAcc) -> Pid ! ?VECTOR_MSG(Lid, VC), receive ?VECTOR_MSG(PidsLid, {Msgs, _, _} = MsgInfo) -> case Msgs =:= [] of true -> MsgAcc; false -> [{PidsLid, MsgInfo}|MsgAcc] end after ?TIME_LIMIT -> error(time_limit, [Pid, MsgAcc]) end end, concuerror_lid:fold_pids(Fun, []). -define(IS_INSTR_MSG(Msg), (is_tuple(Msg) andalso size(Msg) =:= 4 andalso element(1, Msg) =:= ?INSTR_MSG)). instrument_my_messages(Lid, VC) -> Self = self(), Fun = fun(Acc) -> receive Msg when not ?IS_INSTR_MSG(Msg) -> Instr = {?INSTR_MSG, Lid, VC, Msg}, Self ! Instr, {cont, [Msg|Acc]} after 0 -> {trap_exit, Trapping} = erlang:process_info(self(), trap_exit), Links = case Acc =:= [] orelse not Trapping of true -> []; false -> concuerror_rep:find_my_links() end, Monitors = case Acc =:= [] of true -> []; false -> concuerror_rep:find_my_monitors() end, {done, {Acc, Links, Monitors}} end end, dynamic_loop_acc(Fun, []). dynamic_loop_acc(Fun, Arg) -> case Fun(Arg) of {done, Ret} -> Ret; {cont, NewArg} -> dynamic_loop_acc(Fun, NewArg) end. ================================================ FILE: resources/old_source/concuerror_state.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Author : Alkis Gotovos %%% Description : State interface %%%---------------------------------------------------------------------- -module(concuerror_state). -export([extend/2, empty/0, is_empty/1, pack/1, trim_head/1, trim_tail/1]). -export_type([state/0]). -include("gen.hrl"). %%-define(ENABLE_COMPRESSION, true). -ifdef(ENABLE_COMPRESSION). -type state() :: binary(). -define(OPT_T2B, [compressed]). -define(BIN_TO_TERM(X), binary_to_term(X)). -define(TERM_TO_BIN(X), term_to_binary(X, ?OPT_T2B)). -else. -type state() :: {{concuerror_lid:lid(),pos_integer()} | 'undefined', queue(), {concuerror_lid:lid(),pos_integer()} | 'undefined'}. -define(BIN_TO_TERM(X), X). -define(TERM_TO_BIN(X), X). -endif. %% Given a state and a process LID, return a new extended state %% containing the given LID as its last element. -spec extend(state(), concuerror_lid:lid()) -> state(). extend(State, Lid) -> {Front, Queue, Rear} = ?BIN_TO_TERM(State), case Rear of {RLid, N} when RLid==Lid -> NewState = {Front, Queue, {RLid, N+1}}, ?TERM_TO_BIN(NewState); {_RLid, _N} -> NewQueue = queue:in(Rear, Queue), NewState = {Front, NewQueue, {Lid, 1}}, ?TERM_TO_BIN(NewState); undefined -> NewState = {Front, Queue, {Lid, 1}}, ?TERM_TO_BIN(NewState) end. %% Return initial (empty) state. -spec empty() -> state(). empty() -> NewState = {undefined, queue:new(), undefined}, ?TERM_TO_BIN(NewState). %% Check if State is an empty state. -spec is_empty(state()) -> boolean(). is_empty(State) -> case ?BIN_TO_TERM(State) of {undefined, Queue, undefined} -> queue:is_empty(Queue); _ -> false end. %% Pack out state. -spec pack(state()) -> state(). pack(State) -> {Front, Queue, Rear} = ?BIN_TO_TERM(State), Queue1 = case Front of undefined -> Queue; _ -> queue:in_r(Front, Queue) end, Queue2 = case Rear of undefined -> Queue1; _ -> queue:in(Rear, Queue1) end, NewState = {undefined, Queue2, undefined}, ?TERM_TO_BIN(NewState). %% Return a tuple containing the first Lid in the given state %% and a new state with that Lid removed. %% Assume the State is packed and not empty. -spec trim_head(state()) -> {concuerror_lid:lid(), state()}. trim_head(State) -> {Front, Queue, Rear} = ?BIN_TO_TERM(State), case Front of {Lid, N} when N>1 -> NewState = {{Lid, N-1}, Queue, Rear}, {Lid, ?TERM_TO_BIN(NewState)}; {Lid, N} when N==1 -> NewState = {undefined, Queue, Rear}, {Lid, ?TERM_TO_BIN(NewState)}; undefined -> {{value, NewFront}, NewQueue} = queue:out(Queue), NewState = {NewFront, NewQueue, Rear}, trim_head(?TERM_TO_BIN(NewState)) end. %% Return a tuple containing the last Lid in the given state %% and a new state with that Lid removed. %% Assume the State is packed and not empty. -spec trim_tail(state()) -> {concuerror_lid:lid(), state()}. trim_tail(State) -> {Front, Queue, Rear} = ?BIN_TO_TERM(State), case Rear of {Lid, N} when N>1 -> NewState = {Front, Queue, {Lid, N-1}}, {Lid, ?TERM_TO_BIN(NewState)}; {Lid, N} when N==1 -> NewState = {Front, Queue, undefined}, {Lid, ?TERM_TO_BIN(NewState)}; undefined -> {{value, NewRear}, NewQueue} = queue:out_r(Queue), NewState = {Front, NewQueue, NewRear}, trim_tail(?TERM_TO_BIN(NewState)) end. ================================================ FILE: resources/old_source/concuerror_ticket.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Error ticket interface %%%---------------------------------------------------------------------- -module(concuerror_ticket). -export([new/2, get_error/1, get_details/1, details_to_strings/1, sort/1]). -export_type([ticket/0]). -include("gen.hrl"). %% An error ticket containing all the informations about an error %% and the interleaving that caused it. -type ticket() :: {concuerror_error:error(), [concuerror_proc_action:proc_action()]}. %% @doc: Create a new error ticket. -spec new(concuerror_error:error(), [concuerror_proc_action:proc_action()]) -> ticket(). new(Error, ErrorDetails) -> NewError = case Error of {exception, {Type, Stacktrace}} -> {exception, {Type, clean_stacktrace(Stacktrace)}}; Error -> Error end, {NewError, ErrorDetails}. %% --------------------------- clean_stacktrace(Stacktrace) -> clean_stacktrace(Stacktrace, []). clean_stacktrace([], Acc) -> lists:reverse(Acc); clean_stacktrace([{?REP_MOD, _, _, _} | Ts], Acc) -> %% Ignore concuerror's rep module clean_stacktrace(Ts, Acc); clean_stacktrace([{Mod, Fun, Args, Pos} | Ts], Acc) -> %% Rename modules back to their original names OldMod = concuerror_instr:old_module_name(Mod), %% Rename files after their modules (for now). %% TODO: Rename files back to their original names. OldFile = atom_to_list(OldMod) ++ ".erl", OldPos = lists:keyreplace(file, 1, Pos, {file, OldFile}), clean_stacktrace(Ts, [{OldMod, Fun, Args, OldPos} | Acc]). %% --------------------------- -spec get_error(ticket()) -> concuerror_error:error(). get_error({Error, _ErrorDetails}) -> Error. -spec get_details(ticket()) -> [concuerror_proc_action:proc_action()]. get_details({_Error, ErrorDetails}) -> ErrorDetails. -spec details_to_strings(ticket()) -> [string()]. details_to_strings({_Error, ErrorDetails}) -> [concuerror_proc_action:to_string(Detail) || Detail <- ErrorDetails]. %% Sort a list of tickets according to state. -spec sort([ticket()]) -> [ticket()]. sort(Tickets) -> Compare = fun(T1, T2) -> get_details(T1) =< get_details(T2) end, lists:sort(Compare, Tickets). ================================================ FILE: resources/old_source/concuerror_util.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Utilities %%%---------------------------------------------------------------------- -module(concuerror_util). -export([doc/1, test/0, flat_format/2, flush_mailbox/0, get_module_name/1, is_erl_source/1, funs/1, funs/2, funLine/3, pmap/2, wait_messages/1, timer_init/0, timer_start/1, timer/1, timer_stop/1, timer_destroy/0, init_state/0, progress_bar/2, to_elapsed_time/1, to_elapsed_time/2]). -export_type([progress/0]). -include_lib("kernel/include/file.hrl"). -include("gen.hrl"). %% @spec doc(string()) -> 'ok' %% @doc: Build documentation using edoc. -spec doc(string()) -> 'ok'. doc(AppDir) -> AppName = ?APP_ATOM, Options = [], edoc:application(AppName, AppDir, Options). %% @spec test() -> 'ok' %% @doc: Run all EUnit tests for the modules in the `src' directory. -spec test() -> 'ok'. test() -> Modules = [concuerror_lid, concuerror_state, concuerror_error, concuerror_ticket, concuerror_instr], Tests = [{module, M} || M <- Modules], eunit:test(Tests, [verbose]). %% @spec flat_format(string(), [term()]) -> string() %% @doc: Equivalent to lists:flatten(io_lib:format(String, Args)). -spec flat_format(string(), [term()]) -> string(). flat_format(String, Args) -> lists:flatten(io_lib:format(String, Args)). %% Flush a process' mailbox. -spec flush_mailbox() -> 'ok'. flush_mailbox() -> receive _Any -> flush_mailbox() after 0 -> ok end. %% @spec is_erl_source(file:filename()) -> boolean() %% @doc: Check if file exists and has `.erl' suffix -spec is_erl_source(file:filename()) -> boolean(). is_erl_source(File) -> case filename:extension(File) of ".erl" -> case file:read_file_info(File) of {ok, Info} -> Info#file_info.type == 'regular'; _Error -> false end; _Other -> false end. %% @spec get_module_name(file:filename()) -> atom() %% @doc: Get the module name of an erlang file name -spec get_module_name(file:filename()) -> atom(). get_module_name(File) -> list_to_atom(filename:basename(File, ".erl")). %% @spec funs(string()) -> [{atom(), non_neg_integer()}] %% @doc: Same as `funs(File, tuple)'. -spec funs(string()) -> [{atom(), arity()}]. funs(File) -> funs(File, tuple). %% @type: funs_options() = 'tuple' | 'string'. %% @spec funs(string(), Options::funs_options()) -> %% [{atom(), non_neg_integer()}] | [string()] %% @doc: Scan a file for exported functions. %% %% If no `export' attribute is found in the file, all functions of the module %% are returned. %% If called with the `tuple' option, a list of {Fun, Arity} tuples is returned, %% otherwise if called with the `string' option, a list of `"Fun/Arity"' strings %% is returned. -spec funs(string(), 'tuple' | 'string') -> [{atom(), arity()}] | [string()]. funs(File, tuple) -> {ok, Form} = epp_dodger:quick_parse_file(File), getFuns(Form, []); funs(File, string) -> Funs = funs(File, tuple), [lists:concat([Name, "/", Arity]) || {Name, Arity} <- Funs]. getFuns([], Funs) -> Funs; getFuns([Node|Rest] = L, Funs) -> case erl_syntax:type(Node) of attribute -> Name = erl_syntax:atom_name(erl_syntax:attribute_name(Node)), case Name of "export" -> [List] = erl_syntax:attribute_arguments(Node), Args = erl_syntax:list_elements(List), NewFuns = getExports(Args, []), getFuns(Rest, NewFuns ++ Funs); _Other -> getFuns(Rest, Funs) end; function -> case Funs of [] -> getAllFuns(L, []); _Other -> Funs end; _Other -> getFuns(Rest, Funs) end. getExports([], Exp) -> Exp; getExports([Fun|Rest], Exp) -> Name = erl_syntax:atom_name(erl_syntax:arity_qualifier_body(Fun)), Arity = erl_syntax:integer_value(erl_syntax:arity_qualifier_argument(Fun)), getExports(Rest, [{list_to_atom(Name), Arity}|Exp]). getAllFuns([], Funs) -> Funs; getAllFuns([Node|Rest], Funs) -> case erl_syntax:type(Node) of function -> Name = erl_syntax:atom_name(erl_syntax:function_name(Node)), Arity = erl_syntax:function_arity(Node), getAllFuns(Rest, [{list_to_atom(Name), Arity}|Funs]); _Other -> getAllFuns(Rest, Funs) end. -spec funLine(string(), atom(), arity()) -> integer(). funLine(File, Function, Arity) -> {ok, Form} = epp_dodger:quick_parse_file(File), getFunLine(Form, Function, Arity). getFunLine([], _Function, _Arity) -> -1; getFunLine([Node|Rest], Function, Arity) -> case erl_syntax:type(Node) of function -> F = erl_syntax:atom_name(erl_syntax:function_name(Node)), A = erl_syntax:function_arity(Node), case (Function =:= list_to_atom(F)) andalso (Arity =:= A) of true -> erl_syntax:get_pos(Node); false -> getFunLine(Rest, Function, Arity) end; _Other -> getFunLine(Rest, Function, Arity) end. %% ------------------------------------------------------------------- %% A timer function %% It returns true only after X msec since the last time. -spec timer_init() -> ok. timer_init() -> Tweaks = [{write_concurrency,true}, {read_concurrency,true}], ?NT_TIMER = ets:new(?NT_TIMER, [set, public, named_table | Tweaks]), true = ets:insert(?NT_TIMER, {ets_counter, 0}), ok. -spec timer_start(non_neg_integer()) -> pos_integer(). timer_start(MSec) -> %% Create clock N = ets:update_counter(?NT_TIMER, ets_counter, 1), {T, _} = statistics(wall_clock), true = ets:insert(?NT_TIMER, {N, MSec, T}), %% Return the clock id N. -spec timer(pos_integer()) -> pos_integer() | 'false'. timer(ClockId) -> %% Get old value [{ClockId, MSec, T1}] = ets:lookup(?NT_TIMER, ClockId), %% Get new value {T2, _} = statistics(wall_clock), %% Compare T = T2 - T1, if T >= MSec -> %% Update the value (last time we asked) true = ets:update_element(?NT_TIMER, ClockId, {3, T2}), %% Return elapsed time T; true -> %% Not there yet, return false false end. -spec timer_stop(pos_integer()) -> ok. timer_stop(ClockId) -> true = ets:delete(?NT_TIMER, ClockId), ok. -spec timer_destroy() -> ok. timer_destroy() -> true = ets:delete(?NT_TIMER), ok. %% ------------------------------------------------------------------- %% Progress bar %% Log event handler internal state. %% The state (if we want to have progress bar) contains %% the number of errors we have found so far, %% the elapsed time (in msecs), %% the timer. -type progress() :: {non_neg_integer(), non_neg_integer(), pos_integer()}. -spec init_state() -> progress(). init_state() -> {0, 0, concuerror_util:timer_start(1000)}. -spec progress_bar(concuerror_log:progress_type(), progress()) -> {progress(), string()}. progress_bar({'new', RunCnt, SBlocked}, {Errors, Elapsed, Timer}=State) -> case timer(Timer) of false -> {State, ""}; Time -> NewElapsed = Elapsed + Time, {Mins, Secs} = to_elapsed_time(NewElapsed), TruncSecs = erlang:trunc(Secs), StrSecs = case TruncSecs < 10 of true -> "0" ++ integer_to_list(TruncSecs); false -> integer_to_list(TruncSecs) end, Msg = io_lib:format( "[ ~p checked interleavings, ~p sleep set blocked," " ~p errors in ~wm~ss ]", [RunCnt, SBlocked, Errors, Mins, StrSecs]), {{Errors, NewElapsed, Timer}, Msg} end; progress_bar({'error', _Ticket}, {Error, Elapsed, Timer}) -> {{Error+1, Elapsed, Timer}, ""}. %% ------------------------------------------------------------------- %% Elapsed time (from msecs to {Mins, Secs}) -type elapsed_time() :: {integer(), float()}. -spec to_elapsed_time(pos_integer(), pos_integer()) -> elapsed_time(). to_elapsed_time(T1, T2) -> to_elapsed_time(T2 - T1). -spec to_elapsed_time(non_neg_integer()) -> elapsed_time(). to_elapsed_time(ElapsedTime) -> Mins = ElapsedTime div 60000, Secs = (ElapsedTime rem 60000) / 1000, {Mins, Secs}. %% ------------------------------------------------------------------- %% A concurrent map -spec pmap(fun((term()) -> term()), [term()]) -> [term()]. pmap(Fun, List) -> Parent = self(), Pids = [spawn(fun() -> Parent ! Fun(L) end) || L <- List], [receive Ret -> Ret end || _Pid <- Pids]. %% ------------------------------------------------------------------- %% Wait for uninstrumented messages to be processed. -spec wait_messages(concuerror_rep:dest()) -> ok. wait_messages(Dest) -> WaitFlag = ets:member(?NT_OPTIONS, 'wait_messages'), NotInstr = concuerror_lid:from_pid(Dest) =:= 'not_found', case (WaitFlag andalso NotInstr) of true -> Self = self(), Pid = spawn(fun() -> trace(Self) end), receive {Pid, ok} -> ok end; false -> ok end. trace(Pid) -> %% Wait until Pid receives a message {message_queue_len, MsgQueueLen} = process_info(Pid, message_queue_len), traceLoop(Pid, MsgQueueLen, 5), Pid ! {self(), ok}. traceLoop(_Pid, _MsgQueueLen, 0) -> ok; traceLoop(Pid, MsgQueueLen, I) -> {message_queue_len, NewLen} = process_info(Pid, message_queue_len), case NewLen > MsgQueueLen of true -> ok; false -> receive after 2 -> traceLoop(Pid, MsgQueueLen, I-1) end end. ================================================ FILE: resources/perm_tests/run_perm_tests ================================================ #!/usr/bin/env bash MYNAME=$(basename $0) export DIR=$(dirname $0) perm() { local items="$1" local out="$2" local i [[ "$items" == "" ]] && echo "$out" && return for (( i=0; i<${#items}; i++ )) ; do perm "${items:0:i}${items:i+1}" "$out${items:i:1}" done } runme() { R=$($DIR/../concuerror --dpor -p inf -f $DIR/src/$TEST.erl -t $TEST run $1 | \ if [ "$MODE" = "STRICT" ]; then grep Checked | cut -d' ' -f4 else grep erroneous | cut -d' ' -f2 fi) echo "." >> $TOTAL if [ "$R" != "$RESULT" ]; then echo " $TEST $1: FAIL! (Expecting $RESULT got $R)" echo "." >> $FAIL exit 1 fi exit 0 } usage() { cat <&2; exit 1;; esac done shift $((OPTIND-1)) if [ -z "$1" ]; then usage exit 0 fi for i in "$@"; do export TOTAL=$(mktemp) export FAIL=$(mktemp) export TEST=$(basename $i .erl) erlc -o /tmp $DIR/src/$TEST.erl export RESULT=$(erl -pa /tmp -noshell -s $TEST result -s erlang halt) PROCS=$(erl -pa /tmp -noshell -s $TEST procs -s erlang halt) rm /tmp/$TEST.beam export -f runme (perm $(seq -s '' $PROCS) | \ xargs -n 1 -P 10 -i bash -c 'runme "$@"' _ {} \;) && \ echo "$TEST: OK ($(wc -l < $TOTAL))" || \ echo "$TEST: FAILED! ($(wc -l < $FAIL)/$(wc -l < $TOTAL))" rm $TOTAL rm $FAIL done ================================================ FILE: resources/perm_tests/src/complete_test_3.erl ================================================ -module(complete_test_3). -export([result/0, procs/0, run/1]). result() -> io:format("80"). procs() -> io:format("6"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). -define(cover, ets:insert(table, {self(), ?LINE})). proc($1) -> ?cover, ets:insert(table, {x, 1}); proc($2) -> ?cover, ets:insert(table, {z, 1}); proc($3) -> ?cover, [{y, Y}] = ets:lookup(table, y), case Y of 0 -> ?cover, ets:lookup(table, x); _ -> ok end; proc($4) -> ?cover, [{z, Z}] = ets:lookup(table, z), case Z of 1 -> ?cover, ets:lookup(table, x); _ -> ok end, ?cover, ets:insert(table, {y, 1}); proc($5) -> ?cover, ets:insert(table, {x, 2}); proc($6) -> ?cover, ets:insert(table, {y, 2}). ================================================ FILE: resources/perm_tests/src/conditional_readers_3.erl ================================================ -module(conditional_readers_3). -export([result/0, procs/0, run/1]). result() -> io:format("6"). procs() -> io:format("3"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}). proc($1) -> ets:insert(table, {x, 1}); proc($2) -> [{y, Y}] = ets:lookup(table, y), case Y of 0 -> ets:lookup(table, x); _ -> ok end; proc($3) -> ets:lookup(table, x), ets:insert(table, {y, 1}). ================================================ FILE: resources/perm_tests/src/depend_4_1.erl ================================================ -module(depend_4_1). -export([result/0, procs/0, run/1]). result() -> io:format("30"). procs() -> io:format("6"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> ets:insert(table, {z, 1}); proc($2) -> ets:insert(table, {x, 1}); proc($3) -> ets:insert(table, {y, 1}); proc($4) -> [{x, X}] = ets:lookup(table, x), case X of 1 -> [{y, Y}] = ets:lookup(table, y), case Y of 1 -> ets:lookup(table, z); _ -> ok end; _ -> ok end; proc($5) -> [{y, Y}] = ets:lookup(table, y), case Y of 1 -> ets:lookup(table, z); _ -> ok end; proc($6) -> ets:insert(table, {x, 2}). ================================================ FILE: resources/perm_tests/src/depend_4_2.erl ================================================ -module(depend_4_2). -export([result/0, procs/0, run/1]). result() -> io:format("20"). procs() -> io:format("6"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> ets:insert(table, {z, 1}); proc($2) -> ets:insert(table, {x, 1}); proc($3) -> ets:insert(table, {y, 1}); proc($4) -> [{x, X}] = ets:lookup(table, x), case X of 1 -> [{y, Y}] = ets:lookup(table, y), case Y of 1 -> ets:lookup(table, z); _ -> ok end; _ -> ok end; proc($5) -> ets:lookup(table, y); proc($6) -> ets:insert(table, {x, 2}). ================================================ FILE: resources/perm_tests/src/depend_4_3.erl ================================================ -module(depend_4_3). -export([result/0, procs/0, run/1]). result() -> io:format("16"). procs() -> io:format("5"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}). proc($1) -> ets:insert(table, {x, 1}); proc($2) -> ets:insert(table, {y, 1}); proc($3) -> [{x, X}] = ets:lookup(table, x), case X of 1 -> ets:lookup(table, y); _ -> ok end; proc($4) -> ets:lookup(table, y); proc($5) -> ets:insert(table, {x, 2}). ================================================ FILE: resources/perm_tests/src/depend_6_1.erl ================================================ -module(depend_6_1). -export([result/0, procs/0, run/1]). result() -> io:format("18"). procs() -> io:format("4"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> ets:lookup(table, y), ets:lookup(table, z); proc($2) -> ets:lookup(table, y); proc($3) -> ets:insert(table, {z, 1}); proc($4) -> ets:insert(table, {y, 1}), ets:insert(table, {y, 2}). ================================================ FILE: resources/perm_tests/src/depend_6_2.erl ================================================ -module(depend_6_2). -export([result/0, procs/0, run/1]). result() -> io:format("27"). procs() -> io:format("4"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> ets:lookup(table, y), ets:lookup(table, z); proc($2) -> ets:lookup(table, y); proc($3) -> ets:insert(table, {y, 1}); proc($4) -> ets:insert(table, {z, 1}), ets:insert(table, {y, 2}). ================================================ FILE: resources/perm_tests/src/depend_6_3.erl ================================================ -module(depend_6_3). -export([result/0, procs/0, run/1]). result() -> io:format("36"). procs() -> io:format("4"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> ets:lookup(table, y), ets:lookup(table, z); proc($2) -> ets:lookup(table, y); proc($3) -> ets:insert(table, {y, 1}); proc($4) -> ets:insert(table, {y, 2}), ets:insert(table, {z, 1}). ================================================ FILE: resources/perm_tests/src/depend_6_4.erl ================================================ -module(depend_6_4). -export([result/0, procs/0, run/1]). result() -> io:format("18"). procs() -> io:format("4"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> ets:lookup(table, y), ets:lookup(table, z); proc($2) -> ets:lookup(table, z); proc($3) -> ets:insert(table, {y, 1}); proc($4) -> ets:insert(table, {z, 1}), ets:insert(table, {y, 2}). ================================================ FILE: resources/perm_tests/src/depend_6_5.erl ================================================ -module(depend_6_5). -export([result/0, procs/0, run/1]). result() -> io:format("12"). procs() -> io:format("4"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> ets:lookup(table, y); proc($2) -> ets:lookup(table, z); proc($3) -> ets:insert(table, {y, 1}); proc($4) -> ets:insert(table, {z, 1}), ets:insert(table, {y, 2}). ================================================ FILE: resources/perm_tests/src/etsi.erl ================================================ -module(etsi). -export([result/0, procs/0, run/1]). result() -> io:format("7"). procs() -> io:format("3"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}), ets:insert(table, {z, 0}). proc($1) -> fun_abc(x, y, z); proc($2) -> fun_abc(y, z, x); proc($3) -> fun_abc(z, x, y). fun_abc(A, B, C) -> [{A, V}] = ets:lookup(table, A), case V of 0 -> ets:insert(table, {B, 1}); 1 -> ets:insert(table, {C, 0}) end. ================================================ FILE: resources/perm_tests/src/readers_2.erl ================================================ -module(readers_2). -export([result/0, procs/0, run/1]). result() -> io:format("4"). procs() -> io:format("3"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}). proc($1) -> ets:lookup(table, y), ets:insert(table, {x, 1}); proc($2) -> ets:lookup(table, y), ets:lookup(table, x); proc($3) -> proc($2). ================================================ FILE: resources/perm_tests/src/readers_3.erl ================================================ -module(readers_3). -export([result/0, procs/0, run/1]). result() -> io:format("8"). procs() -> io:format("4"). run(Procs) -> [S] = io_lib:format("~p",[Procs]), initial(), run_aux(S), block(). run_aux([]) -> ok; run_aux([P|R]) -> spawn(fun() -> proc(P) end), run_aux(R). block() -> receive after infinity -> never end. initial() -> ets:new(table, [public, named_table]), ets:insert(table, {x, 0}), ets:insert(table, {y, 0}). proc($1) -> ets:lookup(table, y), ets:insert(table, {x, 1}); proc($2) -> ets:lookup(table, y), ets:lookup(table, x); proc($3) -> proc($2); proc($4) -> proc($2). ================================================ FILE: resources/syntax/block_after.erl ================================================ %%%---------------------------------------------------------------------- %%% File : block_after.erl %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Test block expression in after clause %%% Created : 3 Jan 2010 %%%---------------------------------------------------------------------- -module(block_after). -export([block_after_test/0]). block_after_test() -> receive _Any -> ok after 42 -> foo, bar end. ================================================ FILE: resources/syntax/non_local_pat.erl ================================================ %%%---------------------------------------------------------------------- %%% File : non_local_pat.erl %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Test assignment to non-local variables in patterns %%% Created : 3 Jan 2010 %%%---------------------------------------------------------------------- -module(non_local_pat). -export([non_local_pat_test/0]). non_local_pat_test() -> Pid1 = spawn(fun() -> ok end), Pid2 = spawn(fun() -> ok end), receive C1 = {Pid1, _} -> nil end, receive C2 = {Pid2, _} -> Pid1 ! C2, Pid2 ! C1 end. ================================================ FILE: resources/syntax/rec_uscore.erl ================================================ %%%---------------------------------------------------------------------- %%% File : rec_uscore.erl %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Test underscore in record creation %%% Created : 3 Jan 2010 %%%---------------------------------------------------------------------- -module(rec_uscore). -export([rec_uscore_test/0]). -record(test, {foo :: integer(), bar :: atom(), baz :: atom()}). rec_uscore_test() -> _Rec = #test{foo = 42, _ = '_'}. ================================================ FILE: resources/syntax/strip_attr.erl ================================================ %%%---------------------------------------------------------------------- %%% File : strip_attr.erl %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Test stripping type and spec related attributes %%% (type, spec, opaque, export_type, import_type) %%% before instrumentation (import_type not tested). %%% Created : 9 Jan 2010 %%%---------------------------------------------------------------------- -module(strip_attr). -export([foo/0]). -export_type([mytype/0]). -record(myrec, {foo :: integer(), bar :: integer()}). -type mytype() :: #myrec{}. -opaque mytype_2() :: #myrec{}. -spec foo() -> {mytype(), mytype_2()}. foo() -> {#myrec{foo = 42, bar = 42}, #myrec{foo = 42, bar = 42}}. ================================================ FILE: resources/tdd/reg_server.erl ================================================ %%%---------------------------------------------------------------------- %%% Author : Alkis Gotovos %%% Description : Generic registration server %%%---------------------------------------------------------------------- -module(reg_server). -export([attach/0, detach/0, ping/0, start/0, stop/0]). -define(REG_NAME, reg_server). -define(REG_REQUEST, reg_request). -define(REG_REPLY, reg_reply). -include("reg_server.hrl"). -record(state, {free, reg}). %%%---------------------------------------------------------------------- %%% Exports %%%---------------------------------------------------------------------- attach() -> request(attach). detach() -> request(detach). ping() -> request(ping). start() -> case whereis(?REG_NAME) of undefined -> Pid = spawn(fun() -> loop(initState()) end), try register(?REG_NAME, Pid) of true -> ok catch error:badarg -> Pid ! {?REG_REQUEST, kill}, already_started end; _Pid -> already_started end. stop() -> request(stop). %%%---------------------------------------------------------------------- %%% Internals %%%---------------------------------------------------------------------- initState() -> FreeList = lists:seq(1, ?MAX_ATTACHED), #state{free = ordsets:from_list(FreeList), reg = dict:new()}. loop(#state{free = Free, reg = Reg} = State) -> receive {?REG_REQUEST, Target, attach} -> case dict:find(Target, Reg) of {ok, RegNum} -> reply(Target, RegNum), loop(State); error -> case ordsets:to_list(Free) of [] -> reply(Target, server_full), loop(State); [RegNum|NewFreeList] -> NewReg = dict:store(Target, RegNum, Reg), monitor(process, Target), reply(Target, RegNum), NewFree = ordsets:from_list(NewFreeList), NewState = State#state{free = NewFree, reg = NewReg}, loop(NewState) end end; {?REG_REQUEST, Target, detach} -> {Reply, NewFree, NewReg} = detach_proc(Target, Free, Reg), reply(Target, Reply), NewState = State#state{free = NewFree, reg = NewReg}, loop(NewState); {?REG_REQUEST, Target, ping} -> case dict:find(Target, Reg) of {ok, RegNum} -> reply(Target, RegNum); error -> reply(Target, pong) end, loop(State); {'DOWN', _Ref, process, Target, _Info} -> NewState = case dict:is_key(Target, Reg) of true -> {ok, NewFree, NewReg} = detach_proc(Target, Free, Reg), State#state{free = NewFree, reg = NewReg}; false -> State end, loop(NewState); {?REG_REQUEST, Target, stop} -> unregister(?REG_NAME), reply(Target, ok); {?REG_REQUEST, kill} -> killed end. detach_proc(Target, Free, Reg) -> case dict:is_key(Target, Reg) of false -> {ok, Free, Reg}; true -> RegNum = dict:fetch(Target, Reg), NewReg = dict:erase(Target, Reg), NewFree = ordsets:add_element(RegNum, Free), {ok, NewFree, NewReg} end. request(Request) -> case whereis(?REG_NAME) of undefined -> server_down; Pid -> Ref = monitor(process, Pid), Pid ! {?REG_REQUEST, self(), Request}, receive {?REG_REPLY, Reply} -> demonitor(Ref, [flush]), Reply; {'DOWN', Ref, process, Pid, _Reason} -> server_down end end. reply(Target, Reply) -> Target ! {?REG_REPLY, Reply}. ================================================ FILE: resources/tdd/reg_server.hrl ================================================ -define(MAX_ATTACHED, 2). ================================================ FILE: resources/tdd/reg_server_tests.erl ================================================ %%%---------------------------------------------------------------------- %%% Author : Alkis Gotovos %%% Description : Generic registration server tests %%%---------------------------------------------------------------------- -module(reg_server_tests). -include_lib("eunit/include/eunit.hrl"). -include("reg_server.hrl"). -export([start_stop_test/0, ping_test/0, multiple_stops_test/0, multiple_concurrent_stops_test/0, ping_failure_test/0, ping_concurrent_failure_test/0, multiple_starts_test/0, multiple_concurrent_starts_test/0, attach_noping_test/0, attach_test/0, max_attached_proc_test/0, detach_test/0, detach_attach_test/0, detach_non_attached_test/0, detach_on_exit_test/0]). start_stop_test() -> ?assertEqual(ok, reg_server:start()), ?assertEqual(ok, reg_server:stop()). ping_test() -> reg_server:start(), ?assertEqual(pong, reg_server:ping()), ?assertEqual(pong, reg_server:ping()), reg_server:stop(). multiple_stops_test() -> reg_server:start(), ?assertEqual(ok, reg_server:stop()), ?assertEqual(server_down, reg_server:stop()). multiple_concurrent_stops_test() -> Self = self(), reg_server:start(), spawn(fun() -> Self ! reg_server:stop() end), spawn(fun() -> Self ! reg_server:stop() end), ?assertEqual(lists:sort([ok, server_down]), lists:sort(receive_two())). ping_failure_test() -> ?assertEqual(server_down, reg_server:ping()), reg_server:start(), reg_server:stop(), ?assertEqual(server_down, reg_server:ping()). ping_concurrent_failure_test() -> reg_server:start(), spawn(fun() -> R = reg_server:ping(), Results = [pong, server_down], ?assertEqual(true, lists:member(R, Results)) end), reg_server:stop(). multiple_starts_test() -> reg_server:start(), ?assertEqual(already_started, reg_server:start()), reg_server:stop(). multiple_concurrent_starts_test() -> Self = self(), spawn(fun() -> Self ! reg_server:start() end), spawn(fun() -> Self ! reg_server:start() end), ?assertEqual(lists:sort([already_started, ok]), lists:sort(receive_two())), reg_server:stop(). attach_test() -> Self = self(), reg_server:start(), RegNum1 = reg_server:attach(), spawn(fun() -> RegNum2 = reg_server:attach(), ?assertEqual(RegNum2, reg_server:ping()), ?assertEqual(false, RegNum1 =:= RegNum2), Self ! done end), ?assertEqual(RegNum1, reg_server:ping()), receive done -> reg_server:stop() end. attach_noping_test() -> Self = self(), reg_server:start(), reg_server:attach(), spawn(fun() -> reg_server:attach() end), reg_server:stop(). already_attached_test() -> reg_server:start(), RegNum = reg_server:attach(), ?assertEqual(RegNum, reg_server:attach()), reg_server:stop(). max_attached_proc_test() -> reg_server:start(), Ps = [spawn_attach() || _ <- lists:seq(1, ?MAX_ATTACHED)], ?assertEqual(server_full, reg_server:attach()), lists:foreach(fun(Pid) -> Pid ! ok end, Ps), reg_server:stop(). detach_test() -> reg_server:start(), reg_server:attach(), reg_server:detach(), ?assertEqual(pong, reg_server:ping()), reg_server:stop(). detach_attach_test() -> Self = self(), reg_server:start(), Ps = [spawn_attach() || _ <- lists:seq(1, ?MAX_ATTACHED - 1)], LastProc = spawn(fun() -> RegNum = reg_server:attach(), reg_server:detach(), Self ! RegNum, receive ok -> ok end end), receive RegNum -> ok end, ?assertEqual(RegNum, reg_server:attach()), lists:foreach(fun(Pid) -> Pid ! ok end, [LastProc|Ps]), reg_server:stop(). detach_non_attached_test() -> reg_server:start(), ?assertEqual(ok, reg_server:detach()), reg_server:stop(). detach_on_exit_test() -> Self = self(), reg_server:start(), Ps = [spawn_attach() || _ <- lists:seq(1, ?MAX_ATTACHED - 1)], process_flag(trap_exit, true), LastProc = spawn_link(fun() -> Self ! reg_server:attach() end), receive RegNum -> ok end, receive {'EXIT', LastProc, normal} -> ok end, ?assertEqual(RegNum, reg_server:attach()), lists:foreach(fun(Pid) -> Pid ! ok end, [LastProc|Ps]), reg_server:stop(). %%%---------------------------------------------------------------------- %%% Helpers %%%---------------------------------------------------------------------- receive_two() -> receive Result1 -> receive Result2 -> [Result1, Result2] end end. attach_and_wait(Target) -> reg_server:attach(), Target ! done, receive ok -> ok end. spawn_attach() -> Self = self(), Pid = spawn(fun() -> attach_and_wait(Self) end), receive done -> Pid end. ================================================ FILE: resources/utest/concuerror_error_tests.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Error interface unit tests %%%---------------------------------------------------------------------- -module(concuerror_error_tests). -include_lib("eunit/include/eunit.hrl"). -include("gen.hrl"). %% Spec for auto-generated test/0 function (eunit). -spec test() -> 'ok' | {'error', term()}. -spec short_deadlock_test() -> 'ok'. short_deadlock_test() -> Lid1 = concuerror_lid:mock(1), Lid2 = concuerror_lid:mock(2), Blocked = ?SETS:add_element(Lid1, ?SETS:add_element(Lid2, ?SETS:new())), Error = concuerror_error:new({deadlock, Blocked}), ?assertEqual("P1, P2", concuerror_error:short(Error)). -spec short_system_exception_test() -> 'ok'. short_system_exception_test() -> Stack = [{erlang,link,[c:pid(0, 2, 3)]},{sched,rep_link,1},{test,test08,0}], Error = concuerror_error:new({noproc, Stack}), ?assertEqual("{noproc,[...]}", concuerror_error:short(Error)). -spec short_user_exception_test() -> 'ok'. short_user_exception_test() -> Error = concuerror_error:new(foobar), ?assertEqual("foobar", concuerror_error:short(Error)). -spec short_user_exception_similar_to_system_test() -> 'ok'. short_user_exception_similar_to_system_test() -> Error = concuerror_error:new({foo, bar}), ?assertEqual("{foo,bar}", concuerror_error:short(Error)). -spec short_assert_equal_violation_test() -> 'ok'. short_assert_equal_violation_test() -> Error = concuerror_error:new({{assertEqual_failed, [{module, mymodule}, {line, 42}, {expression, "false"}, {expected, true}, {value, false}]}, []}), ?assertEqual("mymodule.erl:42", concuerror_error:short(Error)). -spec short_assert_violation_test() -> 'ok'. short_assert_violation_test() -> Error = concuerror_error:new({{assertion_failed, [{module, mymodule}, {line, 42}, {expression, "true =:= false"}, {expected, true}, {value, false}]}, []}), ?assertEqual("mymodule.erl:42", concuerror_error:short(Error)). ================================================ FILE: resources/utest/concuerror_instr_tests.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Authors : Alkis Gotovos %%% Maria Christakis %%% Description : Instrumenter unit tests %%%---------------------------------------------------------------------- -module(concuerror_instr_tests). -include_lib("eunit/include/eunit.hrl"). -include("gen.hrl"). -define(TEST_PATH, "./resources/syntax/"). %% Spec for auto-generated test/0 function (eunit). -spec test() -> 'ok' | {'error', term()}. -spec syntax_test_() -> term(). syntax_test_() -> Setup = fun() -> _ = concuerror_log:start(), _ = concuerror_log:attach(concuerror_log, []), ?NT_OPTIONS = ets:new(?NT_OPTIONS, [named_table, public, set]) end, Cleanup = fun(_Any) -> ets:delete(?NT_OPTIONS), concuerror_log:stop() end, Test01 = {"Block expression in after clause", fun(_Any) -> test_ok("block_after.erl") end}, Test02 = {"Assignments to non-local variables in patterns", fun(_Any) -> test_ok("non_local_pat.erl") end}, Test03 = {"Underscore in record creation", fun(_Any) -> test_ok("rec_uscore.erl") end}, Test04 = {"Strip types and specs", fun(_Any) -> test_ok("strip_attr.erl") end}, Tests = [Test01, Test02, Test03, Test04], Inst = fun(X) -> [{D, fun() -> T(X) end} || {D, T} <- Tests] end, {foreach, local, Setup, Cleanup, [Inst]}. test_ok(File) -> %% Initialize test Path = filename:join([?TEST_PATH, File]), Result = concuerror_instr:instrument_and_compile([Path], []), %% Cleanup test concuerror_instr:delete_and_purge([]), %% Assert Result ?assertMatch({ok, _Bin}, Result). ================================================ FILE: resources/utest/concuerror_lid_tests.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Author : Alkis Gotovos %%% Description : LID interface unit tests %%%---------------------------------------------------------------------- -module(concuerror_lid_tests). -include_lib("eunit/include/eunit.hrl"). -include("gen.hrl"). %% Spec for auto-generated test/0 function (eunit). -spec test() -> 'ok' | {'error', term()}. -spec one_proc_test_() -> term(). one_proc_test_() -> Setup = fun() -> concuerror_lid:start(), Pid = c:pid(0, 2, 3), Lid = concuerror_lid:new(Pid, noparent), {Pid, Lid} end, Cleanup = fun(_Any) -> concuerror_lid:stop() end, Test1 = {"LID to Pid", fun({Pid, Lid}) -> ?assertEqual(Pid,concuerror_lid:get_pid(Lid)) end}, Test2 = {"Pid to LID", fun({Pid, Lid}) -> ?assertEqual(Lid,concuerror_lid:from_pid(Pid)) end}, Test3 = {"Parent -> Child", fun({_Pid, Lid}) -> ChildPid = c:pid(0, 2, 4), ChildLid = concuerror_lid:new(ChildPid, Lid), ?assertEqual(ChildPid, concuerror_lid:get_pid(ChildLid)), ?assertEqual(ChildLid, concuerror_lid:from_pid(ChildPid)) end}, Tests = [Test1, Test2, Test3], Inst = fun(X) -> [{D, fun() -> T(X) end} || {D, T} <- Tests] end, {foreach, local, Setup, Cleanup, [Inst]}. -spec two_proc_test_() -> term(). two_proc_test_() -> Setup = fun() -> concuerror_lid:start(), Pid1 = spawn(fun() -> receive ok -> ok end end), Pid2 = spawn(fun() -> receive ok -> ok end end), Lid1 = concuerror_lid:new(Pid1, noparent), Lid2 = concuerror_lid:new(Pid2, Lid1), {Pid1, Pid2, Lid1, Lid2} end, Cleanup = fun({Pid1, Pid2, _Lid1, _Lid2}) -> Pid1 ! ok, Pid2 ! ok, concuerror_lid:stop() end, Test1 = {"Fold Pids", fun({Pid1, Pid2, _Lid1, _Lid2}) -> Fun = fun(P, A) -> [P|A] end, Result = concuerror_lid:fold_pids(Fun, []), ?assertEqual(lists:member(Pid1, Result), true), ?assertEqual(lists:member(Pid2, Result), true) end}, Test2 = {"Cleanup", fun({Pid1, _Pid2, Lid1, _Lid2}) -> concuerror_lid:cleanup(Lid1), ?assertEqual(not_found, concuerror_lid:from_pid(Pid1)), ?assertEqual(not_found, concuerror_lid:get_pid(Lid1)) end}, Tests = [Test1, Test2], Inst = fun(X) -> [{D, fun() -> T(X) end} || {D, T} <- Tests] end, {foreach, local, Setup, Cleanup, [Inst]}. ================================================ FILE: resources/utest/concuerror_state_tests.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Author : Alkis Gotovos %%% Description : State interface unit tests %%%---------------------------------------------------------------------- -module(concuerror_state_tests). -include_lib("eunit/include/eunit.hrl"). %% Spec for auto-generated test/0 function (eunit). -spec test() -> 'ok' | {'error', term()}. -spec extend_trim_head_test() -> 'ok'. extend_trim_head_test() -> concuerror_lid:start(), Init = concuerror_state:empty(), Lid = concuerror_lid:new(c:pid(0, 2, 3), noparent), State1 = concuerror_state:extend(Init, Lid), State2 = concuerror_state:pack(State1), {NewLid, NewState} = concuerror_state:trim_head(State2), ?assertEqual(NewLid, Lid), ?assertEqual(true, concuerror_state:is_empty(NewState)), concuerror_lid:stop(). -spec extend_trim_tail_test() -> 'ok'. extend_trim_tail_test() -> concuerror_lid:start(), Init = concuerror_state:empty(), Lid = concuerror_lid:new(c:pid(0, 2, 3), noparent), State1 = concuerror_state:extend(Init, Lid), State2 = concuerror_state:pack(State1), {NewLid, NewState} = concuerror_state:trim_tail(State2), ?assertEqual(NewLid, Lid), ?assertEqual(true, concuerror_state:is_empty(NewState)), concuerror_lid:stop(). ================================================ FILE: resources/utest/concuerror_ticket_tests.erl ================================================ %%%---------------------------------------------------------------------- %%% Copyright (c) 2011, Alkis Gotovos , %%% Maria Christakis %%% and Kostis Sagonas . %%% All rights reserved. %%% %%% This file is distributed under the Simplified BSD License. %%% Details can be found in the LICENSE file. %%%---------------------------------------------------------------------- %%% Author : Alkis Gotovos %%% Maria Christakis %%% Description : Ticket interface unit tests %%%---------------------------------------------------------------------- -module(concuerror_ticket_tests). -include_lib("eunit/include/eunit.hrl"). %% Spec for auto-generated test/0 function (eunit). -spec test() -> 'ok' | {'error', term()}. -spec get_error_test() -> 'ok'. get_error_test() -> Error = concuerror_error:mock(), Pid = spawn(fun() -> ok end), concuerror_lid:start(), Lid = concuerror_lid:new(Pid, noparent), Actions = [{'after', Lid}, {'block', Lid}], Ticket = concuerror_ticket:new(Error, Actions), concuerror_lid:stop(), ?assertEqual(Error, concuerror_ticket:get_error(Ticket)). -spec get_details_test() -> 'ok'. get_details_test() -> Error = concuerror_error:mock(), Pid = spawn(fun() -> ok end), concuerror_lid:start(), Lid = concuerror_lid:new(Pid, noparent), Actions = [{'after', Lid}, {'block', Lid}], Ticket = concuerror_ticket:new(Error, Actions), concuerror_lid:stop(), ?assertEqual(Actions, concuerror_ticket:get_details(Ticket)). ================================================ FILE: src/.gitignore ================================================ concuerror_otp_version.hrl ================================================ FILE: src/concuerror.app.src ================================================ {application, concuerror, [ {description, "Concuerror: Stateless Model Checking tool for Erlang programs" } , {vsn, "git"} , {applications, [kernel, stdlib]} , {maintainers, [ "Stavros Aronis" , "Kostis Sagonas" ]} , {licenses, ["BSD-2-Clause"]} , {links, [ {"Github", "https://github.com/parapluu/Concuerror"} , {"Website", "http://parapluu.github.io/Concuerror"} ]} ]}. ================================================ FILE: src/concuerror.erl ================================================ %%% @doc %%% Concuerror's main module %%% %%% Contains the entry points for invoking Concuerror, either directly %%% from the command-line or from an Erlang program. %%% %%% For general documentation go to the Overview page. -module(concuerror). -export([main/1, run/1, version/0]). %% Internal export for documentation belonging in this module -export([analysis_result_documentation/0]). %% Internal functions for reloading -export([main_internal/1, run_internal/1]). %%------------------------------------------------------------------------------ -export_type([analysis_result/0]). -type analysis_result() :: 'ok' | 'error' | 'fail'. %% @type analysis_result() = 'ok' | 'error' | 'fail'. %% Meaning of Concuerror's analysis results, as returned from {@link %% concuerror:run/1} (the corresponding exit status %% returned by {@link concuerror:main/1} is given in parenthesis): %%
%%
`ok' (exit status: 0)
%%
%% the analysis terminated and found no errors %%
%%
`error' (exit status: 1)
%%
%% the analysis terminated and found errors (see the %% {@link concuerror_options:output_option/0. `output'} option) %%
%%
`fail' (exit status: 2)
%%
%% the analysis failed and it might have found errors or not %%
%%
%%------------------------------------------------------------------------------ -include("concuerror.hrl"). %%------------------------------------------------------------------------------ %% @doc %% Command-line entry point. %% %% This function can be used to invoke Concuerror from the %% command-line. %% %% It accepts a list of strings as argument. This list is processed by %% {@link concuerror_options:parse_cl/1} and the result is passed to %% {@link concuerror:run/1}. %% %% When {@link concuerror:run/1} returns, the Erlang VM will %% terminate, with an exit value corresponding to the {@link %% analysis_result()}. -spec main([string()]) -> no_return(). main(Args) -> _ = application:load(concuerror), maybe_cover_compile(), ?MODULE:main_internal(Args). %% @private -spec main_internal([string()]) -> no_return(). main_internal(Args) -> AnalysisResult = case concuerror_options:parse_cl(Args) of {run, Options} -> run(Options); {return, Result} -> Result end, ExitStatus = case AnalysisResult of ok -> 0; error -> 1; fail -> 2 end, maybe_cover_export(Args), erlang:halt(ExitStatus). %%------------------------------------------------------------------------------ %% @doc %% Erlang entry point. %% %% This function can be used to invoke Concuerror from an Erlang %% program. This is the recommended way to invoke Concuerror when you %% use it as part of a test suite. %% %% This function accepts a `proplists' list as argument. The %% supported properties are specified at {@link concuerror_options}. %% %% The meaning of the return values is explained at {@link %% analysis_result()}. -spec run(concuerror_options:options()) -> analysis_result(). run(Options) -> _ = application:load(concuerror), maybe_cover_compile(), ?MODULE:run_internal(Options). %% @private -spec run_internal(concuerror_options:options()) -> analysis_result(). run_internal(Options) -> Status = case concuerror_options:finalize(Options) of {run, FinalOptions, LogMsgs} -> start(FinalOptions, LogMsgs); {return, ExitStatus} -> ExitStatus end, maybe_cover_export(Options), Status. %%----------------------------------------------------------------------------- %% @doc %% Returns a string representation of Concuerror's version. -spec version() -> string(). version() -> _ = application:load(concuerror), {ok, Vsn} = application:get_key(concuerror, vsn), io_lib:format("Concuerror ~s", [Vsn]). %%------------------------------------------------------------------------------ -type string_constant() :: [1..255, ...]. % Dialyzer underspecs is unhappy otw. %% @private -spec analysis_result_documentation() -> string_constant(). analysis_result_documentation() -> "" "Exit status:~n" " 0 ('ok') : Analysis completed. No errors were found.~n" " 1 ('error') : Analysis completed. Errors were found.~n" " 2 ('fail') : Analysis failed to complete.~n". %%------------------------------------------------------------------------------ start(Options, LogMsgs) -> error_logger:tty(false), Processes = ets:new(processes, [public]), Estimator = concuerror_estimator:start_link(Options), LoggerOptions = [{estimator, Estimator}, {processes, Processes}|Options], Logger = concuerror_logger:start(LoggerOptions), _ = [?log(Logger, Level, Format, Args) || {Level, Format, Args} <- LogMsgs], SchedulerOptions = [{logger, Logger}|LoggerOptions], {Pid, Ref} = spawn_monitor(concuerror_scheduler, run, [SchedulerOptions]), Reason = receive {'DOWN', Ref, process, Pid, R} -> R end, SchedulerStatus = case Reason =:= normal of true -> normal; false -> ?error(Logger, "~s~n", [explain(Reason)]), failed end, ?trace(Logger, "Reached the end!~n", []), ExitStatus = concuerror_logger:stop(Logger, SchedulerStatus), concuerror_estimator:stop(Estimator), ets:delete(Processes), ExitStatus. %%------------------------------------------------------------------------------ maybe_cover_compile() -> Cover = os:getenv("CONCUERROR_COVER"), case get(concuerror_cover) =:= undefined andalso Cover =/= false of true -> put(concuerror_cover, Cover), case cover:is_compiled(?MODULE) of false -> {ok, Modules} = application:get_key(concuerror, modules), [_|_] = cover:compile_beam(Modules), ok; _ -> ok end; false -> ok end. %%------------------------------------------------------------------------------ maybe_cover_export(Args) -> Cover = erase(concuerror_cover), case Cover =/= undefined of true -> Hash = binary:decode_unsigned(erlang:md5(term_to_binary(Args))), Out = filename:join([Cover, io_lib:format("~.16b", [Hash])]), cover:export(Out), ok; false -> ok end. %%------------------------------------------------------------------------------ explain(Reason) -> try {Module, Info} = Reason, Module:explain_error(Info) catch _:_ -> io_lib:format("~n Reason: ~p", [Reason]) end. ================================================ FILE: src/concuerror.hrl ================================================ -include("concuerror_otp_version.hrl"). %%------------------------------------------------------------------------------ -define(join(Strings, Sep), lists:join(Sep, Strings)). %%------------------------------------------------------------------------------ -ifdef(SENSITIVE_DEBUG). -define(display(A), erlang:display({A, ?MODULE, ?LINE})). -else. -define(display(A, B), io:format(standard_error, "# ~p ~p l~p: "++A++"~n", [self(), ?MODULE, ?LINE|B])). -define(display(A), ?display("~w",[A])). -endif. %%------------------------------------------------------------------------------ -ifdef(DEBUG_FLAGS). -ifndef(DEBUG). -define(DEBUG, true). -endif. -endif. %%------------------------------------------------------------------------------ -ifdef(DEBUG). -define(debug(A), ?display(A)). -define(debug(A, B), ?display(A, B)). -define(if_debug(A), A). -else. -define(debug(_A), ok). -define(debug(_A, _B), ok). -define(if_debug(_A), ok). -endif. %%------------------------------------------------------------------------------ -ifdef(DEBUG_FLAGS). -define(debug_flag(A, B), case (?DEBUG_FLAGS band A) =/= 0 of true -> ?display(B); false -> ok end). -define(debug_flag(A, B, C), case (?DEBUG_FLAGS band A) =/= 0 of true ->?display(B, C); false -> ok end). -else. -define(debug_flag(_A, _B), ?debug(_B)). -define(debug_flag(_A, _B, _C), ?debug(_B, _C)). -endif. %%------------------------------------------------------------------------------ -define(opt(A,O), proplists:get_value(A,O)). -define(opt(A,O,D), proplists:get_value(A,O,D)). %%------------------------------------------------------------------------------ %% Logger levels %% See concuerror_options:verbosity/0 for meaning of each level -define(lquiet, 0). -define(lerror, 1). -define(lwarning, 2). -define(ltip, 3). -define(linfo, 4). -define(ltiming, 5). -define(ldebug, 6). -define(ltrace, 7). -define(MAX_LOG_LEVEL, ?ltrace). %%------------------------------------------------------------------------------ -define(nonunique, none). -define(log(Logger, Level, Tag, Format, Data), concuerror_logger:log(Logger, Level, Tag, Format, Data)). -define(log(Logger, Level, Format, Data), ?log(Logger, Level, ?nonunique, Format, Data)). -define(error(Logger, Format, Data), ?log(Logger, ?lerror, Format, Data)). -ifdef(DEV). -define(dev_log(Logger, Level, Format, Data), ?log(Logger, Level, "(~-25w@~4w) " ++ Format, [?MODULE, ?LINE| Data])). -define(debug(Logger, Format, Data), ?dev_log(Logger, ?ldebug, Format, Data)). -define(trace(Logger, Format, Data), ?dev_log(Logger, ?ltrace, Format, Data)). -define(has_dev, true). -else. -define(debug(Logger, Format, Data),ok). -define(trace(Logger, Format, Data),ok). -define(has_dev, false). -endif. -define(unique(Logger, Level, Param, Format, Data), ?log(Logger, Level, {?MODULE, ?LINE, Param}, Format, Data)). -define(unique(Logger, Level, Format, Data), ?unique(Logger, Level, none, Format, Data)). -define(time(Logger, Tag), concuerror_logger:time(Logger, Tag)). -define( autoload_and_log(Module, Logger), case concuerror_loader:load(Module) of already_done -> ok; {ok, Warn} -> ?log(Logger, ?linfo, "Automatically instrumented module ~p~n", [Module]), _ = [?log(Logger, ?lwarning, W, []) || W <- Warn], ok; fail -> ?log(Logger, ?lwarning, "Could not load module '~p'. Check '-h input'.~n", [Module]), ok end). -define(pretty_s(I,E), concuerror_io_lib:pretty_s({I,E#event{location = []}},5)). -define(pretty_s(E), ?pretty_s(0,E)). %%------------------------------------------------------------------------------ -define(crash(Reason), exit({?MODULE, Reason})). -define(can_fix_msg, " If you really need this functionality, contact the developers."). -define(notify_us_msg, " Please notify the developers, as this is a bug of Concuerror."). %%------------------------------------------------------------------------------ -type processes() :: ets:tid(). -define(process_name_none, 0). %%------------------------------------------------------------------------------ -type label() :: reference(). -type mfargs() :: {atom(), atom(), [term()]}. -type location() :: 'exit' | [non_neg_integer() | {file, string()}]. -type index() :: non_neg_integer(). -record(builtin_event, { actor = self() :: pid(), extra :: term(), exiting = false :: boolean(), mfargs :: mfargs(), result :: term(), status = ok :: 'ok' | {'crashed', term()} | 'unknown', trapping = false :: boolean() }). -type builtin_event() :: #builtin_event{}. -type message_id() :: {pid(), pos_integer()} | 'hidden'. -record(message, { data :: term(), id :: message_id() }). -type message() :: #message{}. -type receive_pattern_fun() :: fun((term()) -> boolean()). -type receive_info() :: 'undefined' | 'not_received' | {pos_integer() | 'system', receive_pattern_fun()}. -record(message_event, { cause_label :: label(), instant = true :: boolean(), killing = false :: boolean(), message :: message(), receive_info :: receive_info(), recipient :: pid(), sender = self() :: pid(), trapping = false :: boolean(), type = message :: 'message' | 'exit_signal' }). -type message_event() :: #message_event{}. -record(receive_event, { %% clause_location :: location(), message :: message() | 'after', receive_info :: receive_info(), recipient = self() :: pid(), timeout = infinity :: timeout(), trapping = false :: boolean() }). -type receive_event() :: #receive_event{}. -record(exit_event, { actor = self() :: pid() | reference(), last_status = running :: running | waiting, exit_by_signal = false :: boolean(), links = [] :: [pid()], monitors = [] :: [{reference(), pid()}], name = ?process_name_none :: ?process_name_none | atom(), reason = normal :: term(), stacktrace = [] :: [term()], trapping = false :: boolean() }). -type exit_event() :: #exit_event{}. -type event_info() :: builtin_event() | exit_event() | message_event() | receive_event(). -type channel() :: {pid(), pid()}. -type actor() :: pid() | channel(). -define(is_channel(A), is_tuple(A)). -record(event, { actor :: 'undefined' | actor(), event_info :: 'undefined' | event_info(), label :: 'undefined' | label(), location = [] :: location(), special = [] :: [term()] %% XXX: Specify }). -type event() :: #event{}. ================================================ FILE: src/concuerror_callback.erl ================================================ %%% @private %%% @doc %%% This module contains code for: %%% - managing and interfacing with processes under Concuerror %%% - simulating built-in operations in instrumented processes -module(concuerror_callback). %% Interface to concuerror_inspect: -export([instrumented/4]). %% Interface to scheduler: -export([spawn_first_process/1, start_first_process/3, deliver_message/3, wait_actor_reply/2, collect_deadlock_info/1, enabled/1, reset_processes/1, cleanup_processes/1]). %% Interface to logger: -export([setup_logger/1]). %% Interface for resetting: -export([process_top_loop/1]). %% Interface to instrumenters: -export([is_unsafe/1]). -export([wrapper/4]). -export([explain_error/1]). %%------------------------------------------------------------------------------ %% DEBUGGING SETTINGS -define(flag(A), (1 bsl A)). -define(builtin, ?flag(1)). -define(non_builtin, ?flag(2)). -define(receive_, ?flag(3)). -define(receive_messages, ?flag(4)). -define(args, ?flag(6)). -define(result, ?flag(7)). -define(spawn, ?flag(8)). -define(short_builtin, ?flag(9)). -define(loop, ?flag(10)). -define(send, ?flag(11)). -define(exit, ?flag(12)). -define(trap, ?flag(13)). -define(undefined, ?flag(14)). -define(heir, ?flag(15)). -define(notify, ?flag(16)). -define(ACTIVE_FLAGS, [ ?undefined , ?short_builtin , ?loop , ?notify , ?non_builtin ]). %%-define(DEBUG, true). %%-define(DEBUG_FLAGS, lists:foldl(fun erlang:'bor'/2, 0, ?ACTIVE_FLAGS)). -define(badarg_if_not(A), case A of true -> ok; false -> error(badarg) end). %%------------------------------------------------------------------------------ -include("concuerror.hrl"). -define(crash_instr(Reason), exit(self(), {?MODULE, Reason})). %%------------------------------------------------------------------------------ %% In order to be able to keep TIDs constant and reset the system %% properly, Concuerror covertly hands all ETS tables to its scheduler %% and maintains extra info to determine operation access-rights. -type ets_tables() :: ets:tid(). -define(ets_name_none, 0). -define(ets_table_entry(Tid, Name, Owner, Protection, Heir, System), {Tid, Name, Owner, Protection, Heir, System, true}). -define(ets_table_entry_system(Tid, Name, Protection, Owner), ?ets_table_entry(Tid, Name, Owner, Protection, {heir, none}, true)). -define(ets_tid, 1). -define(ets_name, 2). -define(ets_owner, 3). -define(ets_protection, 4). -define(ets_heir, 5). -define(ets_system, 6). -define(ets_alive, 7). -define(ets_match_owner_to_heir_info(Owner), {'$2', '$3', Owner, '_', '$1', '_', true}). -define(ets_match_tid_to_permission_info(Tid), {Tid, '$3', '$1', '$2', '_', '$4', true}). -define(ets_match_name_to_tid(Name), {'$1', Name, '_', '_', '_', '_', true}). -define(ets_pattern_mine(), {'_', '_', self(), '_', '_', '_', '_'}). -define(persistent_term, persistent_term_bypass). %%------------------------------------------------------------------------------ -type links() :: ets:tid(). -define(links(Pid1, Pid2), [{Pid1, Pid2, active}, {Pid2, Pid1, active}]). %%------------------------------------------------------------------------------ -type monitors() :: ets:tid(). -define(monitor(Ref, Target, As, Status), {Target, {Ref, self(), As}, Status}). -define(monitor_match_to_target_source_as(Ref), {'$1', {Ref, self(), '$2'}, '$3'}). -define(monitor_status, 3). %%------------------------------------------------------------------------------ -define(new_process(Pid, Symbolic), { Pid , exited , ?process_name_none , ?process_name_none , undefined , Symbolic , 0 , regular }). -define(new_system_process(Pid, Name, Type), { Pid , running , Name , Name , undefined , "P." ++ atom_to_list(Name) , 0 , Type }). -define(process_status, 2). -define(process_name, 3). -define(process_last_name, 4). -define(process_leader, 5). -define(process_symbolic, 6). -define(process_children, 7). -define(process_kind, 8). -define(process_pat_pid(Pid), {Pid, _, _, _, _, _, _, _}). -define(process_pat_pid_name(Pid, Name), {Pid, _, Name, _, _, _, _, _}). -define(process_pat_pid_status(Pid, Status), {Pid, Status, _, _, _, _, _, _}). -define(process_pat_pid_kind(Pid, Kind), {Pid, _, _, _, _, _, _, Kind}). -define(process_match_name_to_pid(Name), {'$1', '_', Name, '_', '_', '_', '_', '_'}). -define(process_match_symbol_to_pid(Symbol), {'$1', '_', '_', '_', '_', Symbol, '_', '_'}). -define(process_match_active(), { {'$1', '$2', '_', '_', '_', '_', '_', '_'} , [ {'=/=', '$2', exited} , {'=/=', '$2', exiting} ] , ['$1'] }). %%------------------------------------------------------------------------------ -type timers() :: ets:tid(). %%------------------------------------------------------------------------------ -type ref_queue() :: queue:queue(reference()). -type message_queue() :: queue:queue(#message{}). -type ref_queue_2() :: {ref_queue(), ref_queue()}. -type status() :: 'running' | 'waiting' | 'exiting' | 'exited'. -define(notify_none, 1). -record(process_flags, { trap_exit = false :: boolean(), priority = normal :: 'low' | 'normal' | 'high' | 'max' }). -record(concuerror_info, { after_timeout :: 'infinite' | integer(), delayed_notification = none :: 'none' | {'true', term()}, demonitors = [] :: [reference()], ets_tables :: ets_tables(), exit_by_signal = false :: boolean(), exit_reason = normal :: term(), extra :: term(), flags = #process_flags{} :: #process_flags{}, initial_call :: 'undefined' | mfa(), instant_delivery :: boolean(), is_timer = false :: 'false' | reference(), links :: links(), logger :: concuerror_logger:logger(), message_counter = 1 :: pos_integer(), message_queue = queue:new() :: message_queue(), monitors :: monitors(), event = none :: 'none' | event(), notify_when_ready :: {pid(), boolean()}, processes :: processes(), receive_counter = 1 :: pos_integer(), ref_queue = new_ref_queue() :: ref_queue_2(), scheduler :: concuerror_scheduler:scheduler(), status = 'running' :: status(), system_ets_entries :: ets:tid(), timeout :: timeout(), timers :: timers() }). -type concuerror_info() :: #concuerror_info{}. %%------------------------------------------------------------------------------ -spec spawn_first_process(concuerror_options:options()) -> pid(). spawn_first_process(Options) -> Logger = ?opt(logger, Options), Info = #concuerror_info{ after_timeout = ?opt(after_timeout, Options), ets_tables = ets:new(ets_tables, [public]), instant_delivery = ?opt(instant_delivery, Options), links = ets:new(links, [bag, public]), logger = Logger, monitors = ets:new(monitors, [bag, public]), notify_when_ready = {self(), true}, processes = Processes = ?opt(processes, Options), scheduler = self(), system_ets_entries = ets:new(system_ets_entries, [bag, public]), timeout = ?opt(timeout, Options), timers = ets:new(timers, [public]) }, ?persistent_term = ets:new(?persistent_term, [named_table, public]), system_processes_wrappers(Info), system_ets_entries(Info), ?autoload_and_log(error_handler, Logger), P = new_process(Info), true = ets:insert(Processes, ?new_process(P, "P")), {DefLeader, _} = run_built_in(erlang, whereis, 1, [user], Info), true = ets:update_element(Processes, P, {?process_leader, DefLeader}), P. -spec start_first_process(pid(), {atom(), atom(), [term()]}, timeout()) -> ok. start_first_process(Pid, {Module, Name, Args}, Timeout) -> request_system_reset(Pid), Pid ! {start, Module, Name, Args}, ok = wait_process(Pid, Timeout), ok. -spec setup_logger(processes()) -> ok. setup_logger(Processes) -> concuerror_inspect:start_inspection({logger, Processes}). %%------------------------------------------------------------------------------ -type instrumented_return() :: 'doit' | {'didit', term()} | {'error', term()} | {'skip_timeout', 'false' | {'true', term()}}. -spec instrumented(Tag :: concuerror_inspect:instrumented_tag(), Args :: [term()], Location :: term(), Info :: concuerror_info()) -> {instrumented_return(), concuerror_info()}. instrumented(call, [Module, Name, Args], Location, Info) -> Arity = length(Args), instrumented_call(Module, Name, Arity, Args, Location, Info); instrumented(apply, [Fun, Args], Location, Info) -> case is_function(Fun) of true -> Module = get_fun_info(Fun, module), Name = get_fun_info(Fun, name), Arity = get_fun_info(Fun, arity), case length(Args) =:= Arity of true -> instrumented_call(Module, Name, Arity, Args, Location, Info); false -> {doit, Info} end; false -> {doit, Info} end; instrumented('receive', [PatternFun, RealTimeout], Location, Info) -> case Info of #concuerror_info{after_timeout = AfterTimeout} -> Timeout = case RealTimeout =:= infinity orelse RealTimeout >= AfterTimeout of false -> RealTimeout; true -> infinity end, handle_receive(PatternFun, Timeout, Location, Info); _Logger -> {doit, Info} end. instrumented_call(Module, Name, Arity, Args, _Location, {logger, Processes} = Info) -> case {Module, Name, Arity} of {erlang, pid_to_list, 1} -> [Term] = Args, try Symbol = ets:lookup_element(Processes, Term, ?process_symbolic), PName = ets:lookup_element(Processes, Term, ?process_last_name), Pretty = case PName =:= ?process_name_none of true -> "<" ++ Symbol ++ ">"; false -> lists:flatten(io_lib:format("<~s/~s>", [Symbol, PName])) end, {{didit, Pretty}, Info} catch _:_ -> {doit, Info} end; {erlang, fun_to_list, 1} -> %% Slightly prettier printer than the default... [Fun] = Args, [M, F, A] = [I || {_, I} <- [erlang:fun_info(Fun, T) || T <- [module, name, arity]]], String = lists:flatten(io_lib:format("#Fun<~p.~p.~p>", [M, F, A])), {{didit, String}, Info}; _ -> {doit, Info} end; instrumented_call(erlang, apply, 3, [Module, Name, Args], Location, Info) -> instrumented_call(Module, Name, length(Args), Args, Location, Info); instrumented_call(Module, Name, Arity, Args, Location, Info) when is_atom(Module) -> case erlang:is_builtin(Module, Name, Arity) andalso is_unsafe({Module, Name, Arity}) of true -> built_in(Module, Name, Arity, Args, Location, Info); false -> #concuerror_info{logger = Logger} = Info, ?debug_flag(?non_builtin, {Module, Name, Arity, Location}), ?autoload_and_log(Module, Logger), {doit, Info} end; instrumented_call({Module, _} = Tuple, Name, Arity, Args, Location, Info) -> instrumented_call(Module, Name, Arity + 1, Args ++ Tuple, Location, Info); instrumented_call(_, _, _, _, _, Info) -> {doit, Info}. get_fun_info(Fun, Tag) -> {Tag, Info} = erlang:fun_info(Fun, Tag), Info. %%------------------------------------------------------------------------------ built_in(erlang, Display, 1, [Term], _Location, Info) when Display =:= display; Display =:= display_string -> ?debug_flag(?builtin, {'built-in', erlang, Display, 1, [Term], _Location}), Chars = case Display of display -> io_lib:format("~w~n", [Term]); display_string -> _ = erlang:list_to_atom(Term), % Will throw badarg if not string. Term end, concuerror_logger:print(Info#concuerror_info.logger, standard_io, Chars), {{didit, true}, Info}; %% Inner process dictionary has been restored here. No need to report such ops. %% Also can't fail, as only true builtins reach this code. built_in(erlang, Name, _Arity, Args, _Location, Info) when Name =:= get; Name =:= get_keys; Name =:= put; Name =:= erase -> {{didit, erlang:apply(erlang, Name, Args)}, Info}; built_in(erlang, hibernate, 3, Args, _Location, Info) -> [Module, Name, HibArgs] = Args, self() ! {start, Module, Name, HibArgs}, erlang:hibernate(?MODULE, process_top_loop, [Info]); built_in(erlang, get_stacktrace, 0, [], _Location, Info) -> Stacktrace = clean_stacktrace(erlang_get_stacktrace()), {{didit, Stacktrace}, Info}; %% Instrumented processes may just call pid_to_list (we instrument this builtin %% for the logger) built_in(erlang, pid_to_list, _Arity, _Args, _Location, Info) -> {doit, Info}; built_in(erlang, system_info, 1, [A], _Location, Info) when A =:= os_type; A =:= schedulers; A =:= logical_processors_available; A =:= otp_release -> {doit, Info}; %% XXX: Check if its redundant (e.g. link to already linked) built_in(Module, Name, Arity, Args, Location, InfoIn) -> Info = process_loop(InfoIn), ?debug_flag(?short_builtin, {'built-in', Module, Name, Arity, Location}), #concuerror_info{flags = #process_flags{trap_exit = Trapping}} = LocatedInfo = add_location_info(Location, Info#concuerror_info{extra = undefined}), try {Value, UpdatedInfo} = run_built_in(Module, Name, Arity, Args, LocatedInfo), #concuerror_info{extra = Extra, event = MaybeMessageEvent} = UpdatedInfo, Event = maybe_deliver_message(MaybeMessageEvent, UpdatedInfo), ?debug_flag(?builtin, {'built-in', Module, Name, Arity, Value, Location}), ?debug_flag(?args, {args, Args}), ?debug_flag(?result, {args, Value}), EventInfo = #builtin_event{ exiting = Location =:= exit, extra = Extra, mfargs = {Module, Name, Args}, result = Value, trapping = Trapping }, Notification = Event#event{event_info = EventInfo}, NewInfo = notify(Notification, UpdatedInfo), {{didit, Value}, NewInfo} catch throw:Reason -> #concuerror_info{scheduler = Scheduler} = Info, ?debug_flag(?loop, crashing), exit(Scheduler, {Reason, Module, Name, Arity, Args, Location}), receive after infinity -> ok end; error:Reason -> #concuerror_info{event = FEvent} = LocatedInfo, FEventInfo = #builtin_event{ mfargs = {Module, Name, Args}, status = {crashed, Reason}, trapping = Trapping }, FNotification = FEvent#event{event_info = FEventInfo}, FinalInfo = notify(FNotification, LocatedInfo), {{error, Reason}, FinalInfo} end. run_built_in(erlang, demonitor, 1, [Ref], Info) -> run_built_in(erlang, demonitor, 2, [Ref, []], Info); run_built_in(erlang, demonitor, 2, [Ref, Options], Info) -> ?badarg_if_not(is_reference(Ref)), SaneOptions = try [] =:= [O || O <- Options, O =/= flush, O =/= info] catch _:_ -> false end, ?badarg_if_not(SaneOptions), HasFlush = lists:member(flush, Options), HasInfo = lists:member(info, Options), #concuerror_info{ demonitors = Demonitors, event = Event, monitors = Monitors } = Info, case ets:match(Monitors, ?monitor_match_to_target_source_as(Ref)) of [] -> %% Invalid, expired or foreign monitor {not HasInfo, Info}; [[Target, As, Status]] -> PatternFun = fun(M) -> case M of {'DOWN', Ref, process, _, _} -> true; _ -> false end end, {Flushed, NewInfo} = case HasFlush of true -> {Match, FlushInfo} = has_matching_or_after(PatternFun, infinity, Info), {Match =/= false, FlushInfo}; false -> {false, Info} end, Demonitored = case Status of active -> Active = ?monitor(Ref, Target, As, active), Inactive = ?monitor(Ref, Target, As, inactive), true = ets:delete_object(Monitors, Active), true = ets:insert(Monitors, Inactive), true; inactive -> false end, {Cnt, ReceiveInfo} = get_receive_cnt(NewInfo), NewEvent = Event#event{special = [{demonitor, {Ref, {Cnt, PatternFun}}}]}, FinalInfo = ReceiveInfo#concuerror_info{ demonitors = [Ref|Demonitors], event = NewEvent }, case {HasInfo, HasFlush} of {false, _} -> {true, FinalInfo}; {true, false} -> {Demonitored, FinalInfo}; {true, true} -> {Flushed, FinalInfo} end end; run_built_in(erlang, exit, 2, [Pid, Reason], Info) -> #concuerror_info{ event = #event{event_info = EventInfo} = Event, flags = #process_flags{trap_exit = Trapping} } = Info, ?badarg_if_not(is_pid(Pid)), case EventInfo of %% Replaying... #builtin_event{result = OldResult} -> {_, MsgInfo} = get_message_cnt(Info), {OldResult, MsgInfo}; %% New event... undefined -> Content = case Event#event.location =/= exit andalso Reason =:= kill of true -> kill; false -> case Pid =/= self() orelse Reason =/= normal orelse Trapping of true -> ok; false -> Message = msg(exit_normal_self_abnormal), Logger = Info#concuerror_info.logger, ?unique(Logger, ?lwarning, Message, [Pid]) end, make_exit_signal(Reason) end, MsgInfo = make_message(Info, exit_signal, Content, Pid), {true, MsgInfo} end; run_built_in(erlang, group_leader, 0, [], Info) -> Leader = get_leader(Info, self()), {Leader, Info}; run_built_in(M, group_leader, 2, [GroupLeader, Pid], #concuerror_info{processes = Processes} = Info) when M =:= erlang; M =:= erts_internal -> try {true, Info} = run_built_in(erlang, is_process_alive, 1, [Pid], Info), {true, Info} = run_built_in(erlang, is_process_alive, 1, [GroupLeader], Info), ok catch _:_ -> error(badarg) end, true = ets:update_element(Processes, Pid, {?process_leader, GroupLeader}), {true, Info}; run_built_in(erlang, halt, _, _, Info) -> #concuerror_info{ event = Event, logger = Logger } = Info, Message = msg(limited_halt), Logger = Info#concuerror_info.logger, ?unique(Logger, ?lwarning, Message, []), NewEvent = Event#event{special = [halt]}, {no_return, Info#concuerror_info{event = NewEvent}}; run_built_in(erlang, is_process_alive, 1, [Pid], Info) -> ?badarg_if_not(is_pid(Pid)), #concuerror_info{processes = Processes} = Info, Return = case ets:lookup(Processes, Pid) of [] -> ?crash_instr({checking_system_process, Pid}); [?process_pat_pid_status(Pid, Status)] -> is_active(Status) end, {Return, Info}; run_built_in(erlang, link, 1, [Pid], Info) -> #concuerror_info{ flags = #process_flags{trap_exit = TrapExit}, links = Links, event = #event{event_info = EventInfo} } = Info, case run_built_in(erlang, is_process_alive, 1, [Pid], Info) of {true, Info} -> Self = self(), true = ets:insert(Links, ?links(Self, Pid)), {true, Info}; {false, _} -> case TrapExit of false -> error(noproc); true -> NewInfo = case EventInfo of %% Replaying... #builtin_event{} -> {_, MsgInfo} = get_message_cnt(Info), MsgInfo; %% New event... undefined -> Signal = make_exit_signal(Pid, noproc), make_message(Info, message, Signal, self()) end, {true, NewInfo} end end; run_built_in(erlang, make_ref, 0, [], Info) -> #concuerror_info{event = #event{event_info = EventInfo}} = Info, {Ref, NewInfo} = get_ref(Info), case EventInfo of %% Replaying... #builtin_event{result = Ref} -> ok; %% New event... undefined -> ok end, {Ref, NewInfo}; run_built_in(erlang, monitor, 2, [Type, InTarget], Info) -> #concuerror_info{ monitors = Monitors, event = #event{event_info = EventInfo} } = Info, ?badarg_if_not(Type =:= process), {Target, As} = case InTarget of P when is_pid(P) -> {InTarget, InTarget}; A when is_atom(A) -> {InTarget, {InTarget, node()}}; {Name, Node} = Local when is_atom(Name), Node =:= node() -> {Name, Local}; {Name, Node} when is_atom(Name) -> ?crash_instr({not_local_node, Node}); _ -> error(badarg) end, {Ref, NewInfo} = get_ref(Info), case EventInfo of %% Replaying... #builtin_event{result = Ref} -> ok; %% New event... undefined -> ok end, {IsActive, Pid} = case is_pid(Target) of true -> {IA, _} = run_built_in(erlang, is_process_alive, 1, [Target], Info), {IA, Target}; false -> {P1, _} = run_built_in(erlang, whereis, 1, [Target], Info), case P1 =:= undefined of true -> {false, foo}; false -> {IA, _} = run_built_in(erlang, is_process_alive, 1, [P1], Info), {IA, P1} end end, case IsActive of true -> true = ets:insert(Monitors, ?monitor(Ref, Pid, As, active)); false -> ok end, FinalInfo = case IsActive of true -> NewInfo; false -> case EventInfo of %% Replaying... #builtin_event{} -> {_, MsgInfo} = get_message_cnt(NewInfo), MsgInfo; %% New event... undefined -> Data = {'DOWN', Ref, process, As, noproc}, make_message(NewInfo, message, Data, self()) end end, {Ref, FinalInfo}; run_built_in(erlang, process_info, 2, [Pid, Items], Info) when is_list(Items) -> {Alive, _} = run_built_in(erlang, is_process_alive, 1, [Pid], Info), case Alive of false -> {undefined, Info}; true -> ItemFun = fun (Item) -> ?badarg_if_not(is_atom(Item)), {ItemRes, _} = run_built_in(erlang, process_info, 2, [Pid, Item], Info), case (Item =:= registered_name) andalso (ItemRes =:= []) of true -> {registered_name, []}; false -> ItemRes end end, {lists:map(ItemFun, Items), Info} end; run_built_in(erlang, process_info, 2, [Pid, Item], Info) when is_atom(Item) -> {Alive, _} = run_built_in(erlang, is_process_alive, 1, [Pid], Info), case Alive of false -> {undefined, Info}; true -> {TheirInfo, TheirDict} = case Pid =:= self() of true -> {Info, get()}; false -> get_their_info(Pid) end, Res = case Item of current_function -> case Pid =:= self() of true -> {_, Stacktrace} = erlang:process_info(Pid, current_stacktrace), case clean_stacktrace(Stacktrace) of %% Reachable by %% basic_tests/process_info/test_current_function_top [] -> TheirInfo#concuerror_info.initial_call; [{M, F, A, _}|_] -> {M, F, A} end; false -> #concuerror_info{logger = Logger} = TheirInfo, Msg = "Concuerror does not properly support" " erlang:process_info(Other, current_function)," " returning the initial call instead.~n", ?unique(Logger, ?lwarning, Msg, []), TheirInfo#concuerror_info.initial_call end; current_stacktrace -> case Pid =:= self() of true -> {_, Stacktrace} = erlang:process_info(Pid, current_stacktrace), clean_stacktrace(Stacktrace); false -> #concuerror_info{logger = Logger} = TheirInfo, Msg = "Concuerror does not properly support" " erlang:process_info(Other, current_stacktrace)," " returning an empty list instead.~n", ?unique(Logger, ?lwarning, Msg, []), [] end; dictionary -> TheirDict; group_leader -> get_leader(Info, Pid); initial_call -> TheirInfo#concuerror_info.initial_call; links -> #concuerror_info{links = Links} = TheirInfo, try ets:lookup_element(Links, Pid, 2) catch error:badarg -> [] end; messages -> #concuerror_info{logger = Logger} = TheirInfo, Msg = "Concuerror does not properly support" " erlang:process_info(_, messages)," " returning an empty list instead.~n", ?unique(Logger, ?lwarning, Msg, []), []; message_queue_len -> #concuerror_info{message_queue = Queue} = TheirInfo, queue:len(Queue); registered_name -> #concuerror_info{processes = Processes} = TheirInfo, [?process_pat_pid_name(Pid, Name)] = ets:lookup(Processes, Pid), case Name =:= ?process_name_none of true -> []; false -> Name end; status -> #concuerror_info{logger = Logger} = TheirInfo, Msg = "Concuerror does not properly support erlang:process_info(_," " status), returning always 'running' instead.~n", ?unique(Logger, ?lwarning, Msg, []), running; trap_exit -> TheirInfo#concuerror_info.flags#process_flags.trap_exit; ReturnsANumber when ReturnsANumber =:= heap_size; ReturnsANumber =:= reductions; ReturnsANumber =:= stack_size; false -> #concuerror_info{logger = Logger} = TheirInfo, Msg = "Concuerror does not properly support erlang:process_info(_," " ~w), returning 42 instead.~n", ?unique(Logger, ?lwarning, ReturnsANumber, Msg, [ReturnsANumber]), 42; _ -> throw({unsupported_process_info, Item}) end, TagRes = case Item =:= registered_name andalso Res =:= [] of true -> Res; false -> {Item, Res} end, {TagRes, Info} end; run_built_in(erlang, register, 2, [Name, Pid], Info) -> #concuerror_info{ logger = Logger, processes = Processes } = Info, case Name of eunit_server -> ?unique(Logger, ?lwarning, msg(register_eunit_server), []); _ -> ok end, try true = is_atom(Name), {true, Info} = run_built_in(erlang, is_process_alive, 1, [Pid], Info), [] = ets:match(Processes, ?process_match_name_to_pid(Name)), ?process_name_none = ets:lookup_element(Processes, Pid, ?process_name), false = undefined =:= Name, true = ets:update_element(Processes, Pid, [{?process_name, Name}, {?process_last_name, Name}]), {true, Info} catch _:_ -> error(badarg) end; run_built_in(erlang, ReadorCancelTimer, 1, [Ref], Info) when ReadorCancelTimer =:= read_timer; ReadorCancelTimer =:= cancel_timer -> ?badarg_if_not(is_reference(Ref)), #concuerror_info{timers = Timers} = Info, case ets:lookup(Timers, Ref) of [] -> {false, Info}; [{Ref, Pid, _Dest}] -> case ReadorCancelTimer of read_timer -> ok; cancel_timer -> ?debug_flag(?loop, sending_kill_to_cancel), ets:delete(Timers, Ref), Pid ! {exit_signal, #message{data = kill, id = hidden}, self()}, {false, true} = receive_message_ack(), ok end, {1, Info} end; run_built_in(erlang, SendAfter, 3, [0, Dest, Msg], Info) when SendAfter =:= send_after; SendAfter =:= start_timer -> #concuerror_info{ event = #event{event_info = EventInfo}} = Info, {Ref, NewInfo} = get_ref(Info), case EventInfo of %% Replaying... #builtin_event{result = Ref} -> ok; %% New event... undefined -> ok end, ActualMessage = format_timer_message(SendAfter, Msg, Ref), {_, FinalInfo} = run_built_in(erlang, send, 2, [Dest, ActualMessage], NewInfo), {Ref, FinalInfo}; run_built_in(erlang, SendAfter, 3, [Timeout, Dest, Msg], Info) when SendAfter =:= send_after; SendAfter =:= start_timer -> ?badarg_if_not( (is_pid(Dest) orelse is_atom(Dest)) andalso is_integer(Timeout) andalso Timeout >= 0), #concuerror_info{ event = Event, processes = Processes, timeout = Wait, timers = Timers } = Info, #event{event_info = EventInfo} = Event, {Ref, NewInfo} = get_ref(Info), {Pid, FinalInfo} = case EventInfo of %% Replaying... #builtin_event{result = Ref, extra = OldPid} -> {OldPid, NewInfo#concuerror_info{extra = OldPid}}; %% New event... undefined -> Symbol = "Timer " ++ erlang:ref_to_list(Ref), P = case ets:match(Processes, ?process_match_symbol_to_pid(Symbol)) of [] -> PassedInfo = reset_concuerror_info(NewInfo), TimerInfo = PassedInfo#concuerror_info{ instant_delivery = true, is_timer = Ref }, NewP = new_process(TimerInfo), true = ets:insert(Processes, ?new_process(NewP, Symbol)), NewP; [[OldP]] -> OldP end, NewEvent = Event#event{special = [{new, P}]}, {P, NewInfo#concuerror_info{event = NewEvent, extra = P}} end, ActualMessage = format_timer_message(SendAfter, Msg, Ref), ets:insert(Timers, {Ref, Pid, Dest}), TimerFun = fun() -> MFArgs = [erlang, send, [Dest, ActualMessage]], catch concuerror_inspect:inspect(call, MFArgs, ignored) end, Pid ! {start, erlang, apply, [TimerFun, []]}, ok = wait_process(Pid, Wait), {Ref, FinalInfo}; run_built_in(erlang, SendAfter, 4, [Timeout, Dest, Msg, []], Info) when SendAfter =:= send_after; SendAfter =:= start_timer -> run_built_in(erlang, SendAfter, 3, [Timeout, Dest, Msg], Info); run_built_in(erlang, spawn, 3, [M, F, Args], Info) -> run_built_in(erlang, spawn_opt, 1, [{M, F, Args, []}], Info); run_built_in(erlang, spawn_link, 3, [M, F, Args], Info) -> run_built_in(erlang, spawn_opt, 1, [{M, F, Args, [link]}], Info); run_built_in(erlang, spawn_opt, 4, [Module, Name, Args, SpawnOpts], Info) -> run_built_in(erlang, spawn_opt, 1, [{Module, Name, Args, SpawnOpts}], Info); run_built_in(erlang, spawn_opt, 1, [{Module, Name, Args, SpawnOpts}], Info) -> #concuerror_info{ event = Event, processes = Processes, timeout = Timeout} = Info, #event{event_info = EventInfo} = Event, Parent = self(), ParentSymbol = ets:lookup_element(Processes, Parent, ?process_symbolic), ChildId = ets:update_counter(Processes, Parent, {?process_children, 1}), {HasMonitor, NewInfo} = case lists:member(monitor, SpawnOpts) of false -> {false, Info}; true -> get_ref(Info) end, {Result, FinalInfo} = case EventInfo of %% Replaying... #builtin_event{result = OldResult} -> case HasMonitor of false -> ok; Mon -> {_, Mon} = OldResult, ok end, {OldResult, NewInfo}; %% New event... undefined -> PassedInfo = reset_concuerror_info(NewInfo), ?debug_flag(?spawn, {Parent, spawning_new, PassedInfo}), ChildSymbol = io_lib:format("~s.~w", [ParentSymbol, ChildId]), P = case ets:match(Processes, ?process_match_symbol_to_pid(ChildSymbol)) of [] -> NewP = new_process(PassedInfo), true = ets:insert(Processes, ?new_process(NewP, ChildSymbol)), NewP; [[OldP]] -> OldP end, NewResult = case HasMonitor of false -> P; Mon -> {P, Mon} end, NewEvent = Event#event{special = [{new, P}]}, {NewResult, NewInfo#concuerror_info{event = NewEvent}} end, Pid = case HasMonitor of false -> Result; Ref -> {P1, Ref} = Result, #concuerror_info{monitors = Monitors} = FinalInfo, true = ets:insert(Monitors, ?monitor(Ref, P1, P1, active)), P1 end, case lists:member(link, SpawnOpts) of true -> #concuerror_info{links = Links} = FinalInfo, true = ets:insert(Links, ?links(Parent, Pid)); false -> ok end, {GroupLeader, _} = run_built_in(erlang, group_leader, 0, [], FinalInfo), true = ets:update_element(Processes, Pid, {?process_leader, GroupLeader}), Pid ! {start, Module, Name, Args}, ok = wait_process(Pid, Timeout), {Result, FinalInfo}; run_built_in(erlang, send, 3, [Recipient, Message, _Options], Info) -> {_, FinalInfo} = run_built_in(erlang, send, 2, [Recipient, Message], Info), {ok, FinalInfo}; run_built_in(erlang, Send, 2, [Recipient, Message], Info) when Send =:= '!'; Send =:= 'send' -> #concuerror_info{event = #event{event_info = EventInfo}} = Info, Pid = case is_pid(Recipient) of true -> Recipient; false -> T = case Recipient of A when is_atom(A) -> Recipient; {A, N} when is_atom(A), N =:= node() -> A end, {P, Info} = run_built_in(erlang, whereis, 1, [T], Info), P end, ?badarg_if_not(is_pid(Pid)), Extra = case Info#concuerror_info.is_timer of false -> undefined; Timer -> ets:delete(Info#concuerror_info.timers, Timer), Timer end, case EventInfo of %% Replaying... #builtin_event{result = OldResult} -> {_, MsgInfo} = get_message_cnt(Info), {OldResult, MsgInfo#concuerror_info{extra = Extra}}; %% New event... undefined -> ?debug_flag(?send, {send, Recipient, Message}), MsgInfo = make_message(Info, message, Message, Pid), ?debug_flag(?send, {send, successful}), {Message, MsgInfo#concuerror_info{extra = Extra}} end; run_built_in(erlang, process_flag, 2, [Flag, Value], #concuerror_info{flags = Flags} = Info) -> case Flag of trap_exit -> ?badarg_if_not(is_boolean(Value)), {Flags#process_flags.trap_exit, Info#concuerror_info{flags = Flags#process_flags{trap_exit = Value}}}; priority -> ?badarg_if_not(lists:member(Value, [low, normal, high, max])), {Flags#process_flags.priority, Info#concuerror_info{flags = Flags#process_flags{priority = Value}}}; _ -> throw({unsupported_process_flag, {Flag, Value}}) end; run_built_in(erlang, processes, 0, [], Info) -> #concuerror_info{processes = Processes} = Info, Active = lists:sort(ets:select(Processes, [?process_match_active()])), {Active, Info}; run_built_in(erlang, unlink, 1, [Pid], Info) -> #concuerror_info{links = Links} = Info, Self = self(), [true, true] = [ets:delete_object(Links, L) || L <- ?links(Self, Pid)], {true, Info}; run_built_in(erlang, unregister, 1, [Name], #concuerror_info{processes = Processes} = Info) -> try [[Pid]] = ets:match(Processes, ?process_match_name_to_pid(Name)), true = ets:update_element(Processes, Pid, {?process_name, ?process_name_none}), NewInfo = Info#concuerror_info{extra = Pid}, {true, NewInfo} catch _:_ -> error(badarg) end; run_built_in(erlang, whereis, 1, [Name], Info) -> #concuerror_info{processes = Processes} = Info, case ets:match(Processes, ?process_match_name_to_pid(Name)) of [] -> case whereis(Name) =:= undefined of true -> {undefined, Info}; false -> ?crash_instr({registered_process_not_wrapped, Name}) end; [[Pid]] -> {Pid, Info} end; run_built_in(ets, new, 2, [NameArg, Options], Info) -> #concuerror_info{ ets_tables = EtsTables, event = #event{event_info = EventInfo}, scheduler = Scheduler } = Info, NoNameOptions = [O || O <- Options, O =/= named_table], Name = case Options =/= NoNameOptions of true -> MatchExistingName = ets:match(EtsTables, ?ets_match_name_to_tid(NameArg)), ?badarg_if_not(MatchExistingName =:= []), NameArg; false -> ?ets_name_none end, Tid = case EventInfo of %% Replaying... #builtin_event{extra = {T, Name}} -> T; %% New event... undefined -> %% The last protection option is the one actually used. %% Use that to make the actual table public. T = ets:new(NameArg, NoNameOptions ++ [public]), true = ets:give_away(T, Scheduler, given_to_scheduler), T end, ProtectFold = fun(Option, Selected) -> case Option of O when O =:= 'private'; O =:= 'protected'; O =:= 'public' -> O; _ -> Selected end end, Protection = lists:foldl(ProtectFold, protected, NoNameOptions), Ret = case Name =/= ?ets_name_none of true -> Name; false -> Tid end, Heir = case proplists:lookup(heir, Options) of none -> {heir, none}; Other -> Other end, Entry = ?ets_table_entry(Tid, Name, self(), Protection, Heir, false), true = ets:insert(EtsTables, Entry), ets:delete_all_objects(Tid), {Ret, Info#concuerror_info{extra = {Tid, Name}}}; run_built_in(ets, rename, 2, [NameOrTid, NewName], Info) -> #concuerror_info{ets_tables = EtsTables} = Info, ?badarg_if_not(is_atom(NewName)), {Tid, _, _} = ets_access_table_info(NameOrTid, {rename, 2}, Info), MatchExistingName = ets:match(EtsTables, ?ets_match_name_to_tid(NewName)), ?badarg_if_not(MatchExistingName =:= []), ets:update_element(EtsTables, Tid, [{?ets_name, NewName}]), {NewName, Info#concuerror_info{extra = {Tid, NewName}}}; run_built_in(ets, info, 2, [NameOrTid, Field], Info) -> #concuerror_info{ets_tables = EtsTables} = Info, ?badarg_if_not(is_atom(Field)), try {Tid, Id, _} = ets_access_table_info(NameOrTid, {info, 2}, Info), [TableInfo] = ets:lookup(EtsTables, Tid), Ret = case Field of heir -> case element(?ets_heir, TableInfo) of {heir, none} -> none; {heir, Q, _} -> Q end; protection -> element(?ets_protection, TableInfo); owner -> element(?ets_owner, TableInfo); named_table -> element(?ets_name, TableInfo) =/= ?ets_name_none; _ -> ets:info(Tid, Field) end, {Ret, Info#concuerror_info{extra = Id}} catch error:badarg -> case is_valid_ets_id(NameOrTid) of true -> {undefined, Info}; false -> error(badarg) end end; run_built_in(ets, info, 1, [NameOrTid], Info) -> try {_, Id, _} = ets_access_table_info(NameOrTid, {info, 1}, Info), Fun = fun(Field) -> {FieldRes, _} = run_built_in(ets, info, 2, [NameOrTid, Field], Info), {Field, FieldRes} end, Ret = [Fun(F) || F <- [ owner , heir , name , named_table , type , keypos , protection ]], {Ret, Info#concuerror_info{extra = Id}} catch error:badarg -> case is_valid_ets_id(NameOrTid) of true -> {undefined, Info}; false -> error(badarg) end end; run_built_in(ets, whereis, _, [Name], Info) -> ?badarg_if_not(is_atom(Name)), try {Tid, Id, _} = ets_access_table_info(Name, {whereis, 1}, Info), {Tid, Info#concuerror_info{extra = Id}} catch error:badarg -> {undefined, Info} end; run_built_in(ets, delete, 1, [NameOrTid], Info) -> #concuerror_info{ets_tables = EtsTables} = Info, {Tid, Id, _} = ets_access_table_info(NameOrTid, {delete, 1}, Info), ets:update_element(EtsTables, Tid, [{?ets_alive, false}]), ets:delete_all_objects(Tid), {true, Info#concuerror_info{extra = Id}}; run_built_in(ets, give_away, 3, [NameOrTid, Pid, GiftData], Info) -> #concuerror_info{ ets_tables = EtsTables, event = #event{event_info = EventInfo} } = Info, {Tid, Id, _} = ets_access_table_info(NameOrTid, {give_away, 3}, Info), {Alive, Info} = run_built_in(erlang, is_process_alive, 1, [Pid], Info), Self = self(), NameForMsg = ets_get_name_or_tid(Id), ?badarg_if_not(is_pid(Pid) andalso Pid =/= Self andalso Alive), NewInfo = case EventInfo of %% Replaying. Keep original message #builtin_event{} -> {_Id, MsgInfo} = get_message_cnt(Info), MsgInfo; %% New event... undefined -> Data = {'ETS-TRANSFER', NameForMsg, Self, GiftData}, make_message(Info, message, Data, Pid) end, Update = [{?ets_owner, Pid}], true = ets:update_element(EtsTables, Tid, Update), {true, NewInfo#concuerror_info{extra = Id}}; run_built_in(ets, F, N, [NameOrTid|Args], Info) -> try _ = ets_ops_access_rights_map({F, N}) catch error:function_clause -> #concuerror_info{event = #event{location = Location}} = Info, ?crash_instr({unknown_built_in, {ets, F, N, Location}}) end, {Tid, Id, IsSystemInsert} = ets_access_table_info(NameOrTid, {F, N}, Info), case IsSystemInsert of true -> #concuerror_info{system_ets_entries = SystemEtsEntries} = Info, ets:insert(SystemEtsEntries, {Tid, Args}); false -> true end, {erlang:apply(ets, F, [Tid|Args]), Info#concuerror_info{extra = Id}}; run_built_in(erlang = Module, Name, Arity, Args, Info) when false ;{Name, Arity} =:= {date, 0} ;{Name, Arity} =:= {module_loaded, 1} ;{Name, Arity} =:= {monotonic_time, 0} ;{Name, Arity} =:= {monotonic_time, 1} ;{Name, Arity} =:= {now, 0} ;{Name, Arity} =:= {system_time, 0} ;{Name, Arity} =:= {system_time, 1} ;{Name, Arity} =:= {time, 0} ;{Name, Arity} =:= {time_offset, 0} ;{Name, Arity} =:= {time_offset, 1} ;{Name, Arity} =:= {timestamp, 0} ;{Name, Arity} =:= {unique_integer, 0} ;{Name, Arity} =:= {unique_integer, 1} -> maybe_reuse_old(Module, Name, Arity, Args, Info); run_built_in(os = Module, Name, Arity, Args, Info) when false ;{Name, Arity} =:= {system_time, 0} ;{Name, Arity} =:= {system_time, 1} ;{Name, Arity} =:= {timestamp, 0} -> maybe_reuse_old(Module, Name, Arity, Args, Info); run_built_in(persistent_term, Name, Arity, Args, Info) -> case {Name, Arity} of {erase, 1} -> run_built_in(ets, delete, 2, [?persistent_term|Args], Info); {get, 1} -> [Key] = Args, run_built_in(ets, lookup_element, 3, [?persistent_term, Key, 2], Info); {get, 2} -> [Key, Default] = Args, {R, NewInfo} = run_built_in(ets, lookup, 2, [?persistent_term, Key], Info), case R of [] -> {Default, NewInfo}; [{Key, V}] -> {V, NewInfo} end; {put, 2} -> [Key, Value] = Args, run_built_in(ets, insert, 2, [?persistent_term, {Key, Value}], Info); _Other -> #concuerror_info{event = #event{location = Location}} = Info, ?crash_instr({unknown_built_in, {persistent_term, Name, Arity, Location}}) end; run_built_in(Module, Name, Arity, _Args, #concuerror_info{event = #event{location = Location}}) -> ?crash_instr({unknown_built_in, {Module, Name, Arity, Location}}). maybe_reuse_old(Module, Name, _Arity, Args, Info) -> #concuerror_info{event = #event{event_info = EventInfo}} = Info, Res = case EventInfo of %% Replaying... #builtin_event{result = OldResult} -> OldResult; %% New event... undefined -> erlang:apply(Module, Name, Args) end, {Res, Info}. %%------------------------------------------------------------------------------ maybe_deliver_message(#event{special = Special} = Event, Info) -> case proplists:lookup(message, Special) of none -> Event; {message, MessageEvent} -> #concuerror_info{instant_delivery = InstantDelivery} = Info, #message_event{recipient = Recipient, instant = Instant} = MessageEvent, case (InstantDelivery orelse Recipient =:= self()) andalso Instant of false -> Event; true -> #concuerror_info{timeout = Timeout} = Info, TrapExit = Info#concuerror_info.flags#process_flags.trap_exit, deliver_message(Event, MessageEvent, Timeout, {true, TrapExit}) end end. -spec deliver_message(event(), message_event(), timeout()) -> event(). deliver_message(Event, MessageEvent, Timeout) -> assert_no_messages(), deliver_message(Event, MessageEvent, Timeout, false). deliver_message(Event, MessageEvent, Timeout, Instant) -> #event{special = Special} = Event, #message_event{ message = Message, recipient = Recipient, type = Type} = MessageEvent, ?debug_flag(?loop, {deliver_message, Message, Instant}), Self = self(), Notify = case Recipient =:= Self of true -> %% Instant delivery to self {true, SelfTrapping} = Instant, SelfKilling = Type =:= exit_signal, send_message_ack(Self, SelfTrapping, SelfKilling), ?notify_none; false -> Self end, Recipient ! {Type, Message, Notify}, receive {message_ack, Trapping, Killing} -> NewMessageEvent = MessageEvent#message_event{ killing = Killing, trapping = Trapping }, NewSpecial = case already_known_delivery(Message, Special) of true -> Special; false -> Special ++ [{message_delivered, NewMessageEvent}] end, Event#event{special = NewSpecial}; {system_reply, From, Id, Reply, System} -> ?debug_flag(?loop, got_system_message), case proplists:lookup(message_received, Special) =:= none of true -> SystemReply = #message_event{ cause_label = Event#event.label, message = #message{data = Reply, id = {System, Id}}, sender = Recipient, recipient = From}, SystemSpecials = [{message_delivered, MessageEvent}, {message_received, Id}, {system_communication, System}, {message, SystemReply}], NewEvent = Event#event{special = Special ++ SystemSpecials}, deliver_if_instant(Instant, NewEvent, SystemReply, Timeout); false -> SystemReply = find_system_reply(Recipient, Special), deliver_if_instant(Instant, Event, SystemReply, Timeout) end; {'EXIT', _, What} -> exit(What) after Timeout -> ?crash({no_response_for_message, Timeout, Recipient}) end. already_known_delivery(_, []) -> false; already_known_delivery(Message, [{message_delivered, Event}|Special]) -> #message{id = Id} = Message, #message_event{message = #message{id = Del}} = Event, Id =:= Del orelse already_known_delivery(Message, Special); already_known_delivery(Message, [_|Special]) -> already_known_delivery(Message, Special). deliver_if_instant(Instant, NewEvent, SystemReply, Timeout) -> case Instant =:= false of true -> NewEvent; false -> deliver_message(NewEvent, SystemReply, Timeout, Instant) end. find_system_reply(System, [{message, #message_event{sender = System} = M}|_]) -> M; find_system_reply(System, [_|Special]) -> find_system_reply(System, Special). %%------------------------------------------------------------------------------ -spec wait_actor_reply(event(), timeout()) -> 'retry' | {'ok', event()}. wait_actor_reply(Event, Timeout) -> Pid = Event#event.actor, assert_no_messages(), Pid ! Event, wait_process(Pid, Timeout). %% Wait for a process to instrument any code. wait_process(Pid, Timeout) -> receive ready -> ok; exited -> retry; {blocked, _} -> retry; #event{} = NewEvent -> {ok, NewEvent}; {'ETS-TRANSFER', _, _, given_to_scheduler} -> wait_process(Pid, Timeout); {'EXIT', _, What} -> exit(What) after Timeout -> case concuerror_loader:is_instrumenting() of {true, _Module} -> wait_process(Pid, Timeout); _ -> ?crash({process_did_not_respond, Timeout, Pid}) end end. assert_no_messages() -> receive Msg -> error({pending_message, Msg}) after 0 -> ok end. %%------------------------------------------------------------------------------ -spec reset_processes(processes()) -> ok. reset_processes(Processes) -> Procs = ets:tab2list(Processes), Fold = fun(?process_pat_pid_kind(P, Kind), _) -> case Kind =:= regular of true -> P ! reset, receive reset_done -> ok end; false -> ok end, ok end, ok = lists:foldl(Fold, ok, Procs). %%------------------------------------------------------------------------------ -spec collect_deadlock_info([pid()]) -> [{pid(), location(), [term()]}]. collect_deadlock_info(Actors) -> Fold = fun(P, Acc) -> P ! deadlock_poll, receive {blocked, Info} -> [Info|Acc]; exited -> Acc end end, lists:foldr(Fold, [], Actors). -spec enabled(pid()) -> boolean(). enabled(P) -> P ! enabled, receive {enabled, Answer} -> Answer end. %%------------------------------------------------------------------------------ handle_receive(PatternFun, Timeout, Location, Info) -> %% No distinction between replaying/new as we have to clear the message from %% the queue anyway... {MessageOrAfter, NewInfo} = has_matching_or_after(PatternFun, Timeout, Location, Info), notify_receive(MessageOrAfter, PatternFun, Timeout, Location, NewInfo). has_matching_or_after(PatternFun, Timeout, Location, InfoIn) -> {Result, Info} = has_matching_or_after(PatternFun, Timeout, InfoIn), case Result =:= false of true -> ?debug_flag(?loop, blocked), NewInfo = case Info#concuerror_info.status =:= waiting of true -> Messages = Info#concuerror_info.message_queue, MessageList = [D || #message{data = D} <- queue:to_list(Messages)], Notification = {blocked, {self(), Location, MessageList}}, process_loop(notify(Notification, Info)); false -> process_loop(set_status(Info, waiting)) end, has_matching_or_after(PatternFun, Timeout, Location, NewInfo); false -> ?debug_flag(?loop, ready_to_receive), NewInfo = process_loop(InfoIn), {FinalResult, FinalInfo} = has_matching_or_after(PatternFun, Timeout, NewInfo), {FinalResult, FinalInfo} end. has_matching_or_after(PatternFun, Timeout, Info) -> #concuerror_info{message_queue = Messages} = Info, {MatchingOrFalse, NewMessages} = find_matching_message(PatternFun, Messages), Result = case MatchingOrFalse =:= false of false -> MatchingOrFalse; true -> case Timeout =:= infinity of false -> 'after'; true -> false end end, {Result, Info#concuerror_info{message_queue = NewMessages}}. find_matching_message(PatternFun, Messages) -> find_matching_message(PatternFun, Messages, queue:new()). find_matching_message(PatternFun, NewMessages, OldMessages) -> {Value, NewNewMessages} = queue:out(NewMessages), ?debug_flag(?receive_, {inspect, Value}), case Value of {value, #message{data = Data} = Message} -> case PatternFun(Data) of true -> ?debug_flag(?receive_, matches), {Message, queue:join(OldMessages, NewNewMessages)}; false -> ?debug_flag(?receive_, doesnt_match), NewOldMessages = queue:in(Message, OldMessages), find_matching_message(PatternFun, NewNewMessages, NewOldMessages) end; empty -> {false, OldMessages} end. notify_receive(MessageOrAfter, PatternFun, Timeout, Location, Info) -> {Cnt, ReceiveInfo} = get_receive_cnt(Info), #concuerror_info{ event = NextEvent, flags = #process_flags{trap_exit = Trapping} } = UpdatedInfo = add_location_info(Location, ReceiveInfo), ReceiveEvent = #receive_event{ message = MessageOrAfter, receive_info = {Cnt, PatternFun}, timeout = Timeout, trapping = Trapping}, {Special, CreateMessage} = case MessageOrAfter of #message{data = Data, id = Id} -> {[{message_received, Id}], {ok, Data}}; 'after' -> {[], false} end, Notification = NextEvent#event{event_info = ReceiveEvent, special = Special}, AddMessage = case CreateMessage of {ok, D} -> ?debug_flag(?receive_, {deliver, D}), {true, D}; false -> false end, {{skip_timeout, AddMessage}, delay_notify(Notification, UpdatedInfo)}. %%------------------------------------------------------------------------------ notify(Notification, #concuerror_info{scheduler = Scheduler} = Info) -> ?debug_flag(?notify, {notify, Notification}), Scheduler ! Notification, Info. delay_notify(Notification, Info) -> Info#concuerror_info{delayed_notification = {true, Notification}}. -spec process_top_loop(concuerror_info()) -> no_return(). process_top_loop(Info) -> ?debug_flag(?loop, top_waiting), receive reset -> process_top_loop(notify(reset_done, Info)); reset_system -> reset_system(Info), process_top_loop(notify(reset_system_done, Info)); {start, Module, Name, Args} -> ?debug_flag(?loop, {start, Module, Name, Args}), wrapper(Info, Module, Name, Args) end. -spec wrapper(concuerror_info(), module(), atom(), [term()]) -> no_return(). -ifdef(BEFORE_OTP_21). wrapper(InfoIn, Module, Name, Args) -> Info = InfoIn#concuerror_info{initial_call = {Module, Name, length(Args)}}, concuerror_inspect:start_inspection(set_status(Info, running)), try concuerror_inspect:inspect(call, [Module, Name, Args], []), exit(normal) catch Class:Reason -> Stacktrace = erlang:get_stacktrace(), case concuerror_inspect:stop_inspection() of {true, EndInfo} -> CleanStacktrace = clean_stacktrace(Stacktrace), ?debug_flag(?exit, {exit, Class, Reason, Stacktrace}), NewReason = case Class of throw -> {{nocatch, Reason}, CleanStacktrace}; error -> {Reason, CleanStacktrace}; exit -> Reason end, exiting(NewReason, CleanStacktrace, EndInfo); false -> erlang:raise(Class, Reason, Stacktrace) end end. -else. wrapper(InfoIn, Module, Name, Args) -> Info = InfoIn#concuerror_info{initial_call = {Module, Name, length(Args)}}, concuerror_inspect:start_inspection(set_status(Info, running)), try concuerror_inspect:inspect(call, [Module, Name, Args], []), exit(normal) catch Class:Reason:Stacktrace -> case concuerror_inspect:stop_inspection() of {true, EndInfo} -> CleanStacktrace = clean_stacktrace(Stacktrace), ?debug_flag(?exit, {exit, Class, Reason, Stacktrace}), NewReason = case Class of throw -> {{nocatch, Reason}, CleanStacktrace}; error -> {Reason, CleanStacktrace}; exit -> Reason end, exiting(NewReason, CleanStacktrace, EndInfo); false -> erlang:raise(Class, Reason, Stacktrace) end end. -endif. request_system_reset(Pid) -> Mon = monitor(process, Pid), Pid ! reset_system, receive reset_system_done -> demonitor(Mon, [flush]), ok; {'DOWN', Mon, process, Pid, Reason} -> exit(Reason) after 5000 -> exit(timeout) end. reset_system(Info) -> #concuerror_info{ links = Links, monitors = Monitors, system_ets_entries = SystemEtsEntries } = Info, Entries = ets:tab2list(SystemEtsEntries), lists:foldl(fun delete_system_entries/2, true, Entries), ets:delete_all_objects(SystemEtsEntries), ets:delete_all_objects(Links), ets:delete_all_objects(Monitors). delete_system_entries({T, Objs}, true) when is_list(Objs) -> lists:foldl(fun delete_system_entries/2, true, [{T, O} || O <- Objs]); delete_system_entries({T, O}, true) -> ets:delete_object(T, O). new_process(ParentInfo) -> Info = ParentInfo#concuerror_info{notify_when_ready = {self(), true}}, spawn_link(?MODULE, process_top_loop, [Info]). process_loop(#concuerror_info{delayed_notification = {true, Notification}, scheduler = Scheduler} = Info) -> Scheduler ! Notification, process_loop(Info#concuerror_info{delayed_notification = none}); process_loop(#concuerror_info{notify_when_ready = {Pid, true}} = Info) -> ?debug_flag(?loop, notifying_parent), Pid ! ready, process_loop(Info#concuerror_info{notify_when_ready = {Pid, false}}); process_loop(Info) -> ?debug_flag(?loop, process_loop), receive #event{event_info = EventInfo} = Event -> ?debug_flag(?loop, got_event), Status = Info#concuerror_info.status, case Status =:= exited of true -> ?debug_flag(?loop, exited), process_loop(notify(exited, Info)); false -> NewInfo = Info#concuerror_info{event = Event}, case EventInfo of undefined -> ?debug_flag(?loop, exploring), NewInfo; _OtherReplay -> ?debug_flag(?loop, replaying), NewInfo end end; {exit_signal, #message{data = Data} = Message, Notify} -> Trapping = Info#concuerror_info.flags#process_flags.trap_exit, case {is_active(Info), Data =:= kill} of {true, true} -> ?debug_flag(?loop, kill_signal), send_message_ack(Notify, Trapping, true), exiting(killed, [], Info#concuerror_info{exit_by_signal = true}); {true, false} -> case Trapping of true -> ?debug_flag(?loop, signal_trapped), self() ! {message, Message, Notify}, process_loop(Info); false -> {'EXIT', From, Reason} = Data, send_message_ack(Notify, Trapping, Reason =/= normal), case Reason =:= normal andalso From =/= self() of true -> ?debug_flag(?loop, ignore_normal_signal), process_loop(Info); false -> ?debug_flag(?loop, error_signal), NewInfo = Info#concuerror_info{exit_by_signal = true}, exiting(Reason, [], NewInfo) end end; {false, _} -> ?debug_flag(?loop, ignoring_signal), send_message_ack(Notify, Trapping, false), process_loop(Info) end; {message, Message, Notify} -> ?debug_flag(?loop, message), Trapping = Info#concuerror_info.flags#process_flags.trap_exit, NotDemonitored = not_demonitored(Message, Info), send_message_ack(Notify, Trapping, false), case is_active(Info) andalso NotDemonitored of true -> ?debug_flag(?loop, enqueueing_message), Queue = Info#concuerror_info.message_queue, NewInfo = Info#concuerror_info{ message_queue = queue:in(Message, Queue) }, ?debug_flag(?loop, enqueued_msg), case NewInfo#concuerror_info.status =:= waiting of true -> NewInfo#concuerror_info{status = running}; false -> process_loop(NewInfo) end; false -> ?debug_flag(?loop, ignoring_message), process_loop(Info) end; reset -> ?debug_flag(?loop, reset), ResetInfo = #concuerror_info{ ets_tables = EtsTables, processes = Processes} = reset_concuerror_info(Info), NewInfo = set_status(ResetInfo, exited), _ = erase(), Symbol = ets:lookup_element(Processes, self(), ?process_symbolic), ets:insert(Processes, ?new_process(self(), Symbol)), {DefLeader, _} = run_built_in(erlang, whereis, 1, [user], Info), true = ets:update_element(Processes, self(), {?process_leader, DefLeader}), ets:match_delete(EtsTables, ?ets_pattern_mine()), FinalInfo = NewInfo#concuerror_info{ref_queue = reset_ref_queue(Info)}, _ = notify(reset_done, FinalInfo), erlang:hibernate(concuerror_callback, process_top_loop, [FinalInfo]); deadlock_poll -> ?debug_flag(?loop, deadlock_poll), Status = Info#concuerror_info.status, case Status =:= exited of true -> process_loop(notify(exited, Info)); false -> Info end; enabled -> Status = Info#concuerror_info.status, Reply = Status =:= running orelse Status =:= exiting, process_loop(notify({enabled, Reply}, Info)); {get_info, To} -> To ! {info, {Info, get()}}, process_loop(Info); quit -> exit(normal) end. get_their_info(Pid) -> Pid ! {get_info, self()}, receive {info, Info} -> Info end. send_message_ack(Notify, Trapping, Killing) -> case Notify =/= ?notify_none of true -> Notify ! {message_ack, Trapping, Killing}, ok; false -> ok end. receive_message_ack() -> receive {message_ack, Trapping, Killing} -> {Trapping, Killing} end. get_leader(#concuerror_info{processes = Processes}, P) -> ets:lookup_element(Processes, P, ?process_leader). not_demonitored(Message, Info) -> case Message of #message{data = {'DOWN', Ref, _, _, _}} -> #concuerror_info{demonitors = Demonitors} = Info, not lists:member(Ref, Demonitors); _ -> true end. %%------------------------------------------------------------------------------ exiting(Reason, _, #concuerror_info{is_timer = Timer} = InfoIn) when Timer =/= false -> Info = case Reason of killed -> #concuerror_info{event = Event} = WaitInfo = process_loop(InfoIn), EventInfo = #exit_event{actor = Timer, reason = normal}, Notification = Event#event{event_info = EventInfo}, add_location_info(exit, notify(Notification, WaitInfo)); normal -> InfoIn end, process_loop(set_status(Info, exited)); exiting(Reason, Stacktrace, InfoIn) -> %% XXX: The ordering of the following events has to be verified (e.g. R16B03): %% XXX: - process marked as exiting, new messages are not delivered, name is %% unregistered %% XXX: - cancel timers %% XXX: - transfer ets ownership and send message or delete table %% XXX: - send link signals %% XXX: - send monitor messages #concuerror_info{ exit_by_signal = ExitBySignal, logger = Logger, status = Status } = InfoIn, case ExitBySignal of true -> ?unique(Logger, ?ltip, msg(signal), []); false -> ok end, Info = process_loop(InfoIn), Self = self(), %% Registered name has to be picked up before the process starts %% exiting, otherwise it is no longer alive and process_info returns %% 'undefined'. {MaybeName, Info} = run_built_in(erlang, process_info, 2, [Self, registered_name], Info), LocatedInfo = #concuerror_info{event = Event} = add_location_info(exit, set_status(Info, exiting)), #concuerror_info{ links = LinksTable, monitors = MonitorsTable, flags = #process_flags{trap_exit = Trapping}} = Info, FetchFun = fun(Mode, Table) -> [begin ets:delete_object(Table, E), case Mode of delete -> ok; deactivate -> ets:insert(Table, {K, D, inactive}) end, {D, S} end || {K, D, S} = E <- ets:lookup(Table, Self)] end, Links = lists:sort(FetchFun(delete, LinksTable)), Monitors = lists:sort(FetchFun(deactivate, MonitorsTable)), Name = case MaybeName of [] -> ?process_name_none; {registered_name, N} -> N end, Notification = Event#event{ event_info = #exit_event{ exit_by_signal = ExitBySignal, last_status = Status, links = [L || {L, _} <- Links], monitors = [M || {M, _} <- Monitors], name = Name, reason = Reason, stacktrace = Stacktrace, trapping = Trapping } }, ExitInfo = notify(Notification, LocatedInfo), FunFold = fun(Fun, Acc) -> Fun(Acc) end, FunList = [fun ets_ownership_exiting_events/1, link_monitor_handlers(fun handle_link/4, Links), link_monitor_handlers(fun handle_monitor/4, Monitors)], NewInfo = ExitInfo#concuerror_info{exit_reason = Reason}, FinalInfo = lists:foldl(FunFold, NewInfo, FunList), ?debug_flag(?loop, exited), process_loop(set_status(FinalInfo, exited)). ets_ownership_exiting_events(Info) -> %% XXX: - transfer ets ownership and send message or delete table %% XXX: Mention that order of deallocation/transfer is not monitored. #concuerror_info{ets_tables = EtsTables} = Info, case ets:match(EtsTables, ?ets_match_owner_to_heir_info(self())) of [] -> Info; UnsortedTables -> Tables = lists:sort(UnsortedTables), Fold = fun([HeirSpec, Tid, Name], InfoIn) -> NameOrTid = ets_get_name_or_tid({Tid, Name}), MFArgs = case HeirSpec of {heir, none} -> ?debug_flag(?heir, no_heir), [ets, delete, [NameOrTid]]; {heir, Pid, Data} -> ?debug_flag(?heir, {using_heir, Tid, HeirSpec}), [ets, give_away, [NameOrTid, Pid, Data]] end, case instrumented(call, MFArgs, exit, InfoIn) of {{didit, true}, NewInfo} -> NewInfo; {_, OtherInfo} -> ?debug_flag(?heir, {problematic_heir, NameOrTid, HeirSpec}), DelMFArgs = [ets, delete, [NameOrTid]], {{didit, true}, NewInfo} = instrumented(call, DelMFArgs, exit, OtherInfo), NewInfo end end, lists:foldl(Fold, Info, Tables) end. handle_link(Link, _S, Reason, InfoIn) -> MFArgs = [erlang, exit, [Link, Reason]], {{didit, true}, NewInfo} = instrumented(call, MFArgs, exit, InfoIn), NewInfo. handle_monitor({Ref, P, As}, S, Reason, InfoIn) -> Msg = {'DOWN', Ref, process, As, Reason}, MFArgs = [erlang, send, [P, Msg]], case S =/= active of true -> #concuerror_info{logger = Logger} = InfoIn, ?unique(Logger, ?lwarning, msg(demonitored), []); false -> ok end, {{didit, Msg}, NewInfo} = instrumented(call, MFArgs, exit, InfoIn), NewInfo. link_monitor_handlers(Handler, LinksOrMonitors) -> fun(Info) -> #concuerror_info{exit_reason = Reason} = Info, Fold = fun({LinkOrMonitor, S}, InfoIn) -> Handler(LinkOrMonitor, S, Reason, InfoIn) end, lists:foldl(Fold, Info, LinksOrMonitors) end. %%------------------------------------------------------------------------------ is_valid_ets_id(NameOrTid) -> is_atom(NameOrTid) orelse is_reference(NameOrTid). -ifdef(BEFORE_OTP_21). ets_system_name_to_tid(Name) -> Name. -else. ets_system_name_to_tid(Name) -> ets:whereis(Name). -endif. ets_access_table_info(NameOrTid, Op, Info) -> #concuerror_info{ets_tables = EtsTables} = Info, ?badarg_if_not(is_valid_ets_id(NameOrTid)), Tid = case is_atom(NameOrTid) of true -> case ets:match(EtsTables, ?ets_match_name_to_tid(NameOrTid)) of [] -> error(badarg); [[RT]] -> RT end; false -> NameOrTid end, case ets:match(EtsTables, ?ets_match_tid_to_permission_info(Tid)) of [] -> error(badarg); [[Owner, Protection, Name, IsSystem]] -> IsAllowed = (Owner =:= self() orelse case ets_ops_access_rights_map(Op) of none -> true; own -> false; read -> Protection =/= private; write -> Protection =:= public end), ?badarg_if_not(IsAllowed), IsSystemInsert = IsSystem andalso ets_ops_access_rights_map(Op) =:= write andalso case element(1, Op) of delete -> false; insert -> true; NotAllowed -> ?crash_instr({restricted_ets_system, NameOrTid, NotAllowed}) end, {Tid, {Tid, Name}, IsSystemInsert} end. ets_ops_access_rights_map(Op) -> case Op of {delete, 1} -> own; {delete, 2} -> write; {delete_all_objects, 1} -> write; {delete_object, 2} -> write; {first, _} -> read; {give_away, _} -> own; {info, _} -> none; {insert, _} -> write; {insert_new, _} -> write; {internal_delete_all, 2} -> write; {internal_select_delete, 2} -> write; {lookup, _} -> read; {lookup_element, _} -> read; {match, _} -> read; {match_object, _} -> read; {member, _} -> read; {next, _} -> read; {rename, 2} -> write; {select, _} -> read; {select_delete, 2} -> write; {update_counter, 3} -> write; {update_element, 3} -> write; {whereis, 1} -> none end. ets_get_name_or_tid(Id) -> case Id of {Tid, ?ets_name_none} -> Tid; {_, Name} -> Name end. %%------------------------------------------------------------------------------ -spec cleanup_processes(processes()) -> ok. cleanup_processes(ProcessesTable) -> ets:delete(?persistent_term), Processes = ets:tab2list(ProcessesTable), Foreach = fun(?process_pat_pid(P)) -> unlink(P), P ! quit end, lists:foreach(Foreach, Processes). %%------------------------------------------------------------------------------ system_ets_entries(#concuerror_info{ets_tables = EtsTables}) -> Map = fun(Name) -> Tid = ets_system_name_to_tid(Name), [Owner, Protection] = [ets:info(Tid, F) || F <- [owner, protection]], ?ets_table_entry_system(Tid, Name, Protection, Owner) end, SystemEtsEntries = [Map(Name) || Name <- ets:all(), is_atom(Name)], ets:insert(EtsTables, SystemEtsEntries). system_processes_wrappers(Info) -> [wrap_system(Name, Info) || Name <- registered()], ok. wrap_system(Name, Info) -> #concuerror_info{processes = Processes} = Info, Wrapped = whereis(Name), {_, Leader} = process_info(Wrapped, group_leader), Fun = fun() -> system_wrapper_loop(Name, Wrapped, Info) end, Pid = spawn_link(Fun), ets:insert(Processes, ?new_system_process(Pid, Name, wrapper)), true = ets:update_element(Processes, Pid, {?process_leader, Leader}), ok. system_wrapper_loop(Name, Wrapped, Info) -> receive quit -> exit(normal); Message -> case Message of {message, #message{data = Data, id = Id}, Report} -> try {F, R} = case Name of application_controller -> throw(comm_application_controller); code_server -> {Call, From, Request} = Data, check_request(Name, Request), erlang:send(Wrapped, {Call, self(), Request}), receive Msg -> {From, Msg} end; erl_prim_loader -> {From, Request} = Data, check_request(Name, Request), erlang:send(Wrapped, {self(), Request}), receive {_, Msg} -> {From, {self(), Msg}} end; error_logger -> %% erlang:send(Wrapped, Data), throw(no_reply); file_server_2 -> {Call, {From, Ref}, Request} = Data, check_request(Name, Request), erlang:send(Wrapped, {Call, {self(), Ref}, Request}), receive Msg -> {From, Msg} end; init -> {From, Request} = Data, check_request(Name, Request), erlang:send(Wrapped, {self(), Request}), receive Msg -> {From, Msg} end; logger -> throw(no_reply); standard_error -> #concuerror_info{logger = Logger} = Info, {From, Reply, _} = handle_io(Data, {standard_error, Logger}), Msg = "Your test sends messages to the 'standard_error' process" " to write output. Such messages from different processes" " may race, producing spurious interleavings. Consider" " using '--non_racing_system standard_error' to avoid" " them.~n", ?unique(Logger, ?ltip, Msg, []), {From, Reply}; user -> #concuerror_info{logger = Logger} = Info, {From, Reply, _} = handle_io(Data, {standard_io, Logger}), Msg = "Your test sends messages to the 'user' process to write" " output. Such messages from different processes may race," " producing spurious interleavings. Consider using" " '--non_racing_system user' to avoid them.~n", ?unique(Logger, ?ltip, Msg, []), {From, Reply}; Else -> throw({unknown_protocol_for_system, {Else, Data}}) end, Report ! {system_reply, F, Id, R, Name}, ok catch no_reply -> send_message_ack(Report, false, false); Reason -> ?crash(Reason); Class:Reason -> ?crash({system_wrapper_error, Name, Class, Reason}) end; {get_info, To} -> To ! {info, {Info, get()}}, ok end, system_wrapper_loop(Name, Wrapped, Info) end. check_request(code_server, get_path) -> ok; check_request(code_server, {ensure_loaded, _}) -> ok; check_request(code_server, {is_cached, _}) -> ok; check_request(code_server, {is_loaded, _}) -> ok; check_request(erl_prim_loader, {get_file, _}) -> ok; check_request(erl_prim_loader, {list_dir, _}) -> ok; check_request(file_server_2, {get_cwd}) -> ok; check_request(file_server_2, {read_file_info, _}) -> ok; check_request(init, {get_argument, _}) -> ok; check_request(init, get_arguments) -> ok; check_request(Name, Request) -> throw({unsupported_request, Name, Request}). reset_concuerror_info(Info) -> {Pid, _} = Info#concuerror_info.notify_when_ready, Info#concuerror_info{ demonitors = [], exit_by_signal = false, exit_reason = normal, flags = #process_flags{}, message_counter = 1, message_queue = queue:new(), event = none, notify_when_ready = {Pid, true}, receive_counter = 1, ref_queue = new_ref_queue(), status = 'running' }. %%------------------------------------------------------------------------------ new_ref_queue() -> {queue:new(), queue:new()}. reset_ref_queue(#concuerror_info{ref_queue = {_, Stored}}) -> {Stored, Stored}. get_ref(#concuerror_info{ref_queue = {Active, Stored}} = Info) -> {Result, NewActive} = queue:out(Active), case Result of {value, Ref} -> {Ref, Info#concuerror_info{ref_queue = {NewActive, Stored}}}; empty -> Ref = make_ref(), NewStored = queue:in(Ref, Stored), {Ref, Info#concuerror_info{ref_queue = {NewActive, NewStored}}} end. make_exit_signal(Reason) -> make_exit_signal(self(), Reason). make_exit_signal(From, Reason) -> {'EXIT', From, Reason}. format_timer_message(SendAfter, Msg, Ref) -> case SendAfter of send_after -> Msg; start_timer -> {timeout, Ref, Msg} end. make_message(Info, Type, Data, Recipient) -> #concuerror_info{event = #event{label = Label} = Event} = Info, {Id, MsgInfo} = get_message_cnt(Info), MessageEvent = #message_event{ cause_label = Label, message = #message{data = Data, id = Id}, recipient = Recipient, type = Type}, NewEvent = Event#event{special = [{message, MessageEvent}]}, MsgInfo#concuerror_info{event = NewEvent}. get_message_cnt(#concuerror_info{message_counter = Counter} = Info) -> {{self(), Counter}, Info#concuerror_info{message_counter = Counter + 1}}. get_receive_cnt(#concuerror_info{receive_counter = Counter} = Info) -> {Counter, Info#concuerror_info{receive_counter = Counter + 1}}. %%------------------------------------------------------------------------------ add_location_info(Location, #concuerror_info{event = Event} = Info) -> Info#concuerror_info{event = Event#event{location = Location}}. set_status(#concuerror_info{processes = Processes} = Info, Status) -> MaybeDropName = case Status =:= exiting of true -> [{?process_name, ?process_name_none}]; false -> [] end, Updates = [{?process_status, Status}|MaybeDropName], true = ets:update_element(Processes, self(), Updates), Info#concuerror_info{status = Status}. is_active(#concuerror_info{exit_by_signal = ExitBySignal, status = Status}) -> not ExitBySignal andalso is_active(Status); is_active(Status) when is_atom(Status) -> (Status =:= running) orelse (Status =:= waiting). -ifdef(BEFORE_OTP_21). erlang_get_stacktrace() -> erlang:get_stacktrace(). -else. erlang_get_stacktrace() -> []. -endif. clean_stacktrace(Trace) -> [T || T <- Trace, not_concuerror_module(element(1, T))]. not_concuerror_module(Atom) -> case atom_to_list(Atom) of "concuerror" ++ _ -> false; _ -> true end. %%------------------------------------------------------------------------------ handle_io({io_request, From, ReplyAs, Req}, IOState) -> {Reply, NewIOState} = io_request(Req, IOState), {From, {io_reply, ReplyAs, Reply}, NewIOState}; handle_io(_, _) -> throw(no_reply). io_request({put_chars, Chars}, {Tag, Data} = IOState) -> true = is_atom(Tag), Logger = Data, concuerror_logger:print(Logger, Tag, Chars), {ok, IOState}; io_request({put_chars, M, F, As}, IOState) -> try apply(M, F, As) of Chars -> io_request({put_chars, Chars}, IOState) catch _:_ -> {{error, request}, IOState} end; io_request({put_chars, _Enc, Chars}, IOState) -> io_request({put_chars, Chars}, IOState); io_request({put_chars, _Enc, Mod, Func, Args}, IOState) -> io_request({put_chars, Mod, Func, Args}, IOState); %% io_request({get_chars, _Enc, _Prompt, _N}, IOState) -> %% {eof, IOState}; %% io_request({get_chars, _Prompt, _N}, IOState) -> %% {eof, IOState}; %% io_request({get_line, _Prompt}, IOState) -> %% {eof, IOState}; %% io_request({get_line, _Enc, _Prompt}, IOState) -> %% {eof, IOState}; %% io_request({get_until, _Prompt, _M, _F, _As}, IOState) -> %% {eof, IOState}; %% io_request({setopts, _Opts}, IOState) -> %% {ok, IOState}; %% io_request(getopts, IOState) -> %% {error, {error, enotsup}, IOState}; %% io_request({get_geometry,columns}, IOState) -> %% {error, {error, enotsup}, IOState}; %% io_request({get_geometry,rows}, IOState) -> %% {error, {error, enotsup}, IOState}; %% io_request({requests, Reqs}, IOState) -> %% io_requests(Reqs, {ok, IOState}); io_request(_, IOState) -> {{error, request}, IOState}. %% io_requests([R | Rs], {ok, IOState}) -> %% io_requests(Rs, io_request(R, IOState)); %% io_requests(_, Result) -> %% Result. %%------------------------------------------------------------------------------ msg(demonitored) -> "Concuerror may let exiting processes emit 'DOWN' messages for cancelled" " monitors. Any such messages are discarded upon delivery and can never be" " received.~n"; msg(exit_normal_self_abnormal) -> "A process that is not trapping exits (~w) sent a 'normal' exit" " signal to itself. This shouldn't make it exit, but in the current" " OTP it does, unless it's trapping exit signals. Concuerror respects the" " implementation.~n"; msg(limited_halt) -> "A process called erlang:halt/1." " Concuerror does not do race analysis for calls to erlang:halt/0,1,2 as" " such analysis would require reordering such calls with too many other" " built-in operations.~n"; msg(register_eunit_server) -> "Your test seems to try to set up an EUnit server. This is a bad" " idea, for at least two reasons:" " 1) you probably don't want to test all of EUnit's boilerplate" " code systematically and" " 2) the default test function generated by EUnit runs all tests," " one after another; as a result, systematic testing will have to" " explore a number of schedulings that is the product of every" " individual test's schedulings! You should use Concuerror on single tests" " instead.~n"; msg(signal) -> "An abnormal exit signal killed a process. This is probably the worst" " thing that can happen race-wise, as any other side-effecting" " operation races with the arrival of the signal. If the test produces" " too many interleavings consider refactoring your code.~n". %%------------------------------------------------------------------------------ -spec explain_error(term()) -> string(). explain_error({checking_system_process, Pid}) -> io_lib:format( "A process tried to link/monitor/inspect process ~p which was not" " started by Concuerror and has no suitable wrapper to work with" " Concuerror." ?notify_us_msg, [Pid]); explain_error(comm_application_controller) -> io_lib:format( "Your test communicates with the 'application_controller' process. This" " is problematic, as this process is not under Concuerror's" " control. Try to start the test from a top-level" " supervisor (or even better a top level gen_server) instead.", [] ); explain_error({inconsistent_builtin, [Module, Name, Arity, Args, OldResult, NewResult, Location]}) -> io_lib:format( "While re-running the program, a call to ~p:~p/~p with" " arguments:~n ~p~nreturned a different result:~n" "Earlier result: ~p~n" " Later result: ~p~n" "Concuerror cannot explore behaviours that depend on~n" "data that may differ on separate runs of the program.~n" "Location: ~p~n", [Module, Name, Arity, Args, OldResult, NewResult, Location]); explain_error({no_response_for_message, Timeout, Recipient}) -> io_lib:format( "A process took more than ~pms to send an acknowledgement for a message" " that was sent to it. (Process: ~p)" ?notify_us_msg, [Timeout, Recipient]); explain_error({not_local_node, Node}) -> io_lib:format( "A built-in tried to use ~p as a remote node. Concuerror does not support" " remote nodes.", [Node]); explain_error({process_did_not_respond, Timeout, Actor}) -> io_lib:format( "A process (~p) took more than ~pms to report a built-in event. You can try" " to increase the '--timeout' limit and/or ensure that there are no" " infinite loops in your test.", [Actor, Timeout] ); explain_error({registered_process_not_wrapped, Name}) -> io_lib:format( "The test tries to communicate with a process registered as '~w' that is" " not under Concuerror's control." ?can_fix_msg, [Name]); explain_error({restricted_ets_system, NameOrTid, NotAllowed}) -> io_lib:format( "A process tried to execute an 'ets:~p' operation on ~p. Only insert and" " delete write operations are supported for public ETS tables owned by" " 'system' processes." ?can_fix_msg, [NotAllowed, NameOrTid]); explain_error({system_wrapper_error, Name, Type, Reason}) -> io_lib:format( "Concuerror's wrapper for system process ~p crashed (~p):~n" " Reason: ~p~n" ?notify_us_msg, [Name, Type, Reason]); explain_error({unexpected_builtin_change, [Module, Name, Arity, Args, M, F, OArgs, Location]}) -> io_lib:format( "While re-running the program, a call to ~p:~p/~p with" " arguments:~n ~p~nwas found instead of the original call~n" "to ~p:~p/~p with args:~n ~p~n" "Concuerror cannot explore behaviours that depend on~n" "data that may differ on separate runs of the program.~n" "Location: ~p~n", [Module, Name, Arity, Args, M, F, length(OArgs), OArgs, Location]); explain_error({unknown_protocol_for_system, {System, Data}}) -> io_lib:format( "A process tried to send a message (~p) to system process ~p. Concuerror" " does not currently support communication with this process." ?can_fix_msg, [Data, System]); explain_error({unknown_built_in, {Module, Name, Arity, Location}}) -> LocationString = case Location of [Line, {file, File}] -> location(File, Line); _ -> "" end, io_lib:format( "Concuerror does not support calls to built-in ~p:~p/~p~s." ?can_fix_msg, [Module, Name, Arity, LocationString]); explain_error({unsupported_request, Name, Type}) -> io_lib:format( "A process sent a request of type '~w' to ~p. Concuerror does not yet" " support this type of request to this process." ?can_fix_msg, [Type, Name]). location(F, L) -> Basename = filename:basename(F), io_lib:format(" (found in ~s line ~w)", [Basename, L]). %%------------------------------------------------------------------------------ -spec is_unsafe({atom(), atom(), non_neg_integer()}) -> boolean(). is_unsafe({erlang, exit, 2}) -> true; is_unsafe({erlang, pid_to_list, 1}) -> true; %% Instrumented for symbolic PIDs pretty printing. is_unsafe({erlang, fun_to_list, 1}) -> true; %% Instrumented for fun pretty printing. is_unsafe({erlang, F, A}) -> case (erl_internal:guard_bif(F, A) orelse erl_internal:arith_op(F, A) orelse erl_internal:bool_op(F, A) orelse erl_internal:comp_op(F, A) orelse erl_internal:list_op(F, A) orelse is_data_type_conversion_op(F)) of true -> false; false -> StringF = atom_to_list(F), not erl_safe(StringF) end; is_unsafe({erts_internal, garbage_collect, _}) -> false; is_unsafe({erts_internal, map_next, 3}) -> false; is_unsafe({Safe, _, _}) when Safe =:= binary ; Safe =:= lists ; Safe =:= maps ; Safe =:= math ; Safe =:= re ; Safe =:= string ; Safe =:= unicode -> false; is_unsafe({error_logger, warning_map, 0}) -> false; is_unsafe({file, native_name_encoding, 0}) -> false; is_unsafe({net_kernel, dflag_unicode_io, 1}) -> false; is_unsafe({os, F, A}) when {F, A} =:= {get_env_var, 1}; {F, A} =:= {getenv, 1} -> false; is_unsafe({prim_file, internal_name2native, 1}) -> false; is_unsafe(_) -> true. is_data_type_conversion_op(Name) -> StringName = atom_to_list(Name), case re:split(StringName, "_to_") of [_] -> false; [_, _] -> true end. erl_safe("adler32" ++ _) -> true; erl_safe("append" ++ _) -> true; erl_safe("apply" ) -> true; erl_safe("bump_reductions" ) -> true; erl_safe("crc32" ++ _) -> true; erl_safe("decode_packet" ) -> true; erl_safe("delete_element" ) -> true; erl_safe("delete_module" ) -> true; erl_safe("dt_" ++ _) -> true; erl_safe("error" ) -> true; erl_safe("exit" ) -> true; erl_safe("external_size" ) -> true; erl_safe("fun_info" ++ _) -> true; erl_safe("function_exported" ) -> true; erl_safe("garbage_collect" ) -> true; erl_safe("get_module_info" ) -> true; erl_safe("hibernate" ) -> false; %% Must be instrumented. erl_safe("insert_element" ) -> true; erl_safe("iolist_size" ) -> true; erl_safe("is_builtin" ) -> true; erl_safe("load_nif" ) -> true; erl_safe("make_fun" ) -> true; erl_safe("make_tuple" ) -> true; erl_safe("match_spec_test" ) -> true; erl_safe("md5" ++ _) -> true; erl_safe("nif_error" ) -> true; erl_safe("phash" ++ _) -> true; erl_safe("raise" ) -> true; erl_safe("seq_" ++ _) -> true; erl_safe("setelement" ) -> true; erl_safe("split_binary" ) -> true; erl_safe("subtract" ) -> true; erl_safe("throw" ) -> true; erl_safe( _) -> false. ================================================ FILE: src/concuerror_dependencies.erl ================================================ %%% @private -module(concuerror_dependencies). -export([dependent/3, dependent_safe/2, explain_error/1]). -export_type([assume_racing_opt/0]). %%------------------------------------------------------------------------------ -include("concuerror.hrl"). -type assume_racing_opt() :: {boolean(), concuerror_logger:logger() | 'ignore'}. %%------------------------------------------------------------------------------ -type dep_ret() :: boolean() | 'irreversible' | {'true', message_id()}. -spec dependent_safe(event(), event()) -> dep_ret(). dependent_safe(E1, E2) -> dependent(E1, E2, {true, ignore}). -spec dependent(event(), event(), assume_racing_opt()) -> dep_ret(). dependent(#event{actor = A}, #event{actor = A}, _) -> irreversible; dependent(#event{event_info = Info1, special = Special1}, #event{event_info = Info2, special = Special2}, AssumeRacing) -> try case dependent(Info1, Info2) of false -> M1 = [M || {message_delivered, M} <- Special1], M2 = [M || {message_delivered, M} <- Special2], first_non_false_dep([Info1|M1], M2, [Info2|M2]); Else -> Else end catch throw:irreversible -> irreversible; error:function_clause -> case AssumeRacing of {true, ignore} -> true; {true, Logger} -> Explanation = show_undefined_dependency(Info1, Info2), Msg = io_lib:format( "~s~n" " Concuerror treats such pairs as racing (--assume_racing)." " (No other such warnings will appear)~n", [Explanation]), ?unique(Logger, ?lwarning, Msg, []), true; {false, _} -> ?crash({undefined_dependency, Info1, Info2, []}) end end. first_non_false_dep([], _, _) -> false; first_non_false_dep([_|R], [], I2) -> first_non_false_dep(R, I2, I2); first_non_false_dep([I1H|_] = I1, [I2H|R], I2) -> case dependent(I1H, I2H) of false -> first_non_false_dep(I1, R, I2); Else -> Else end. %% The first event happens before the second. dependent(_, #builtin_event{mfargs = {erlang, halt, _}}) -> false; dependent(#builtin_event{status = {crashed, _}}, #builtin_event{status = {crashed, _}}) -> false; dependent(#builtin_event{mfargs = MFArgs, extra = Extra}, #exit_event{} = Exit) -> dependent_exit(Exit, MFArgs, Extra); dependent(#exit_event{} = Exit, #builtin_event{} = Builtin) -> dependent(Builtin, Exit); dependent(#builtin_event{mfargs = {erlang, process_info, _}} = PInfo, B) -> dependent_process_info(PInfo, B); dependent(B, #builtin_event{mfargs = {erlang, process_info, _}} = PInfo) -> dependent_process_info(PInfo, B); dependent(#builtin_event{} = BI1, #builtin_event{} = BI2) -> dependent_built_in(BI1, BI2); dependent(#builtin_event{actor = Recipient, exiting = false, trapping = Trapping} = Builtin, #message_event{message = #message{data = Signal}, recipient = Recipient, type = exit_signal}) -> #builtin_event{mfargs = MFArgs, result = Old} = Builtin, Signal =:= kill orelse case MFArgs of {erlang,process_flag,[trap_exit,New]} when New =/= Old -> true; _ -> {'EXIT', _, Reason} = Signal, not Trapping andalso Reason =/= normal end; dependent(#builtin_event{actor = Recipient, mfargs = {erlang, demonitor, [R|Rest]} }, #message_event{message = #message{data = {'DOWN', R, _, _, _}}, recipient = Recipient, type = message}) -> Options = case Rest of [] -> []; [O] -> O end, try [] = [O || O <- Options, O =/= flush, O =/= info], {lists:member(flush, Options), lists:member(info, Options)} of {true, false} -> false; %% Message will be discarded either way {true, true} -> true; %% Result is affected by the message being flushed {false, _} -> true %% Message is discarded upon delivery or not catch _:_ -> false end; dependent(#builtin_event{}, #message_event{}) -> false; dependent(#message_event{} = Message, #builtin_event{} = Builtin) -> dependent(Builtin, Message); dependent(#exit_event{ actor = Recipient, exit_by_signal = ExitBySignal, last_status = LastStatus, trapping = Trapping}, #message_event{ killing = Killing, message = #message{data = Signal}, recipient = Recipient, type = exit_signal}) -> case Killing andalso ExitBySignal of true -> case LastStatus =:= running of false -> throw(irreversible); true -> true end; false -> case ExitBySignal of true -> false; false -> case Signal of kill -> true; {'EXIT', _, Reason} -> not Trapping andalso Reason =/= normal end end end; dependent(#message_event{} = Message, #exit_event{} = Exit) -> dependent(Exit, Message); dependent(#exit_event{}, #exit_event{}) -> false; dependent(#message_event{ killing = Killing1, message = #message{id = Id, data = EarlyData}, receive_info = EarlyInfo, recipient = Recipient, trapping = Trapping, type = EarlyType}, #message_event{ killing = Killing2, message = #message{data = Data}, receive_info = LateInfo, recipient = Recipient, type = Type }) -> KindFun = fun(exit_signal, _, kill) -> exit_signal; (exit_signal, true, _) -> message; ( message, _, _) -> message; (exit_signal, _, _) -> exit_signal end, case {KindFun(EarlyType, Trapping, EarlyData), KindFun(Type, Trapping, Data)} of {message, message} -> case EarlyInfo of undefined -> true; not_received -> false; {Counter1, Patterns} -> ObsDep = Patterns(Data) andalso case LateInfo of {Counter2, _} -> Counter2 >= Counter1; not_received -> true; undefined -> false end, case ObsDep of true -> {true, Id}; false -> false end end; {_, _} -> Killing1 orelse Killing2 %% This is an ugly hack, see blame. end; dependent(#message_event{ message = #message{data = Data, id = MsgId}, recipient = Recipient, type = Type }, #receive_event{ message = Recv, receive_info = {_, Patterns}, recipient = Recipient, timeout = Timeout, trapping = Trapping }) -> EffType = case {Type, Trapping, Data} of {exit_signal, _, kill} -> exit_signal; {exit_signal, true, _} -> message; { message, _, _} -> message; _ -> exit_signal end, case EffType =:= exit_signal of true -> case Data of kill -> true; {'EXIT', _, Reason} -> not Trapping andalso Reason =/= normal end; false -> case Recv of 'after' -> %% Can only happen during wakeup (otherwise an actually %% delivered msg would be received) message_could_match(Patterns, Data, Trapping, Type); #message{id = RecId} -> %% Race exactly with the delivery of the received %% message MsgId =:= RecId andalso case Timeout =/= infinity of true -> true; false -> throw(irreversible) end end end; dependent(#receive_event{ message = 'after', receive_info = {RecCounter, Patterns}, recipient = Recipient, trapping = Trapping}, #message_event{ message = #message{data = Data}, receive_info = LateInfo, recipient = Recipient, type = Type }) -> case LateInfo of {Counter, _} -> %% The message might have been discarded before the receive. Counter >= RecCounter; _ -> true end andalso message_could_match(Patterns, Data, Trapping, Type); dependent(#receive_event{ recipient = Recipient, trapping = Trapping}, #message_event{ message = #message{data = Signal}, recipient = Recipient, type = exit_signal }) -> case Signal of kill -> true; {'EXIT', _, Reason} -> not Trapping andalso Reason =/= normal end; dependent(#message_event{}, _EventB) -> false; dependent(_EventA, #message_event{}) -> false; dependent(#receive_event{}, _EventB) -> false; dependent(_EventA, #receive_event{}) -> false. %%------------------------------------------------------------------------------ dependent_exit(#exit_event{actor = Exiting, name = Name}, {erlang,UnRegisterOp,[RName|Rest]}, Extra) when UnRegisterOp =:= register; UnRegisterOp =:= unregister -> RName =:= Name orelse case UnRegisterOp of unregister -> Extra =:= Exiting; register -> [Pid] = Rest, Exiting =:= Pid end; dependent_exit(Exit, MFArgs, _Extra) -> dependent_exit(Exit, MFArgs). dependent_exit(_Exit, {erlang, A, _}) when false ;A =:= date ;A =:= exit ;A =:= get_stacktrace ;A =:= make_ref ;A =:= module_loaded ;A =:= monotonic_time ;A =:= now ;A =:= process_flag ;A =:= send_after ;A =:= spawn ;A =:= spawn_link ;A =:= spawn_opt ;A =:= start_timer ;A =:= system_time ;A =:= time ;A =:= time_offset ;A =:= timestamp ;A =:= unique_integer -> false; dependent_exit(_Exit, {os, Name, []}) when false ;Name =:= system_time ;Name =:= timestamp -> false; dependent_exit(_Exit, {os, Name, [_]}) when false ;Name =:= system_time -> false; dependent_exit(_Exit, {persistent_term, _, _}) -> false; dependent_exit(#exit_event{}, {_, group_leader, []}) -> false; dependent_exit(#exit_event{actor = Exiting}, {_, group_leader, [Leader, Leaded]}) -> Exiting =:= Leader orelse Exiting =:= Leaded; dependent_exit(#exit_event{actor = Actor}, {erlang, processes, []}) -> is_pid(Actor); dependent_exit(#exit_event{actor = Cancelled}, {erlang, ReadorCancelTimer, [Timer]}) when ReadorCancelTimer =:= read_timer; ReadorCancelTimer =:= cancel_timer -> Cancelled =:= Timer; dependent_exit(#exit_event{actor = Exiting}, {erlang, is_process_alive, [Pid]}) -> Exiting =:= Pid; dependent_exit(#exit_event{actor = Exiting}, {erlang, process_info, [Pid|_]}) -> Exiting =:= Pid; dependent_exit(#exit_event{actor = Exiting}, {erlang, UnLink, [Linked]}) when UnLink =:= link; UnLink =:= unlink -> Exiting =:= Linked; dependent_exit(#exit_event{monitors = Monitors}, {erlang, demonitor, [Ref|Rest]}) -> Options = case Rest of [] -> []; [O] -> O end, try [] = [O || O <- Options, O =/= flush, O =/= info], {lists:member(flush, Options), lists:member(info, Options)} of {false, true} -> %% Result is whether monitor has been emitted false =/= lists:keyfind(Ref, 1, Monitors); {_, _} -> false catch _:_ -> false end; dependent_exit(#exit_event{actor = Exiting, name = Name}, {erlang, monitor, [process, PidOrName]}) -> Exiting =:= PidOrName orelse Name =:= PidOrName; dependent_exit(#exit_event{name = Name}, {erlang, NameRelated, [OName|_]}) when NameRelated =:= '!'; NameRelated =:= send; NameRelated =:= whereis -> OName =:= Name; dependent_exit(#exit_event{actor = Exiting}, {ets, give_away, [_, Pid, _]}) -> Exiting =:= Pid; dependent_exit(_Exit, {ets, _, _}) -> false. %%------------------------------------------------------------------------------ dependent_process_info(#builtin_event{mfargs = {M,F,[Pid, List]}} = ProcessInfo, Other) when is_list(List) -> Pred = fun(Item) -> ItemInfo = ProcessInfo#builtin_event{mfargs = {M,F,[Pid,Item]}}, dependent_process_info(ItemInfo, Other) end, lists:any(Pred, List); dependent_process_info(#builtin_event{mfargs = {_,_,[Pid, group_leader]}}, Other) -> case Other of #builtin_event{mfargs = {_,group_leader,[_, Pid]}} -> true; _ -> false end; dependent_process_info(#builtin_event{mfargs = {_,_,[Pid, links]}}, Other) -> case Other of #builtin_event{ actor = Pid, mfargs = {erlang, UnLink, _} } when UnLink =:= link; UnLink =:= unlink -> true; #builtin_event{mfargs = {erlang, UnLink, [Pid]}} when UnLink =:= link; UnLink =:= unlink -> true; _ -> false end; dependent_process_info(#builtin_event{mfargs = {_,_,[Pid, message_queue_len]}}, Other) -> case Other of #message_event{recipient = Recipient} -> Recipient =:= Pid; #receive_event{recipient = Recipient, message = M} -> Recipient =:= Pid andalso M =/= 'after'; #builtin_event{actor = Recipient, mfargs = {M, F, [_, Args]}} -> Recipient =:= Pid andalso {M, F} =:= {erlang, demonitor} andalso try lists:member(flush, Args) catch _:_ -> false end; _ -> false end; dependent_process_info(#builtin_event{mfargs = {_, _, [Pid, registered_name]}}, Other) -> case Other of #builtin_event{extra = E, mfargs = {Module, Name, Args}} -> case Module =:= erlang of true when Name =:= register -> [_, RPid] = Args, Pid =:= RPid; true when Name =:= unregister -> E =:= Pid; _ -> false end; _ -> false end; dependent_process_info(#builtin_event{mfargs = {_,_,[Pid, trap_exit]}}, Other) -> case Other of #builtin_event{ actor = Pid, mfargs = {erlang, process_flag, [trap_exit, _]}} -> true; _ -> false end; dependent_process_info(#builtin_event{mfargs = {_,_,[_, Safe]}}, _) when Safe =:= current_function; Safe =:= current_stacktrace; Safe =:= dictionary; Safe =:= heap_size; Safe =:= messages; %% If fixed, it should be an observer of message races Safe =:= reductions; Safe =:= stack_size; Safe =:= status -> false. %%------------------------------------------------------------------------------ dependent_built_in(#builtin_event{mfargs = {_,group_leader,ArgsA}} = A, #builtin_event{mfargs = {_,group_leader,ArgsB}} = B) -> case {ArgsA, ArgsB} of {[], []} -> false; {[New, For], []} -> #builtin_event{actor = Actor, result = Result} = B, New =/= Result andalso Actor =:= For; {[], [_,_]} -> dependent_built_in(B, A); {[_, ForA], [_, ForB]} -> ForA =:= ForB end; dependent_built_in(#builtin_event{actor = A, mfargs = {erlang, Spawn, _}}, #builtin_event{mfargs = {_, group_leader, [_, Leaded]}}) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_opt -> Leaded =:= A; dependent_built_in(#builtin_event{mfargs = {_, group_leader, [_, Leaded]}}, #builtin_event{actor = A, mfargs = {erlang, Spawn, _}}) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_opt -> Leaded =:= A; dependent_built_in(#builtin_event{mfargs = {_, group_leader, _}}, #builtin_event{}) -> false; dependent_built_in(#builtin_event{}, #builtin_event{mfargs = {_, group_leader, _}}) -> false; dependent_built_in(#builtin_event{mfargs = {erlang, processes, []}}, #builtin_event{mfargs = {erlang, Spawn, _}}) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_opt -> true; dependent_built_in(#builtin_event{mfargs = {erlang, Spawn, _}}, #builtin_event{mfargs = {erlang, processes, []}}) when Spawn =:= spawn; Spawn =:= spawn_link; Spawn =:= spawn_opt -> true; dependent_built_in(#builtin_event{mfargs = {erlang, A, _}}, #builtin_event{mfargs = {erlang, B, _}}) when (A =:= '!' orelse A =:= send orelse A =:= whereis orelse A =:= process_flag orelse A =:= link orelse A =:= unlink), (B =:= '!' orelse B =:= send orelse B =:= whereis orelse B =:= process_flag orelse B =:= link orelse B =:= unlink) -> false; dependent_built_in(#builtin_event{mfargs = {erlang,UnRegisterA,[AName|ARest]}}, #builtin_event{mfargs = {erlang,UnRegisterB,[BName|BRest]}}) when (UnRegisterA =:= register orelse UnRegisterA =:= unregister), (UnRegisterB =:= register orelse UnRegisterB =:= unregister) -> AName =:= BName orelse (ARest =/= [] andalso ARest =:= BRest); dependent_built_in(#builtin_event{mfargs = {erlang,SendOrWhereis,[SName|_]}}, #builtin_event{mfargs = {erlang,UnRegisterOp,[RName|_]}}) when (UnRegisterOp =:= register orelse UnRegisterOp =:= unregister), (SendOrWhereis =:= '!' orelse SendOrWhereis =:= send orelse SendOrWhereis =:= whereis) -> SName =:= RName; dependent_built_in(#builtin_event{mfargs = {erlang,UnRegisterOp,_}} = R, #builtin_event{mfargs = {erlang,SendOrWhereis,_}} = S) when (UnRegisterOp =:= register orelse UnRegisterOp =:= unregister), (SendOrWhereis =:= '!' orelse SendOrWhereis =:= send orelse SendOrWhereis =:= whereis) -> dependent_built_in(S, R); dependent_built_in(#builtin_event{mfargs = {erlang,monitor,[process,SName]}}, #builtin_event{mfargs = {erlang,UnRegisterOp,[RName|_]}}) when (UnRegisterOp =:= register orelse UnRegisterOp =:= unregister) -> SName =:= RName; dependent_built_in(#builtin_event{mfargs = {erlang,UnRegisterOp,_}} = R, #builtin_event{mfargs = {erlang,monitor,_}} = S) when (UnRegisterOp =:= register orelse UnRegisterOp =:= unregister) -> dependent_built_in(S, R); dependent_built_in(#builtin_event{mfargs = {erlang,RegistryOp,_}}, #builtin_event{mfargs = {erlang,LinkOp,_}}) when (RegistryOp =:= register orelse RegistryOp =:= unregister orelse RegistryOp =:= whereis), (LinkOp =:= link orelse LinkOp =:= unlink) -> false; dependent_built_in(#builtin_event{mfargs = {erlang,LinkOp,_}} = L, #builtin_event{mfargs = {erlang,RegistryOp,_}} = R) when (RegistryOp =:= register orelse RegistryOp =:= unregister orelse RegistryOp =:= whereis), (LinkOp =:= link orelse LinkOp =:= unlink) -> dependent_built_in(R, L); dependent_built_in(#builtin_event{mfargs = {erlang,ReadorCancelTimerA,[TimerA]}}, #builtin_event{mfargs = {erlang,ReadorCancelTimerB,[TimerB]}}) when (ReadorCancelTimerA =:= read_timer orelse ReadorCancelTimerA =:= cancel_timer), (ReadorCancelTimerB =:= read_timer orelse ReadorCancelTimerB =:= cancel_timer), (ReadorCancelTimerA =:= cancel_timer orelse ReadorCancelTimerB =:= cancel_timer) -> TimerA =:= TimerB; dependent_built_in(#builtin_event{mfargs = {erlang,send,_}, extra = Extra}, #builtin_event{mfargs = {erlang,ReadorCancelTimer,[Timer]}}) when is_reference(Extra), (ReadorCancelTimer =:= read_timer orelse ReadorCancelTimer =:= cancel_timer) -> Extra =:= Timer; dependent_built_in(#builtin_event{mfargs = {erlang,ReadorCancelTimer,_}} = Timer, #builtin_event{mfargs = {erlang,send,_}} = Deliver) when ReadorCancelTimer =:= read_timer; ReadorCancelTimer =:= cancel_timer -> dependent_built_in(Deliver, Timer); dependent_built_in(#builtin_event{mfargs = {erlang,ReadorCancelTimer,_}}, #builtin_event{}) when ReadorCancelTimer =:= read_timer; ReadorCancelTimer =:= cancel_timer -> false; dependent_built_in(#builtin_event{}, #builtin_event{mfargs = {erlang,ReadorCancelTimer,_}}) when ReadorCancelTimer =:= read_timer; ReadorCancelTimer =:= cancel_timer -> false; dependent_built_in(#builtin_event{mfargs = {erlang, monotonic_time, _}}, #builtin_event{mfargs = {erlang, monotonic_time, _}}) -> true; dependent_built_in(#builtin_event{mfargs = {erlang, _, _}}, #builtin_event{mfargs = {ets, _, _}}) -> false; dependent_built_in(#builtin_event{mfargs = {ets, _, _}} = Ets, #builtin_event{mfargs = {erlang, _, _}} = Erlang) -> dependent_built_in(Erlang, Ets); dependent_built_in(#builtin_event{mfargs = {persistent_term, Name1, Args1}}, #builtin_event{mfargs = {persistent_term, Name2, Args2}}) -> case {Name1, Name2, Args1, Args2} of {get, get, _, _} -> false; {Mod, _, [K|_], [K|_]} when Mod =:= put; Mod =:= erase -> true; {_, Mod, [K|_], [K|_]} when Mod =:= put; Mod =:= erase -> true; _ -> false end; dependent_built_in(#builtin_event{mfargs = {persistent_term, _, _}}, #builtin_event{}) -> false; dependent_built_in(#builtin_event{}, #builtin_event{mfargs = {persistent_term, _, _}}) -> false; dependent_built_in(#builtin_event{mfargs = {os, Name, []}}, #builtin_event{}) when false ;Name =:= system_time ;Name =:= timestamp -> false; dependent_built_in(#builtin_event{mfargs = {os, Name, [_]}}, #builtin_event{}) when false ;Name =:= system_time -> false; dependent_built_in(#builtin_event{} = Other, #builtin_event{mfargs = {os, _, _}} = OsOp) -> dependent_built_in(OsOp, Other); dependent_built_in(#builtin_event{mfargs = {MaybeErlangA, A, _}}, #builtin_event{mfargs = {MaybeErlangB, B, _}}) when MaybeErlangA =:= erlang; MaybeErlangB =:= erlang -> MaybeSafeErlangA = MaybeErlangA =:= erlang andalso safe_erlang(A), MaybeSafeErlangB = MaybeErlangB =:= erlang andalso safe_erlang(B), case {MaybeSafeErlangA, MaybeSafeErlangB} of {true, _} -> false; {_, true} -> false; {_, _} -> error(function_clause) end; %%------------------------------------------------------------------------------ dependent_built_in(#builtin_event{mfargs = {ets, rename, [TableA, NameA]} , extra = IdA}, #builtin_event{mfargs = {ets, AnyB, [TableB|ArgB]} , extra = IdB}) -> ets_same_table(TableA, IdA, TableB, IdB) orelse ets_same_table(NameA, IdA, TableB, IdB) orelse TableA =:= TableB orelse (AnyB =:= rename andalso ArgB =:= [NameA]); dependent_built_in(#builtin_event{mfargs = {ets, _Any, _}} = EventA, #builtin_event{mfargs = {ets, rename, _}} = EventB) -> dependent_built_in(EventB, EventA); dependent_built_in(#builtin_event{mfargs = {ets, delete, [TableA]} , extra = IdA}, #builtin_event{mfargs = {ets, _Any, [TableB|_]} , extra = IdB}) -> ets_same_table(TableA, IdA, TableB, IdB); dependent_built_in(#builtin_event{mfargs = {ets, _Any, _}} = EventA, #builtin_event{mfargs = {ets, delete, [_]}} = EventB) -> dependent_built_in(EventB, EventA); dependent_built_in(#builtin_event{mfargs = {ets, new, [TableA|_]} , extra = IdA}, #builtin_event{mfargs = {ets, _Any, [TableB|_]} , extra = IdB}) -> ets_same_table(TableA, IdA, TableB, IdB); dependent_built_in(#builtin_event{mfargs = {ets, _Any, _}} = EventA, #builtin_event{mfargs = {ets, new, _}} = EventB) -> dependent_built_in(EventB, EventA); dependent_built_in(#builtin_event{ mfargs = {ets, _, [TableA|_]} , extra = IdA} = EventA, #builtin_event{ mfargs = {ets, _, [TableB|_]} , extra = IdB} = EventB) -> ets_same_table(TableA, IdA, TableB, IdB) andalso case ets_is_mutating(EventA) of false -> case ets_is_mutating(EventB) of false -> false; Pred -> Pred(EventA) end; Pred -> Pred(EventB) end. %%------------------------------------------------------------------------------ safe_erlang(A) when false ;A =:= date ;A =:= demonitor %% Depends only with an exit event or proc_info ;A =:= exit %% Sending an exit signal (dependencies are on delivery) ;A =:= get_stacktrace %% Depends with nothing ;A =:= is_process_alive %% Depends only with an exit event ;A =:= make_ref %% Depends with nothing ;A =:= module_loaded ;A =:= monitor %% Depends only with an exit event or proc_info ;A =:= monotonic_time ;A =:= now ;A =:= process_flag %% Depends only with delivery of a signal ;A =:= processes %% Depends only with spawn and exit ;A =:= send_after ;A =:= spawn %% Depends only with processes/0 ;A =:= spawn_link %% Depends only with processes/0 ;A =:= spawn_opt %% Depends only with processes/0 ;A =:= start_timer ;A =:= system_time ;A =:= time ;A =:= time_offset ;A =:= timestamp ;A =:= unique_integer -> true; safe_erlang(_) -> false. %%------------------------------------------------------------------------------ message_could_match(Patterns, Data, Trapping, Type) -> Patterns(Data) andalso ((Trapping andalso Data =/= kill) orelse (Type =:= message)). %%------------------------------------------------------------------------------ ets_same_table(TableA, IdA, TableB, IdB) -> ets_same_table(IdA, TableB) orelse ets_same_table(IdB, TableA). ets_same_table(undefined, _Arg) -> false; ets_same_table({Tid, Name}, Arg) -> case is_atom(Arg) of true -> Name =:= Arg; false -> Tid =:= Arg end. -define(deps_with_any,fun(_) -> true end). ets_is_mutating(#builtin_event{ status = {crashed, _}}) -> false; ets_is_mutating(#builtin_event{ mfargs = {_, Op, [_|Rest] = Args} , extra = {Tid, _}} = Event) -> case {Op, length(Args)} of {delete, 2} -> with_key(hd(Rest)); {delete_object, 2} -> from_insert(Tid, hd(Rest), true); {DelAll, N} when {DelAll, N} =:= {delete_all_objects, 1}; {DelAll, N} =:= {internal_delete_all, 2} -> ?deps_with_any; {first, _} -> false; {give_away, _} -> ?deps_with_any; {info, _} -> false; {insert, _} -> from_insert(Tid, hd(Rest), false); {insert_new, _} when Event#builtin_event.result -> from_insert(Tid, hd(Rest), true); {insert_new, _} -> false; {lookup, _} -> false; {lookup_element, _} -> false; {match, _} -> false; {match_object, _} -> false; {member, _} -> false; {next, _} -> false; {select, _} -> false; {SelDelete, 2} when SelDelete =:= select_delete; SelDelete =:= internal_select_delete -> from_delete(hd(Rest)); {update_counter, 3} -> with_key(hd(Rest)); {update_element, 3} -> with_key(hd(Rest)); {whereis, 1} -> false end. with_key(Key) -> fun(Event) -> Keys = ets_reads_keys(Event), case Keys =:= any of true -> true; false -> lists:any(fun(K) -> K =:= Key end, Keys) end end. ets_reads_keys(Event) -> case keys_or_tuples(Event) of any -> any; none -> []; {matchspec, _MS} -> any; % can't test the matchspec against a single key {keys, Keys} -> Keys; {tuples, Tuples} -> #builtin_event{extra = {Tid, _}} = Event, KeyPos = ets:info(Tid, keypos), [element(KeyPos, Tuple) || Tuple <- Tuples] end. keys_or_tuples(#builtin_event{mfargs = {_, Op, [_|Rest] = Args}}) -> case {Op, length(Args)} of {delete, 2} -> {keys, [hd(Rest)]}; {DelAll, N} when {DelAll, N} =:= {delete_all_objects, 1}; {DelAll, N} =:= {internal_delete_all, 2} -> any; {delete_object, 2} -> {tuples, [hd(Rest)]}; {first, _} -> any; {give_away, _} -> any; {info, _} -> any; {Insert, _} when Insert =:= insert; Insert =:= insert_new -> Inserted = hd(Rest), {tuples, case is_list(Inserted) of true -> Inserted; false -> [Inserted] end}; {lookup, _} -> {keys, [hd(Rest)]}; {lookup_element, _} -> {keys, [hd(Rest)]}; {match, _} -> {matchspec, [{hd(Rest), [], ['$$']}]}; {match_object, _} -> {matchspec, [{hd(Rest), [], ['$_']}]}; {member, _} -> {keys, [hd(Rest)]}; {next, _} -> any; {select, _} -> {matchspec, hd(Rest)}; {SelDelete, 2} when SelDelete =:= select_delete; SelDelete =:= internal_select_delete -> {matchspec, hd(Rest)}; {update_counter, 3} -> {keys, [hd(Rest)]}; {update_element, 3} -> {keys, [hd(Rest)]}; {whereis, 1} -> none end. from_insert(undefined, _, _) -> %% If table is undefined the op crashed so not mutating false; from_insert(Table, Insert, InsertNewOrDelete) -> KeyPos = ets:info(Table, keypos), InsertList = case is_list(Insert) of true -> Insert; false -> [Insert] end, fun(Event) -> case keys_or_tuples(Event) of any -> true; none -> false; {keys, Keys} -> InsertKeys = ordsets:from_list([element(KeyPos, T) || T <- InsertList]), lists:any(fun(K) -> ordsets:is_element(K, InsertKeys) end, Keys); {tuples, Tuples} -> Pred = fun(Tuple) -> case lists:keyfind(element(KeyPos, Tuple), KeyPos, InsertList) of false -> false; InsertTuple -> InsertNewOrDelete orelse Tuple =/= InsertTuple end end, lists:any(Pred, Tuples); {matchspec, MS} -> Pred = fun (Tuple) -> case erlang:match_spec_test(Tuple, MS, table) of {error, _} -> false; {ok, Result, [], _Warnings} -> Result =/= false end end, lists:any(Pred, InsertList) end end. from_delete(MatchSpec) -> fun (Event) -> case keys_or_tuples(Event) of any -> true; none -> false; {keys, _Keys} -> true; {matchspec, _MS} -> true; {tuples, Tuples} -> Pred = fun (Tuple) -> case erlang:match_spec_test(Tuple, MatchSpec, table) of {error, _} -> false; {ok, Result, [], _Warnings} -> Result =:= true end end, lists:any(Pred, Tuples) end end. %%------------------------------------------------------------------------------ -spec explain_error(term()) -> string(). explain_error({undefined_dependency, A, B, C}) -> Message = show_undefined_dependency(A, B), io_lib:format( "~s~n" " You can run without '--assume_racing false' to treat them as racing.~n" " ~p~n", [Message, C]). show_undefined_dependency(A, B) -> io_lib:format( "The following pair of instructions is not explicitly marked as non-racing" " in Concuerror's internals:~n" " 1) ~s~n 2) ~s~n" " Please notify the developers to add info about this pair.", [concuerror_io_lib:pretty_s(#event{event_info = I}, 10) || I <- [A,B]]). ================================================ FILE: src/concuerror_estimator.erl ================================================ %%% @private %%% @doc %%% The estimator process is being updated by the scheduler and polled %%% independently by the logger. It stores a lightweight %%% representation/summarry of the exploration tree and uses it to %%% give an estimation of the total size. -module(concuerror_estimator). -behaviour(gen_server). %% API -export([start_link/1, stop/1, restart/2, plan/2, get_estimation/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %%------------------------------------------------------------------------------ -export_type([estimator/0, estimation/0]). -type estimator() :: pid() | 'none'. -type estimation() :: pos_integer() | 'unknown'. -type average() :: concuerror_window_average:average(). %%------------------------------------------------------------------------------ -define(SERVER, ?MODULE). %%------------------------------------------------------------------------------ -include("concuerror.hrl"). -type explored() :: dict:dict(index(), [pos_integer()]). -type planned() :: dict:dict(index(), pos_integer()). %%------------------------------------------------------------------------------ -define(INITIAL_DELAY, 500). -define(DELAY, 200). -record(state, { average = initial :: 'initial' | average(), delay = ?INITIAL_DELAY :: non_neg_integer(), estimation = unknown :: estimation(), explored = dict:new() :: explored(), planned = dict:new() :: planned(), style :: estimation_style() }). -type state() :: #state{}. %%%============================================================================= %%% API %%%============================================================================= -type call() :: 'get_estimation'. -type cast() :: {'restart' | 'plan', index()}. %%%============================================================================= -spec start_link(concuerror_options:options()) -> estimator(). start_link(Options) -> case estimation_style(Options) of unknown -> none; Other -> {ok, Pid} = gen_server:start_link(?MODULE, Other, []), Pid end. %%------------------------------------------------------------------------------ -record(delay_bounded, { bound = 0 :: pos_integer(), races_avg = init_average(4, 20) :: average() }). -type estimation_style() :: {'hard_bound', pos_integer(), estimation_style()} | {'recursive', 'one_step' | 'tree'} | #delay_bounded{} | 'unknown'. -spec estimation_style(concuerror_options:options()) -> estimation_style(). estimation_style(Options) -> Verbosity = ?opt(verbosity, Options), case concuerror_logger:showing_progress(Verbosity) of false -> unknown; true -> Style = case ?opt(scheduling_bound_type, Options) of delay -> Bound = ?opt(scheduling_bound, Options), #delay_bounded{bound = Bound}; none -> case ?opt(dpor, Options) =:= optimal of false -> {recursive, one_step}; true -> {recursive, tree} end; _ -> unknown end, case ?opt(interleaving_bound, Options) of IBound when is_number(IBound), Style =/= unknown -> {hard_bound, IBound, Style}; _ -> Style end end. %%------------------------------------------------------------------------------ -spec stop(estimator()) -> 'ok'. stop(none) -> ok; stop(Estimator) -> gen_server_stop(Estimator). gen_server_stop(Server) -> gen_server:stop(Server). %%------------------------------------------------------------------------------ -spec restart(estimator(), index()) -> 'ok'. restart(none, _Index) -> ok; restart(Estimator, Index) -> %% io:format("Restart: ~p~n", [Index]), gen_server:cast(Estimator, {restart, Index}). %%------------------------------------------------------------------------------ -spec plan(estimator(), index()) -> 'ok'. plan(none, _Index) -> ok; plan(Estimator, Index) -> %% io:format("Plan: ~p~n", [Index]), gen_server:cast(Estimator, {plan, Index}). %%------------------------------------------------------------------------------ -spec get_estimation(estimator()) -> estimation(). get_estimation(none) -> unknown; get_estimation(Estimator) -> gen_server:call(Estimator, get_estimation). %%%============================================================================= %%% gen_server callbacks %%%============================================================================= -spec init(estimation_style()) -> {'ok', state()}. init(Style) -> {ok, #state{style = Style}}. %%------------------------------------------------------------------------------ -spec handle_call(call(), _From, state()) -> {'reply', term(), state()}. handle_call(get_estimation, _From, #state{estimation = Estimation} = State) -> {reply, Estimation, State}. %%------------------------------------------------------------------------------ -spec handle_cast(cast(), state()) -> {'noreply', state()}. handle_cast(stop, State) -> {stop, normal, State}; handle_cast({plan, I}, State) -> #state{planned = Planned} = State, NewPlanned = dict:update_counter(I, 1, Planned), {noreply, State#state{planned = NewPlanned}}; handle_cast({restart, I}, State) -> #state{explored = Explored, planned = Planned} = State, SmallerFun = fun(K) -> K =< I end, NewPlanned = case dict:find(I, Planned) of {ok, Value} when Value > 0 -> dict:update_counter(I, -1, Planned); _ -> CleanupFun = fun(_, V) -> V > 0 end, CleanPlanned = dict:filter(CleanupFun, Planned), AllPlanned = lists:sort(dict:fetch_keys(CleanPlanned)), [NI|_] = lists:reverse(lists:takewhile(SmallerFun, AllPlanned)), %% io:format("Miss! Hit @ ~p~n", [NI]), dict:update_counter(NI, -1, Planned) end, FoldFun = fun(M, {Total, E}) -> Sum = lists:sum(dict:fetch(M, E)), NE = dict:erase(M, E), {Total + Sum, NE} end, Marks = ordsets:from_list(dict:fetch_keys(Explored)), Larger = lists:dropwhile(SmallerFun, Marks), {Sum, OutExplored} = lists:foldl(FoldFun, {1, Explored}, Larger), NewExplored = dict:append(I, Sum, OutExplored), NewState = State#state{explored = NewExplored, planned = NewPlanned}, FinalState = reestimate(NewState), {noreply, FinalState}. %%------------------------------------------------------------------------------ -spec handle_info(term(), state()) -> {noreply, state()}. handle_info(_Info, State) -> {noreply, State}. %%------------------------------------------------------------------------------ -spec terminate('normal', state()) -> 'ok'. terminate(normal, _State) -> ok. %%------------------------------------------------------------------------------ -spec code_change(term(), state(), term()) -> {'ok', state()}. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%============================================================================= %%% Internal functions %%%============================================================================= init_average(Value, Window) -> concuerror_window_average:init(Value, Window). update_average(Value, Average) -> concuerror_window_average:update(Value, Average). reestimate(#state{average = Average, delay = Delay} = State) -> case Delay > 0 of true -> State#state{delay = Delay - 1}; false -> {Value, NewState} = estimate(State), {Estimation, NewAverage} = case Average =:= initial of false -> update_average(Value, Average); true -> {Value, init_average(Value, 10)} end, NewState#state{ average = NewAverage, delay = ?DELAY, estimation = round(Estimation) } end. all_keys(Explored, Planned) -> [ExploredKeys, PlannedKeys] = [ordsets:from_list(dict:fetch_keys(D)) || D <- [Explored, Planned]], ordsets:union(ExploredKeys, PlannedKeys). estimate(#state{style = {hard_bound, Bound, Style}} = State) -> {Est, NewState} = estimate(State#state{style = Style}), NewStyle = NewState#state.style, {min(Est, Bound), NewState#state{style = {hard_bound, Bound, NewStyle}}}; estimate(State) -> #state{ explored = Explored, planned = RawPlanned, style = Style } = State, CleanupFun = fun(_, V) -> V > 0 end, Planned = dict:filter(CleanupFun, RawPlanned), NewState = State#state{planned = Planned}, case Style of {recursive, Subtree} -> Marks = all_keys(Explored, Planned), FoldFun = fun(M, L) -> AllExplored = case dict:find(M, Explored) of error -> [L]; {ok, More} -> [L|More] end, Sum = lists:sum(AllExplored), AllPlanned = case dict:find(M, Planned) of error -> 0; {ok, P} -> case Subtree of one_step -> %% Each one-step plan will explore a similar tree P * Sum / length(AllExplored); tree -> %% Each plan is a single planned execution so %% plans are the size of the tree and the %% estimation is an average between everything %% we so far know (already explored plus this %% planned tree). (Sum + P) / (length(AllExplored) + 1) end end, Sum + AllPlanned end, AllButLast = lists:reverse(Marks), {round(lists:foldl(FoldFun, 1, AllButLast)), NewState}; #delay_bounded{ bound = Bound, races_avg = RacesAvg } -> MoreThanOne = fun(_, V) -> V > 1 end, SignificantPlanned = dict:filter(MoreThanOne, Planned), Marks = all_keys(Explored, SignificantPlanned), Length = length(Marks), {Races, NewRacesAvg} = update_average(Length, RacesAvg), Est = bounded_estimation(Races, Bound), NewStyle = Style#delay_bounded{races_avg = NewRacesAvg}, {round(Est), NewState#state{style = NewStyle}} end. bounded_estimation(Races, Bound) -> bounded_estimation(Races, Bound, 1). bounded_estimation(_Races, 0, Acc) -> Acc; bounded_estimation(Races, N , Acc) -> %% XXX: Think more about this... bounded_estimation(Races, N - 1, 1 + Races * Acc). ================================================ FILE: src/concuerror_inspect.erl ================================================ %%% @private %%% @doc %%% The instrumenter replaces interesting operations with calls %%% to inspect/3 -module(concuerror_inspect). %% Interface to instrumented code: -export([start_inspection/1, stop_inspection/0, inspect/3, explain_error/1]). -export_type([instrumented_tag/0]). %%------------------------------------------------------------------------------ -include("concuerror.hrl"). %%------------------------------------------------------------------------------ -type instrumented_tag() :: 'apply' | 'call' | 'receive'. %%------------------------------------------------------------------------------ -spec start_inspection(term()) -> 'ok'. start_inspection(Info) -> NewDict = erase(), put(concuerror_info, {under_concuerror, Info, NewDict}), ok. -spec stop_inspection() -> 'false' | {'true', term()}. stop_inspection() -> case get(concuerror_info) of {under_concuerror, Info, Dict} -> erase(concuerror_info), _ = [put(K, V) || {K, V} <- Dict], {true, Info}; _ -> false end. %% Function inspect/3 should: %% - return the result of a call, if it is called from a non-Concuerror process %% - grab concuerror_info and continue to concuerror_callback, otherwise -spec inspect(Tag :: instrumented_tag(), Args :: [term()], Location :: term()) -> Return :: term(). inspect(Tag, Args, Location) -> Ret = case stop_inspection() of false -> doit; {true, Info} -> {R, NewInfo} = concuerror_callback:instrumented(Tag, Args, Location, Info), start_inspection(NewInfo), R end, case Ret of doit -> case {Tag, Args} of {apply, [Fun, ApplyArgs]} -> erlang:apply(Fun, ApplyArgs); {call, [Module, Name, CallArgs]} -> erlang:apply(Module, Name, CallArgs); {'receive', [_, Timeout]} -> Timeout end; {didit, Res} -> Res; {error, Reason} -> StackTop = case {Tag, Args} of {apply, Args} -> {erlang, apply, Args, Location}; {call, [Module, Name, CallArgs]} -> {Module, Name, CallArgs, Location} end, erlang:raise(error, Reason, [StackTop|get_stacktrace()]); {skip_timeout, CreateMessage} -> assert_no_messages(), case CreateMessage of false -> ok; {true, D} -> self() ! D end, 0 end. assert_no_messages() -> receive Msg -> exit(self(), {?MODULE, {pending_message, self(), Msg}}) after 0 -> ok end. get_stacktrace() -> {_, Trace} = erlang:process_info(self(), current_stacktrace), [T || T <- Trace, not_concuerror_module(element(1, T))]. not_concuerror_module(Atom) -> case atom_to_list(Atom) of "concuerror" ++ _ -> false; _ -> true end. -spec explain_error(term()) -> string(). explain_error({pending_message, Proc, Msg}) -> io_lib:format( "A process (~w) had a message (~w) in it's mailbox when it" " shouldn't." ++ ?notify_us_msg, [Proc, Msg]). ================================================ FILE: src/concuerror_instrumenter.erl ================================================ %%% @private -module(concuerror_instrumenter). -export([instrument/3]). -define(inspect, concuerror_inspect). -define(flag(A), (1 bsl A)). -define(input, ?flag(1)). -define(output, ?flag(2)). -define(ACTIVE_FLAGS, [?input, ?output]). %% -define(DEBUG_FLAGS, lists:foldl(fun erlang:'bor'/2, 0, ?ACTIVE_FLAGS)). -include("concuerror.hrl"). -spec instrument(module(), erl_syntax:forms(), concuerror_loader:instrumented()) -> {erl_syntax:forms(), [iodata()]}. instrument(?inspect, AbstractCode, _Instrumented) -> %% The inspect module should never be instrumented. {AbstractCode, []}; instrument(Module, AbstractCode, Instrumented) -> ?if_debug(Stripper = fun(Node) -> erl_syntax:set_ann(Node, []) end), ?debug_flag(?input, "~s~n", [[erl_prettypr:format(erl_syntax_lib:map(Stripper, A)) || A <- AbstractCode]]), true = ets:insert(Instrumented, {{current}, Module}), Acc = #{ file => "" , instrumented => Instrumented , warnings => [] }, {Is, #{warnings := Warnings}} = fold(AbstractCode, Acc, []), true = ets:delete(Instrumented, {current}), ?debug_flag(?output, "~s~n", [[erl_prettypr:format(erl_syntax_lib:map(Stripper, I)) || I <- Is]]), {Is, warn_to_string(Module, lists:usort(Warnings))}. %% Replace with form_list please. fold([], Arg, Acc) -> {erl_syntax:revert_forms(lists:reverse(Acc)), Arg}; fold([H|T], Arg, Acc) -> ArgIn = Arg#{var => erl_syntax_lib:variables(H)}, {R, NewArg} = erl_syntax_lib:mapfold(fun mapfold/2, ArgIn, H), fold(T, NewArg, [R|Acc]). mapfold(Node, Acc) -> #{ file := File , instrumented := Instrumented , warnings := Warnings , var := Var } = Acc, Type = erl_syntax:type(Node), NewNodeAndMaybeWarn = case Type of application -> Args = erl_syntax:application_arguments(Node), LArgs = erl_syntax:list(Args), Op = erl_syntax:application_operator(Node), OpType = erl_syntax:type(Op), case OpType of module_qualifier -> Module = erl_syntax:module_qualifier_argument(Op), Name = erl_syntax:module_qualifier_body(Op), case is_safe(Module, Name, length(Args), Instrumented) of has_load_nif -> {newwarn, Node, has_load_nif}; true -> Node; false -> inspect(call, [Module, Name, LArgs], Node, Acc) end; atom -> Node; _ -> inspect(apply, [Op, LArgs], Node, Acc) end; infix_expr -> Op = erl_syntax:infix_expr_operator(Node), COp = erl_syntax:operator_name(Op), case COp of '!' -> Left = erl_syntax:infix_expr_left(Node), Right = erl_syntax:infix_expr_right(Node), Args = erl_syntax:list([Left, Right]), inspect(call, [abstr(erlang), abstr('!'), Args], Node, Acc); _ -> Node end; receive_expr -> Fun = receive_matching_fun(Node), Timeout = erl_syntax:receive_expr_timeout(Node), TArg = case Timeout =:= none of true -> abstr(infinity); false -> Timeout end, Call = inspect('receive', [Fun, TArg], Node, Acc), case Timeout =:= none of true -> %% Leave receives without after clauses unaffected, so %% that the compiler can expose matched patterns to the %% rest of the program erl_syntax:block_expr([Call, Node]); false -> %% Otherwise, replace original timeout with a fresh %% variable to make the after clause immediately reachable %% when needed. Clauses = erl_syntax:receive_expr_clauses(Node), Action = erl_syntax:receive_expr_action(Node), TimeoutVar = erl_syntax:variable(erl_syntax_lib:new_variable_name(Var)), Match = erl_syntax:match_expr(TimeoutVar, Call), RecNode = erl_syntax:receive_expr(Clauses, TimeoutVar, Action), Block = erl_syntax:block_expr([Match, RecNode]), {newvar, Block, TimeoutVar} end; _ -> Node end, {NewNode, NewVar, NewWarnings} = case NewNodeAndMaybeWarn of {newwarn, NN, W} -> {NN, Var, [W|Warnings]}; {newvar, NN, V} -> {NN, sets:add_element(V, Var), Warnings}; _ -> {NewNodeAndMaybeWarn, Var, Warnings} end, NewFile = case Type of attribute -> case erl_syntax_lib:analyze_attribute(Node) of {file, {NF, _}} -> NF; _ -> File end; _ -> File end, NewAcc = Acc #{ file => NewFile , warnings => NewWarnings , var => NewVar }, {NewNode, NewAcc}. inspect(Tag, Args, Node, Acc) -> #{ file := File} = Acc, Pos = erl_syntax:get_pos(Node), PosInfo = [Pos, {file, File}], CTag = abstr(Tag), CArgs = erl_syntax:list(Args), App = erl_syntax:application( abstr(?inspect) , abstr(inspect) , [ CTag , CArgs , abstr(PosInfo)]), erl_syntax:copy_attrs(Node, App). receive_matching_fun(Node) -> Clauses = erl_syntax:receive_expr_clauses(Node), NewClauses = extract_patterns(Clauses), %% We need a case in a fun to avoid shadowing %% i.e. if the receive uses a bound var in a clause and we insert it %% bare as a clause into a new fun it will shadow the original %% and change the code's meaning Var = erl_syntax:variable('__Concuerror42'), NewCase = erl_syntax:case_expr(Var, NewClauses), erl_syntax:fun_expr([erl_syntax:clause([Var], abstr(true), [NewCase])]). extract_patterns(Clauses) -> extract_patterns(Clauses, []). extract_patterns([], Acc) -> Pat = [erl_syntax:underscore()], Guard = abstr(true), Body = [abstr(false)], lists:reverse([erl_syntax:clause(Pat, Guard, Body)|Acc]); extract_patterns([Node|Rest], Acc) -> Body = [abstr(true)], Pats = erl_syntax:clause_patterns(Node), Guard = erl_syntax:clause_guard(Node), NClause = erl_syntax:clause(Pats, Guard, Body), extract_patterns(Rest, [erl_syntax:copy_attrs(Node, NClause)|Acc]). is_safe(Module, Name, Arity, Instrumented) -> case erl_syntax:is_literal(Module) andalso erl_syntax:is_literal(Name) of false -> false; true -> NameLit = concr(Name), ModuleLit = concr(Module), case {ModuleLit, NameLit, Arity} of %% erlang:apply/3 is safe only when called inside of erlang.erl {erlang, apply, 3} -> ets:lookup_element(Instrumented, {current}, 2) =:= erlang; {erlang, load_nif, 2} -> has_load_nif; _ -> case erlang:is_builtin(ModuleLit, NameLit, Arity) of true -> not concuerror_callback:is_unsafe({ModuleLit, NameLit, Arity}); false -> ets:lookup(Instrumented, ModuleLit) =/= [] end end end. abstr(Term) -> erl_syntax:abstract(Term). concr(Tree) -> erl_syntax:concrete(Tree). warn_to_string(Module, Tags) -> [io_lib:format("Module ~w ~s", [Module, tag_to_warn(T)]) || T <- Tags]. %%------------------------------------------------------------------------------ tag_to_warn(has_load_nif) -> "contains a call to erlang:load_nif/2." " Concuerror cannot reliably execute operations that are implemented as" " NIFs." " Moreover, Concuerror cannot even detect if a NIF is used by the test." " If your test uses NIFs, you may see error messages of the form" " 'replaying a built-in returned a different result than expected'." " If your test does not use NIFs you have nothing to worry about.". ================================================ FILE: src/concuerror_io_lib.erl ================================================ %%% @private -module(concuerror_io_lib). -export([error_s/2, pretty/3, pretty_s/2]). -include("concuerror.hrl"). -spec error_s(concuerror_scheduler:interleaving_error(), pos_integer()) -> string(). error_s(fatal, _Depth) -> io_lib:format("* Concuerror crashed~n", []); error_s({Type, Info}, Depth) -> case Type of abnormal_halt -> {Step, P, Status} = Info, S1 = io_lib:format( "* At step ~w process ~p called halt with an abnormal status~n", [Step, P]), S2 = io_lib:format( " Status:~n" " ~P~n", [Status, Depth]), [S1, S2]; abnormal_exit -> {Step, P, Reason, Stacktrace} = Info, S1 = io_lib:format( "* At step ~w process ~p exited abnormally~n", [Step, P]), S2 = io_lib:format( " Reason:~n" " ~P~n", [Reason, Depth]), S3 = io_lib:format( " Stacktrace:~n" " ~p~n", [Stacktrace]), [S1, S2, S3]; deadlock -> InfoStr = [io_lib:format( " ~p ~s~n" " Mailbox contents: ~p~n", [P, location(F, L), Msgs]) || {P, [L, {file, F}], Msgs} <- Info], Format = "* Blocked at a 'receive' (\"deadlocked\";" " other processes have exited):~n~s", io_lib:format(Format, [InfoStr]); depth_bound -> io_lib:format("* Reached the depth bound of ~p events~n", [Info]) end. -spec pretty('disable' | io:device(), event(), pos_integer()) -> ok. pretty(disable, _, _) -> ok; pretty(Output, I, Depth) -> Fun = fun(P, A) -> Msg = io_lib:format(P ++ "~n", A), io:format(Output, "~s", [Msg]) end, _ = pretty_aux(I, {Fun, []}, Depth), ok. -type indexed_event() :: {index(), event()}. -spec pretty_s(event() | indexed_event() | [indexed_event()], pos_integer()) -> [string()]. pretty_s(Events, Depth) -> {_, Acc} = pretty_aux(Events, {fun io_lib:format/2, []}, Depth), lists:reverse(Acc). pretty_aux({I, #event{} = Event}, {F, Acc}, Depth) -> #event{ actor = Actor, event_info = EventInfo, location = Location } = Event, TraceString = case I =/= 0 of true -> io_lib:format("~4w: ", [I]); false -> "" end, ActorString = case Actor of P when is_pid(P) -> io_lib:format("~p: ", [P]); _ -> "" end, EventString = pretty_info(EventInfo, Depth), LocationString = case Location of [Line, {file, File}] -> io_lib:format("~n ~s", [location(File, Line)]); exit -> case EventInfo of #exit_event{} -> ""; _Other -> io_lib:format("~n (while exiting)", []) end; _ -> "" end, R = F("~s~s~s~s", [TraceString, ActorString, EventString, LocationString]), {F, [R|Acc]}; pretty_aux(#event{} = Event, FAcc, Depth) -> pretty_aux({0, Event}, FAcc, Depth); pretty_aux(List, FAcc, Depth) when is_list(List) -> Fun = fun(Event, Acc) -> pretty_aux(Event, Acc, Depth) end, lists:foldl(Fun, FAcc, List). pretty_info(#builtin_event{mfargs = {erlang, send, [To, Msg]}, extra = Ref}, Depth) when is_reference(Ref) -> io_lib:format("expires, delivering ~W to ~W", [Msg, Depth, To, Depth]); pretty_info(#builtin_event{mfargs = {erlang, '!', [To, Msg]}, status = {crashed, Reason}}, Depth) -> io_lib:format("Exception ~W is raised by: ~W ! ~W", [Reason, Depth, To, Depth, Msg, Depth]); pretty_info(#builtin_event{mfargs = {M, F, Args}, status = {crashed, Reason}}, Depth) -> ArgString = pretty_arg(Args, Depth), io_lib:format("Exception ~W is raised by: ~p:~p(~s)", [Reason, Depth, M, F, ArgString]); pretty_info(#builtin_event{mfargs = {erlang, '!', [To, Msg]}, result = Result}, Depth) -> io_lib:format("~W = ~w ! ~W", [Result, Depth, To, Msg, Depth]); pretty_info(#builtin_event{mfargs = {M, F, Args}, result = Result}, Depth) -> ArgString = pretty_arg(Args, Depth), io_lib:format("~W = ~p:~p(~s)", [Result, Depth, M, F, ArgString]); pretty_info(#exit_event{actor = Timer}, _Depth) when is_reference(Timer) -> "is removed"; pretty_info(#exit_event{reason = Reason}, Depth) -> ReasonStr = case Reason =:= normal of true -> "normally"; false -> io_lib:format("abnormally (~W)", [Reason, Depth]) end, io_lib:format("exits ~s", [ReasonStr]); pretty_info(#message_event{} = MessageEvent, Depth) -> #message_event{ message = #message{data = Data}, recipient = Recipient, sender = Sender, type = Type } = MessageEvent, MsgString = case Type of message -> io_lib:format("Message (~W)", [Data, Depth]); exit_signal -> Reason = case Data of {'EXIT', Sender, R} -> R; kill -> kill end, io_lib:format("Exit signal (~W)", [Reason, Depth]) end, io_lib:format("~s from ~p reaches ~p", [MsgString, Sender, Recipient]); pretty_info(#receive_event{message = Message, timeout = Timeout}, Depth) -> case Message of 'after' -> io_lib:format("receive timeout expires after ~p ms", [Timeout]); #message{data = Data} -> io_lib:format("receives message (~W)", [Data, Depth]) end. pretty_arg(Args, Depth) -> pretty_arg(lists:reverse(Args), "", Depth). pretty_arg([], Acc, _Depth) -> Acc; pretty_arg([Arg|Args], "", Depth) -> pretty_arg(Args, io_lib:format("~W", [Arg, Depth]), Depth); pretty_arg([Arg|Args], Acc, Depth) -> pretty_arg(Args, io_lib:format("~W, ", [Arg, Depth]) ++ Acc, Depth). location(F, L) -> Basename = filename:basename(F), io_lib:format("in ~s line ~w", [Basename, L]). ================================================ FILE: src/concuerror_loader.erl ================================================ %%% @private -module(concuerror_loader). -export([initialize/1, load/1, load_initially/1, is_instrumenting/0]). %%------------------------------------------------------------------------------ -export_type([instrumented/0]). -type instrumented() :: 'concuerror_instrumented'. %%------------------------------------------------------------------------------ -include("concuerror.hrl"). %%------------------------------------------------------------------------------ -spec initialize([atom()]) -> 'ok' | {'error', string()}. initialize(Excluded) -> Instrumented = get_instrumented_table(), case ets:info(Instrumented, name) =:= undefined of true -> setup_sticky_directories(), Instrumented = ets:new(Instrumented, [named_table, public]), ok; false -> ets:match_delete(Instrumented, {'_', concuerror_excluded}), ok end, Entries = [{X, concuerror_excluded} || X <- Excluded], try true = ets:insert_new(Instrumented, Entries), ok catch _:_ -> Error = "Excluded modules have already been instrumented. Restart the shell.", {error, Error} end. setup_sticky_directories() -> {module, concuerror_inspect} = code:ensure_loaded(concuerror_inspect), _ = [true = code:unstick_mod(M) || {M, preloaded} <- code:all_loaded()], [] = [D || D <- code:get_path(), ok =/= code:unstick_dir(D)], case code:get_object_code(erlang) =:= error of true -> true = code:add_pathz(filename:join(code:root_dir(), "erts/preloaded/ebin")); false -> ok end. %%------------------------------------------------------------------------------ -spec load(module()) -> {'ok', iodata()} | 'already_done' | 'fail'. load(Module) -> Instrumented = get_instrumented_table(), load(Module, Instrumented). load(Module, Instrumented) -> case ets:lookup(Instrumented, Module) =:= [] of true -> set_is_instrumenting({true, Module}), {Beam, Filename} = case code:which(Module) of preloaded -> {Module, BeamBinary, F} = code:get_object_code(Module), {BeamBinary, F}; F -> {F, F} end, try {ok, Warnings} = load_binary(Module, Filename, Beam, Instrumented), set_is_instrumenting(false), {ok, Warnings} catch _:_ -> fail end; false -> already_done end. %%------------------------------------------------------------------------------ -spec load_initially(module()) -> {ok, module(), [string()]} | {error, string()}. load_initially(Module) -> Instrumented = get_instrumented_table(), load_initially(Module, Instrumented). load_initially(File, Instrumented) -> MaybeModule = case filename:extension(File) of ".erl" -> case compile:file(File, [binary, debug_info, report_errors]) of error -> Format = "could not compile ~s (try to add the .beam file instead)", {error, io_lib:format(Format, [File])}; Else -> Else end; ".beam" -> case beam_lib:chunks(File, []) of {ok, {M, []}} -> {ok, M, File}; Else -> {error, beam_lib:format_error(Else)} end; _Other -> {error, io_lib:format("~s is not a .erl or .beam file", [File])} end, case MaybeModule of {ok, Module, Binary} -> Warnings = check_shadow(File, Module), {ok, MoreWarnings} = load_binary(Module, File, Binary, Instrumented), {ok, Module, Warnings ++ MoreWarnings}; Error -> Error end. %%------------------------------------------------------------------------------ -spec is_instrumenting() -> {'true', module()} | 'false'. is_instrumenting() -> Instrumented = get_instrumented_table(), [{_, V}] = ets:lookup(Instrumented, {is_instrumenting}), V. -spec set_is_instrumenting( {'true', module()} | 'false') -> 'ok'. set_is_instrumenting(Value) -> Instrumented = get_instrumented_table(), ets:insert(Instrumented, {{is_instrumenting}, Value}), ok. %%------------------------------------------------------------------------------ get_instrumented_table() -> concuerror_instrumented. check_shadow(File, Module) -> Default = code:which(Module), case Default =:= non_existing of true -> []; false -> [io_lib:format("File ~s shadows ~s (found in path)", [File, Default])] end. load_binary(Module, Filename, Beam, Instrumented) -> Abstract = get_abstract(Beam), {InstrumentedAbstract, Warnings} = case ets:lookup(Instrumented, Module) =:= [] of true -> ets:insert(Instrumented, {Module, concuerror_instrumented}), concuerror_instrumenter:instrument(Module, Abstract, Instrumented); false -> {Abstract, []} end, %% io:format("~p~n~p~n", [Abstract, InstrumentedAbstract]), %% exit(1), {ok, _, NewBinary} = compile:forms(InstrumentedAbstract, [report_errors, binary]), {module, Module} = code:load_binary(Module, Filename, NewBinary), {ok, Warnings}. get_abstract(Beam) -> {ok, {Module, [{abstract_code, ChunkInfo}]}} = beam_lib:chunks(Beam, [abstract_code]), case ChunkInfo of {_, Chunk} -> {ok, _, Abs} = compile:forms(Chunk, [binary, to_exp]), Abs; no_abstract_code -> {ok, {Module, [{compile_info, CompileInfo}]}} = beam_lib:chunks(Beam, [compile_info]), {source, File} = proplists:lookup(source, CompileInfo), {options, CompileOptions} = proplists:lookup(options, CompileInfo), Filter = fun(Option) -> lists:member(element(1, Option), [d, i, parse_transform]) end, CleanOptions = lists:filter(Filter, CompileOptions), Options = [debug_info, report_errors, binary, to_exp|CleanOptions], {ok, _, Abstract} = compile:file(File, Options), Abstract end. ================================================ FILE: src/concuerror_logger.erl ================================================ %%% @private %%% @doc %%% The logger is a process responsible for collecting information and %%% sending output to the user in reports and stderr. -module(concuerror_logger). -export([start/1, complete/2, plan/1, log/5, race/3, stop/2, print/3, time/2]). -export([bound_reached/1, set_verbosity/2]). -export([graph_set_node/3, graph_new_node/4, graph_race/3]). -export([print_log_message/3]). -export([showing_progress/1, progress_help/0]). -export_type([logger/0, log_level/0]). %%------------------------------------------------------------------------------ -include("concuerror.hrl"). -type logger() :: pid(). -type log_level() :: 0..7. -define(TICKER_TIMEOUT, 500). -define(llog(L, F, A), ?log(self(), L, F, A)). -define(llog(L, F), ?llog(L, F, [])). %%------------------------------------------------------------------------------ -type unique_id() :: concuerror_scheduler:unique_id(). -type stream() :: 'standard_io' | 'standard_error' | 'race' | file:filename(). -type graph_data() :: { file:io_device() , unique_id() | 'init' , unique_id() | 'none' }. %%------------------------------------------------------------------------------ -type unique_ids() :: sets:set(integer()). %%------------------------------------------------------------------------------ -type timestamp() :: integer(). timestamp() -> erlang:monotonic_time(milli_seconds). timediff(After, Before) -> (After - Before) / 1000. %%------------------------------------------------------------------------------ -record(rate_info, { average :: 'init' | concuerror_window_average:average(), prev :: non_neg_integer(), timestamp :: timestamp() }). -record(logger_state, { already_emitted = sets:new() :: unique_ids(), bound_reached = false :: boolean(), emit_logger_tips = initial :: 'initial' | 'false', errors = 0 :: non_neg_integer(), estimator :: concuerror_estimator:estimator(), graph_data :: graph_data() | 'disable', interleaving_bound :: concuerror_options:bound(), last_had_output = false :: boolean(), log_all :: boolean(), log_msgs = [] :: [string()], output :: file:io_device() | 'disable', output_name :: string(), print_depth :: pos_integer(), rate_info = init_rate_info() :: #rate_info{}, streams = [] :: [{stream(), [string()]}], timestamp = timestamp() :: timestamp(), ticker = none :: pid() | 'none', ticks = 0 :: non_neg_integer(), traces_explored = 0 :: non_neg_integer(), traces_ssb = 0 :: non_neg_integer(), traces_total = 0 :: non_neg_integer(), verbosity :: log_level() }). %%------------------------------------------------------------------------------ -spec start(concuerror_options:options()) -> pid(). start(Options) -> Parent = self(), Ref = make_ref(), Fun = fun() -> State = initialize(Options), Parent ! Ref, loop(State) end, P = spawn_link(Fun), receive Ref -> P end. initialize(Options) -> Timestamp = format_utc_timestamp(), Graph = ?opt(graph, Options), {Output, OutputName} = ?opt(output, Options), LogAll = ?opt(log_all, Options), Processes = ?opt(processes, Options), SymbolicNames = ?opt(symbolic_names, Options), Verbosity = ?opt(verbosity, Options), GraphData = graph_preamble(Graph), Header = io_lib:format("~s started at ~s~n", [concuerror:version(), Timestamp]), Ticker = case showing_progress(Verbosity) of false -> none; true -> to_stderr("~s~n", [Header]), initialize_ticker(), ?llog(?linfo, "Showing progress ('-h progress', for details)~n"), Self = self(), spawn_link(fun() -> ticker(Self) end) end, case Output =:= disable of true -> ?llog(?lwarning, "No output report will be generated~n"); false -> ?llog(?linfo, "Writing results in ~s~n", [OutputName]) end, case GraphData =:= disable of true -> ok; false -> {_, GraphName} = Graph, ?llog(?linfo, "Writing graph in ~s~n", [GraphName]) end, case LogAll of true -> ?llog(?lwarning, "Logging all interleavings ('--log_all true')~n"); false -> ?llog(?linfo, "Only logging errors ('--log_all false')~n") end, PrintableOptions = delete_props( [estimator, graph, output, processes, timers, verbosity], Options), to_file(Output, "~s", [Header]), to_file( Output, " Options:~n" " ~p~n", [lists:sort(PrintableOptions)]), ?autoload_and_log(io_lib, self()), ok = setup_symbolic_names(SymbolicNames, Processes), #logger_state{ estimator = ?opt(estimator, Options), graph_data = GraphData, interleaving_bound = ?opt(interleaving_bound, Options), log_all = LogAll, output = Output, output_name = OutputName, print_depth = ?opt(print_depth, Options), ticker = Ticker, verbosity = Verbosity }. delete_props([], Proplist) -> Proplist; delete_props([Key|Rest], Proplist) -> delete_props(Rest, proplists:delete(Key, Proplist)). -spec bound_reached(logger()) -> ok. bound_reached(Logger) -> Logger ! bound_reached, ok. -spec plan(logger()) -> ok. plan(Logger) -> Logger ! plan, ok. -spec complete(logger(), concuerror_scheduler:interleaving_result()) -> ok. complete(Logger, Warnings) -> Ref = make_ref(), Logger ! {complete, Warnings, self(), Ref}, receive Ref -> ok end. -spec log(logger(), log_level(), term(), string(), [term()]) -> ok. log(Logger, Level, Tag, Format, Data) -> Logger ! {log, Level, Tag, Format, Data}, ok. -spec stop(logger(), term()) -> concuerror:analysis_result(). stop(Logger, Status) -> Logger ! {stop, Status, self()}, receive {stopped, ExitStatus} -> ExitStatus end. -spec print(logger(), stream(), string()) -> ok. print(Logger, Type, String) -> Logger ! {print, Type, String}, ok. -spec time(logger(), term()) -> ok. time(Logger, Tag) -> Logger ! {time, Tag}, ok. -spec race(logger(), {index(), event()}, {index(), event()}) -> ok. race(Logger, EarlyEvent, Event) -> Logger ! {race, EarlyEvent, Event}, ok. -spec set_verbosity(logger(), log_level()) -> ok. set_verbosity(Logger, Verbosity) -> Logger ! {set_verbosity, Verbosity}, ok. -spec print_log_message(log_level(), string(), [term()]) -> ok. print_log_message(Level, Format, Args) -> LevelFormat = level_to_tag(Level), NewFormat = "* " ++ LevelFormat ++ Format, to_stderr(NewFormat, Args). -spec showing_progress(log_level()) -> boolean(). showing_progress(Verbosity) -> (Verbosity =/= ?lquiet) andalso (Verbosity < ?ltiming). %%------------------------------------------------------------------------------ loop(State) -> Message = receive {stop, _, _} = Stop -> receive M -> self() ! Stop, M after 0 -> Stop end; M -> M end, loop(Message, State). loop(Message, #logger_state{ emit_logger_tips = initial, errors = Errors, traces_explored = 10, traces_total = TracesTotal } = State) -> case TracesTotal > 250 of true -> ManyMsg = "A lot of events in this test are racing. You can see such pairs" " by using '--show_races' true. You may want to consider reducing some" " parameters in your test (e.g. number of processes or events).~n", ?llog(?ltip, ManyMsg); false -> ok end, case Errors =:= 10 of true -> ErrorsMsg = "Each of the first 10 interleavings explored so far had some error." " This can make later debugging difficult, as the generated report will" " include too much info. Consider refactoring your code, or using the" " appropriate options to filter out irrelevant errors.~n", ?llog(?ltip, ErrorsMsg); false -> ok end, loop(Message, State#logger_state{emit_logger_tips = false}); loop(Message, State) -> #logger_state{ already_emitted = AlreadyEmitted, errors = Errors, last_had_output = LastHadOutput, log_all = LogAll, log_msgs = LogMsgs, output = Output, output_name = OutputName, print_depth = PrintDepth, streams = Streams, timestamp = Timestamp, traces_explored = TracesExplored, traces_ssb = TracesSSB, traces_total = TracesTotal, verbosity = Verbosity } = State, case Message of {time, Tag} -> Now = timestamp(), Diff = timediff(Now, Timestamp), Msg = "~nTimer: +~6.3fs ~s~n", loop( {log, ?ltiming, none, Msg, [Diff, Tag]}, State#logger_state{timestamp = Now}); {race, EarlyEvent, Event} -> print_depth_tip(), Msg = io_lib:format( "~n* ~s~n ~s~n", [concuerror_io_lib:pretty_s(E, PrintDepth) || E <- [EarlyEvent, Event]]), loop({print, race, Msg}, State); {log, Level, Tag, Format, Data} -> {NewLogMsgs, NewAlreadyEmitted} = case Tag =/= ?nonunique andalso sets:is_element(Tag, AlreadyEmitted) of true -> {LogMsgs, AlreadyEmitted}; false -> case Verbosity < Level of true -> ok; false -> LevelFormat = level_to_tag(Level), NewFormat = "* " ++ LevelFormat ++ Format, printout(State, NewFormat, Data) end, NLM = case Level < ?ltiming of true -> orddict:append(Level, {Format, Data}, LogMsgs); false -> LogMsgs end, NAE = case Tag =/= ?nonunique of true -> sets:add_element(Tag, AlreadyEmitted); false -> AlreadyEmitted end, {NLM, NAE} end, loop(State#logger_state{ already_emitted = NewAlreadyEmitted, log_msgs = NewLogMsgs}); {graph, Command} -> loop(graph_command(Command, State)); {stop, SchedulerStatus, Scheduler} -> NewState = stop_ticker(State), separator(Output, $#), to_file(Output, "Exploration completed!~n", []), ExitStatus = case SchedulerStatus =:= normal of true -> case Errors =/= 0 of true -> case Verbosity =:= ?lquiet of true -> ok; false -> Form = "Errors were found! (check ~s)~n", printout(NewState, Form, [OutputName]) end, error; false -> to_file(Output, " No errors found!~n", []), ok end; false -> fail end, separator(Output, $#), print_log_msgs(Output, LogMsgs), FinishTimestamp = format_utc_timestamp(), Format = "Done at ~s (Exit status: ~p)~n Summary: ", Args = [FinishTimestamp, ExitStatus], to_file(Output, Format, Args), IntMsg = final_interleavings_message(NewState), to_file(Output, "~s", [IntMsg]), ok = close_files(NewState), case Verbosity =:= ?lquiet of true -> ok; false -> FinalFormat = Format ++ IntMsg, printout(NewState, FinalFormat, Args) end, Scheduler ! {stopped, ExitStatus}, ok; plan -> NewState = State#logger_state{traces_total = TracesTotal + 1}, loop(NewState); bound_reached -> NewState = State#logger_state{bound_reached = true}, loop(NewState); {print, Type, String} -> NewStreams = orddict:append(Type, String, Streams), NewState = State#logger_state{streams = NewStreams}, loop(NewState); {set_verbosity, NewVerbosity} -> NewState = State#logger_state{verbosity = NewVerbosity}, loop(NewState); {complete, {Warnings, TraceInfo}, Scheduler, Ref} -> %% We may have race information referring to the previous %% interleaving, as race analysis happens after trace logging. RaceInfo = [S || S = {T, _} <- Streams, T =:= race], case RaceInfo =:= [] of true -> ok; false -> case LastHadOutput of true -> ok; false -> %% Add missing header separator(Output, $#), to_file(Output, "Interleaving #~p~n", [TracesExplored]) end, separator(Output, $-), print_streams(RaceInfo, Output) end, case TraceInfo =/= [] of true -> separator(Output, $#), to_file(Output, "Interleaving #~p~n", [TracesExplored + 1]), separator(Output, $-), case Warnings =:= [] of true -> to_file(Output, "No errors found.~n", []), separator(Output, $-); false -> to_file(Output, "Errors found:~n", []), print_depth_tip(), WarnStr = [concuerror_io_lib:error_s(W, PrintDepth) || W <- Warnings], to_file(Output, "~s", [WarnStr]), separator(Output, $-) end, print_streams([S || S = {T, _} <- Streams, T =/= race], Output), to_file(Output, "Event trace:~n", []), concuerror_io_lib:pretty(Output, TraceInfo, PrintDepth); false -> ok end, {NewErrors, NewSSB, GraphFinal, GraphColor} = case Warnings of sleep_set_block -> Msg = "Some interleavings were 'sleep-set blocked' (SSB). This" " is expected, since you are not using '--dpor" " optimal', but indicates wasted effort.~n", ?unique(self(), ?lwarning, Msg, []), {Errors, TracesSSB + 1, "SSB", "yellow"}; [] -> {Errors, TracesSSB, "Ok", "limegreen"}; _ -> ErrorString = case proplists:get_value(fatal, Warnings) of true -> " (Concuerror crashed)"; undefined -> case proplists:get_value(deadlock, Warnings) of undefined -> ""; Deadlocks -> Pids = [element(1, D) || D <- Deadlocks], io_lib:format(" (~p blocked)", [Pids]) end end, {Errors + 1, TracesSSB, "Error" ++ ErrorString, "red"} end, _ = graph_command({status, TracesExplored, GraphFinal, GraphColor}, State), NewState = State#logger_state{ last_had_output = LogAll orelse NewErrors =/= Errors, streams = [], traces_explored = TracesExplored + 1, traces_ssb = NewSSB, errors = NewErrors }, Scheduler ! Ref, loop(NewState); tick -> N = clear_ticks(1), loop(progress_refresh(N, State)) end. format_utc_timestamp() -> TS = os:timestamp(), {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(TS), Mstr = element(Month, {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}), io_lib:format("~2..0w ~s ~4w ~2..0w:~2..0w:~2..0w", [Day, Mstr, Year, Hour, Minute, Second]). printout(#logger_state{ticker = Ticker} = State, Format, Data) when Ticker =/= none -> progress_clear(), to_stderr(Format, Data), progress_print(State); printout(_, Format, Data) -> to_stderr(Format, Data). print_log_msgs(Output, LogMsgs) -> ForeachInner = fun({Format, Data}) -> to_file(Output, "* " ++ Format, Data) end, Foreach = fun({Type, Messages}) -> Header = level_to_string(Type), Suffix = case Type of ?linfo -> ""; _ -> "s" end, to_file(Output, "~s~s:~n", [Header, Suffix]), separator(Output, $-), lists:foreach(ForeachInner, Messages), to_file(Output, "~n", []), separator(Output, $#) end, lists:foreach(Foreach, LogMsgs). level_to_tag(Level) -> Suffix = case Level > ?linfo of true -> ""; false -> ": " end, level_to_string(Level) ++ Suffix. level_to_string(Level) -> case Level of ?lerror -> "Error"; ?lwarning -> "Warning"; ?ltip -> "Tip"; ?linfo -> "Info"; _ -> "" end. %%------------------------------------------------------------------------------ initialize_ticker() -> self() ! tick, progress_initial_padding(). ticker(Logger) -> Logger ! tick, receive {stop, L} -> L ! stopped after ?TICKER_TIMEOUT -> ticker(Logger) end. clear_ticks(N) -> receive tick -> clear_ticks(N + 1) after 0 -> N end. stop_ticker(#logger_state{ticker = Ticker} = State) -> case is_pid(Ticker) of true -> Ticker ! {stop, self()}, progress_clear(), receive stopped -> State#logger_state{ticker = none} end; false -> State end. %%------------------------------------------------------------------------------ separator_string(Char) -> lists:duplicate(80, Char). separator(Output, Char) -> to_file(Output, "~s~n", [separator_string(Char)]). print_streams(Streams, Output) -> Fold = fun(Tag, Buffer, ok) -> print_stream(Tag, Buffer, Output), ok end, orddict:fold(Fold, ok, Streams). print_stream(Tag, Buffer, Output) -> to_file(Output, stream_tag_to_string(Tag), []), to_file(Output, "~s~n", [Buffer]), case Tag =/= race of true -> to_file(Output, "~n", []), separator(Output, $-); false -> ok end. stream_tag_to_string(standard_io) -> "Standard Output:~n"; stream_tag_to_string(standard_error) -> "Standard Error:~n"; stream_tag_to_string(race) -> "New races found:". % ~n is added by buffer %%------------------------------------------------------------------------------ progress_initial_padding() -> Line = progress_line(0), to_stderr("~s~n", [Line]), to_stderr("~s~n", [progress_header(0)]), to_stderr("~s~n", [Line]), to_stderr("~n", []). progress_clear() -> delete_lines(4). progress_refresh(N, #logger_state{ticks = T} = State) -> %% No extra line afterwards to ease printing of 'running logs'. delete_lines(1), {Str, NewState} = progress_content(State#logger_state{ticks = T + N}), to_stderr("~s~n", [Str]), NewState. delete_lines(0) -> ok; delete_lines(N) -> to_stderr("~c[1A~c[2K\r", [27, 27]), delete_lines(N - 1). progress_print(#logger_state{traces_ssb = SSB} = State) -> Line = progress_line(SSB), to_stderr("~s~n", [Line]), to_stderr("~s~n", [progress_header(SSB)]), to_stderr("~s~n", [Line]), {Str, _NewState} = progress_content(State), to_stderr("~s~n", [Str]). -spec progress_help() -> string(). progress_help() -> io_lib:format( "Errors : Schedulings with errors~n" "Explored : Schedulings already explored~n" "SSB (if >0) : Sleep set blocked schedulings (wasted effort)~n" "Planned : Schedulings that will certainly be explored~n" "~~Rate : Average rate of exploration (in schedulings/s)~n" "Elapsed : Time elapsed (actively running)~n" "Est.Total : Estimation of total number of schedulings (see below)~n" "Est.TTC : Estimated time to completion (see below)~n" "~n" "Estimations:~n" "The total number of schedulings is estimated from the shape of the" " exploration tree. It has been observed to be WITHIN ONE ORDER OF" " MAGNITUDE of the actual number, when using default options.~n" "The time to completion is estimated using the estimated remaining" " schedulings (Est.Total - Explored) divided by the current Rate.~n" , []). progress_header(0) -> progress_header_common(""); progress_header(_State) -> progress_header_common(" SSB |"). progress_header_common(SSB) -> "" "Errors |" " Explored |" ++ SSB ++ " Planned |" " ~Rate |" " Elapsed |" " Est.Total |" " Est.TTC". progress_line(SSB) -> L = lists:duplicate(length(progress_header(SSB)), $-), io_lib:format("~s", [L]). progress_content(State) -> #logger_state{ errors = Errors, estimator = Estimator, rate_info = RateInfo, ticks = Ticks, traces_explored = TracesExplored, traces_ssb = TracesSSB, traces_total = TracesTotal } = State, Planned = TracesTotal - TracesExplored, {Rate, NewRateInfo} = update_rate(RateInfo, TracesExplored), Estimation = concuerror_estimator:get_estimation(Estimator), EstimatedTotal = sanitize_estimation(Estimation, TracesTotal), ErrorsStr = case Errors of 0 -> "none"; _ when Errors < 10000 -> add_seps_to_int(Errors); _ -> "> 10k" end, [TracesExploredStr, PlannedStr] = [add_seps_to_int(S) || S <- [TracesExplored, Planned]], SSBStr = case TracesSSB of 0 -> ""; _ when TracesSSB < 100000 -> io_lib:format("~8s |", [add_seps_to_int(TracesSSB)]); _ -> io_lib:format("~8s |", ["> 100k"]) end, RateStr = case Rate of init -> "..."; 0 -> "<1/s"; _ -> io_lib:format("~w/s", [Rate]) end, EstimatedTotalStr = case EstimatedTotal of unknown -> "..."; _ when EstimatedTotal < 10000000 -> add_seps_to_int(EstimatedTotal); _ -> Low = trunc(math:log10(EstimatedTotal)), io_lib:format("< 10e~w", [Low + 1]) end, ElapsedStr = time_string(round(Ticks * ?TICKER_TIMEOUT / 1000)), CompletionStr = estimate_completion(EstimatedTotal, TracesExplored, Rate), Str = io_lib:format( "~6s |" "~11s |" "~s" "~8s |" "~6s |" "~8s |" "~10s |" "~8s", [ErrorsStr, TracesExploredStr, SSBStr, PlannedStr, RateStr, ElapsedStr, EstimatedTotalStr, CompletionStr] ), NewState = State#logger_state{rate_info = NewRateInfo}, {Str, NewState}. %%------------------------------------------------------------------------------ init_rate_info() -> #rate_info{ average = init, prev = 0, timestamp = timestamp() }. update_rate(RateInfo, TracesExplored) -> #rate_info{ average = Average, prev = Prev, timestamp = Old } = RateInfo, New = timestamp(), {Rate, NewAverage} = case TracesExplored < 10 of true -> {init, init}; false -> Time = timediff(New, Old), Diff = TracesExplored - Prev, CurrentRate = Diff / (Time + 0.0001), case Average =:= init of true -> NA = concuerror_window_average:init(CurrentRate, 50), {round(CurrentRate), NA}; false -> {R, NA} = concuerror_window_average:update(CurrentRate, Average), {round(R), NA} end end, NewRateInfo = RateInfo#rate_info{ average = NewAverage, prev = TracesExplored, timestamp = New }, {Rate, NewRateInfo}. sanitize_estimation(Estimation, _) when not is_number(Estimation) -> Estimation; sanitize_estimation(Estimation, TracesTotal) -> EstSignificant = two_significant(Estimation), case EstSignificant > TracesTotal of true -> EstSignificant; false -> two_significant(TracesTotal) end. two_significant(Number) when Number < 100 -> Number + 1; two_significant(Number) -> 10 * two_significant(Number div 10). %%------------------------------------------------------------------------------ to_stderr(Format, Data) -> to_file(standard_error, Format, Data). to_file(disable, _, _) -> ok; to_file(Output, Format, Data) -> Msg = io_lib:format(Format, Data), io:format(Output, "~s", [Msg]). %%------------------------------------------------------------------------------ final_interleavings_message(State) -> #logger_state{ bound_reached = BoundReached, errors = Errors, interleaving_bound = InterleavingBound, traces_explored = TracesExplored, traces_ssb = TracesSSB, traces_total = TracesTotal } = State, SSB = case TracesSSB =:= 0 of true -> ""; false -> io_lib:format(" (~p sleep-set blocked)", [TracesSSB]) end, BR = case BoundReached of true -> " (the scheduling bound was reached)"; false -> "" end, ExploreTotal = min(TracesTotal, InterleavingBound), io_lib:format("~p errors, ~p/~p interleavings explored~s~s~n", [Errors, TracesExplored, ExploreTotal, SSB, BR]). %%------------------------------------------------------------------------------ estimate_completion(Estimated, Explored, Rate) when not is_number(Estimated); not is_number(Explored); not is_number(Rate) -> "..."; estimate_completion(Estimated, Explored, Rate) -> Remaining = Estimated - Explored, Completion = round(Remaining/(Rate + 0.001)), approximate_time_string(Completion). %%------------------------------------------------------------------------------ -type posint() :: pos_integer(). -type split_fun() :: fun((posint()) -> posint() | {posint(), posint()}). -record(time_formatter, { threshold = 1 :: pos_integer() | 'infinity', rounding = 1 :: pos_integer(), split_fun :: split_fun(), one_format = "~w" :: string(), two_format = "~w ~w" :: string() }). approximate_time_string(Seconds) -> lists:flatten(time_string(approximate_time_formatters(), Seconds)). time_string(Seconds) -> lists:flatten(time_string(time_formatters(), Seconds)). time_string([ATF|Rest], Value) -> #time_formatter{ threshold = Threshold, rounding = Rounding, split_fun = SplitFun, one_format = OneFormat, two_format = TwoFormat } = ATF, case Value >= Threshold of true -> time_string(Rest, Value div Rounding); false -> case SplitFun(Value) of {High, Low} -> io_lib:format(TwoFormat, [High, Low]); Single -> io_lib:format(OneFormat, [Single]) end end. time_formatters() -> SecondsSplitFun = fun(S) -> S end, SecondsATF = #time_formatter{ threshold = 60 * 1, rounding = 1, split_fun = SecondsSplitFun, one_format = "~ws" }, MinutesSplitFun = fun(Seconds) -> {Seconds div 60, Seconds rem 60} end, MinutesATF = #time_formatter{ threshold = 60 * 60, rounding = 60, split_fun = MinutesSplitFun, two_format = "~wm~2..0ws" }, HoursSplitFun = fun(Minutes) -> {Minutes div 60, Minutes rem 60} end, HoursATF = #time_formatter{ threshold = 2 * 24 * 60, rounding = 60, split_fun = HoursSplitFun, two_format = "~wh~2..0wm" }, DaysSplitFun = fun(Hours) -> {Hours div 24, Hours rem 24} end, DaysATF = #time_formatter{ threshold = infinity, split_fun = DaysSplitFun, two_format = "~wd~2..0wh" }, [ SecondsATF , MinutesATF , HoursATF , DaysATF ]. approximate_time_formatters() -> SecondsSplitFun = fun(_) -> 1 end, SecondsATF = #time_formatter{ threshold = 60 * 1, rounding = 60, split_fun = SecondsSplitFun, one_format = "<~wm" }, MinutesSplitFun = fun(Minutes) -> Minutes end, MinutesATF = #time_formatter{ threshold = 30 * 1, rounding = 10, split_fun = MinutesSplitFun, one_format = "~wm" }, TensSplitFun = fun(Tens) -> case Tens < 6 of true -> Tens * 10; false -> {Tens div 6, (Tens rem 6) * 10} end end, TensATF = #time_formatter{ threshold = 2 * 6, rounding = 6, split_fun = TensSplitFun, one_format = "~wm", two_format = "~wh~2..0wm" }, HoursSplitFun = fun(Hours) -> case Hours < 60 of true -> Hours; false -> {Hours div 24, Hours rem 24} end end, HoursATF = #time_formatter{ threshold = 2 * 24, rounding = 24, split_fun = HoursSplitFun, one_format = "~wh", two_format = "~wd~2..0wh" }, DaysSplitFun = fun(Days) -> Days end, DaysATF = #time_formatter{ threshold = 12 * 30, rounding = 30, split_fun = DaysSplitFun, one_format = "~wd" }, MonthsSplitFun = fun(Months) -> {Months div 12, Months rem 12} end, MonthsATF = #time_formatter{ threshold = 50 * 12, rounding = 12, split_fun = MonthsSplitFun, two_format = "~wy~2..0wm" }, YearsSplitFun = fun(Years) -> Years end, YearsATF = #time_formatter{ threshold = 10000, rounding = 1, split_fun = YearsSplitFun, one_format = "~wy" }, TooMuchSplitFun = fun(_) -> 10000 end, TooMuchATF = #time_formatter{ threshold = infinity, split_fun = TooMuchSplitFun, one_format = "> ~wy" }, [ SecondsATF , MinutesATF , TensATF , HoursATF , DaysATF , MonthsATF , YearsATF , TooMuchATF ]. %%------------------------------------------------------------------------------ add_seps_to_int(Integer) when Integer < 1000 -> integer_to_list(Integer); add_seps_to_int(Integer) -> Rem = Integer rem 1000, DivS = add_seps_to_int(Integer div 1000), io_lib:format("~s ~3..0w", [DivS, Rem]). %%------------------------------------------------------------------------------ -spec graph_set_node(logger(), unique_id(), unique_id()) -> ok. graph_set_node(Logger, Parent, Sibling) -> Logger ! {graph, {set_node, Parent, Sibling}}, ok. -spec graph_new_node(logger(), unique_id(), index(), event()) -> ok. graph_new_node(Logger, Ref, Index, Event) -> Logger ! {graph, {new_node, Ref, Index, Event}}, ok. -spec graph_race(logger(), unique_id(), unique_id()) -> ok. graph_race(Logger, EarlyRef, Ref) -> Logger ! {graph, {race, EarlyRef, Ref}}, ok. graph_preamble({disable, ""}) -> disable; graph_preamble({GraphFile, _}) -> to_file( GraphFile, "digraph {~n" " graph [ranksep=0.3]~n" " node [shape=box,width=7,fontname=Monospace]~n" " \"init\" [label=\"Initial\"];~n" " subgraph interleaving_1 {~n", []), {GraphFile, init, none}. graph_command(_Command, #logger_state{graph_data = disable} = State) -> State; graph_command(Command, State) -> #logger_state{ graph_data = {GraphFile, Parent, Sibling} = Graph, print_depth = PrintDepth } = State, NewGraph = case Command of {new_node, Ref, I, Event} -> ErrorS = case Event#event.event_info of #exit_event{reason = normal} -> ",color=limegreen,penwidth=5"; #exit_event{} -> ",color=red,penwidth=5"; #builtin_event{status = {crashed, _}} -> ",color=orange,penwidth=5"; _ -> "" end, print_depth_tip(), NoLocEvent = Event#event{location = []}, Label = concuerror_io_lib:pretty_s({I, NoLocEvent}, PrintDepth - 19), to_file( GraphFile, " \"~p\" [label=\"~s\\l\"~s];~n", [Ref, Label, ErrorS]), case Sibling =:= none of true -> to_file(GraphFile, "~s [weight=1000];~n", [ref_edge(Parent, Ref)]); false -> to_file( GraphFile, "~s [style=invis,weight=1];~n" "~s [constraint=false];~n", [ref_edge(Parent, Ref), ref_edge(Sibling, Ref)]) end, {GraphFile, Ref, none}; {race, EarlyRef, Ref} -> to_file( GraphFile, "~s [constraint=false, color=red, dir=back, penwidth=3," " style=dashed];~n", [dref_edge(EarlyRef, Ref)]), Graph; {set_node, {I, _} = NewParent, NewSibling} -> to_file( GraphFile, " }~n" " subgraph interleaving_~w {~n", [I + 1]), {GraphFile, NewParent, NewSibling}; {status, Count, String, Color} -> Final = {Count + 1, final}, to_file( GraphFile, " \"~p\" [label=\"~p: ~s\",style=filled,fillcolor=~s];~n" "~s [weight=1000];~n", [Final, Count+1, String, Color, ref_edge(Parent, Final)]), Graph end, State#logger_state{graph_data = NewGraph}. ref_edge(RefA, RefB) -> io_lib:format(" \"~p\" -> \"~p\"", [RefA, RefB]). dref_edge(RefA, RefB) -> io_lib:format(" \"~p\":e -> \"~p\":e", [RefA, RefB]). close_files(State) -> graph_close(State), file_close(State#logger_state.output). graph_close(#logger_state{graph_data = disable}) -> ok; graph_close(#logger_state{graph_data = {GraphFile, _, _}}) -> to_file( GraphFile, " }~n" "}~n", []), file_close(GraphFile). file_close(disable) -> ok; file_close(File) -> ok = file:close(File). %%------------------------------------------------------------------------------ print_depth_tip() -> Tip = "Increase '--print_depth' if output/graph contains \"...\".~n", ?unique(self(), ?ltip, Tip, []). %%------------------------------------------------------------------------------ setup_symbolic_names(SymbolicNames, Processes) -> case SymbolicNames of false -> ok; true -> print_symbolic_info(), concuerror_callback:setup_logger(Processes) end. print_symbolic_info() -> Tip = "Showing PIDs as \"\"" " ('-h symbolic_names').~n", ?unique(self(), ?linfo, Tip, []). ================================================ FILE: src/concuerror_options.erl ================================================ %%% @doc %%% Concuerror's options module %%% %%% The `_option()' functions listed on this page all correspond to %%% valid configuration options. %%% %%% For general documentation go to the Overview page. %%% %%% == Table of Contents == %%% %%%
    %%%
  • {@section Help}
  • %%%
  • {@section Options}
  • %%%
  • {@section Standard Error Printout}
  • %%%
  • {@section Report File}
  • %%%
%%% %%% == Help == %%% %%% You can also access documentation about options using the {@link %%% help_option/0. `help'} option. You can get more help with {@link %%% help_option/0. `concuerror --help help'}. In the future even more %%% help might be added. %%% %%% If you invoke Concuerror without an argument, `--help' is assumed %%% as an argument. %%% %%% == Options == %%%
    %%%
  1. All options have a long name.
  2. %%%
  3. Some options also have a short name.
  4. %%%
  5. Options marked with an asterisk * are considered %%% experimental and may be brittle and disappear in future versions.
  6. %%%
%%% %%% === Arguments === %%% %%% The type of each options' argument is listed at the option's %%% specification below. When specifying {@type integer()} or {@type %%% boolean()} options in the command line you can omit `true' or 1 %%% as values. %%% %%% === Module Attributes === %%% %%% You can use the following attributes in the module specified by `--module' %%% to pass options to Concuerror: %%%
%%%
`-concuerror_options(Options)'
%%%
%%% A list of Options that can be overriden by other options. %%%
%%%
`-concuerror_options_forced(Options)'
%%%
%%% A list of Options that override any other options. %%%
%%%
%%% %%% This information is also available via {@link %%% help_option/0. `concuerror --help attributes'} %%% %%% === Keywords === %%% %%% Each option is associated with one or more %%% keywords. These can be used with {@link help_option/0. `help'} %%% to find related options. %%% %%% If you invoke {@link help_option/0. `help'} without an argument, %%% you will only see options with the keyword `basic'. To see all %%% options use {@link help_option/0. `--help all'}. %%% %%% === Multiple Arguments === %%% %%% Some options can be specified multiple times, each time with a %%% different argument. For those that don't the last value is kept %%% (this makes invocation via command line easier). Concuerror %%% reports any overrides. %%% %%% == Standard Error Printout == %%% %%% By default, Concuerror prints diagnostic messages in the standard %%% error stream. Such messages are also printed at the bottom of the %%% {@section Report File} after the analysis is completed. You can %%% find explanation of the classification of these messages in the %%% {@link verbosity_option/0. `verbosity'} option. %%% %%% By default, Concuerror also prints progress information in the %%% standard error stream. You can find what is the meaning of each field %%% by running `concuerror --help progress'. %%% %%% The printout can be reduced or disabled (see {@link %%% verbosity_option/0. `verbosity'} option). Diagnostic messages are %%% always printed in the {@section Report File}. %%% %%% == Report File == %%% %%% By default, Concuerror prints analysis findings in a report file. %%% %%% This file contains: %%% %%%
    %%%
  1. A header line containing the version used and starting time.
  2. %%%
  3. A list of all the options used in the particular run.
  4. %%%
  5. Zero or more {@section Error Reports} about erroneous %%% interleavings.
  6. %%%
  7. Diagnostic messages (see {@section Standard Error Printout}).
  8. %%%
  9. Summary about the analysis, including %%%
      %%%
    • completion time,
    • %%%
    • {@link concuerror:analysis_result(). analysis result}
    • %%%
    • total number of interleavings explored
    • %%%
    • total number of interleavings with errors
    • %%%
    %%%
  10. %%%
%%% %%% === Error Reports === %%% %%% An error report corresponds to an interleaving that lead to errors and %%% contains at least the following sections: %%% %%%
    %%%
  • Description of all errors encountered.
  • %%%
  • Linear trace of all events in the interleaving. This contains only %%% the operations that read/write shared information.
  • %%%
%%% %%% If the program produce any output, this is also included. %%% %%% By default, Concuerror reports the following errors: %%%
    %%%
  • A process exited abnormally.
  • %%%
  • One or more processes are 'stuck' at a receive statement.
  • %%%
  • The trace exceeded a (configurable but finite) number of events.
  • %%%
  • Abnormal errors.
  • %%%
%%% %%% If the {@link show_races_option/0. `show_races'} option is used, %%% the pairs of racing events that justify the exploration of new %%% interleavings are also shown. These are shown for all %%% interleavings, not only the ones with errors. %%% %%% If the {@link log_all_option/0. `log_all'} option is used, %%% all interleavings will be shown, not only the ones with errors. %%% -module(concuerror_options). -export( [ after_timeout_option/0 , assertions_only_option/0 , assume_racing_option/0 , depth_bound_option/0 , disable_sleep_sets_option/0 , dpor_option/0 , exclude_module_option/0 , file_option/0 , first_process_errors_only_option/0 , graph_option/0 , help_option/0 , ignore_error_option/0 , instant_delivery_option/0 , interleaving_bound_option/0 , keep_going_option/0 , log_all_option/0 , module_option/0 , no_output_option/0 , non_racing_system_option/0 , observers_option/0 , optimal_option/0 , output_option/0 , pa_option/0 , print_depth_option/0 , pz_option/0 , quiet_option/0 , scheduling_bound_option/0 , scheduling_bound_type_option/0 , scheduling_option/0 , show_races_option/0 , strict_scheduling_option/0 , symbolic_names_option/0 , test_option/0 , timeout_option/0 , treat_as_normal_option/0 , use_receive_patterns_option/0 , verbosity_option/0 , version_option/0 ]). -export([finalize/1, parse_cl/1]). %% Exported for autocomplete tests -export([options/0]). -export_type( [ option_spec/0 , options/0 ]). -export_type( [ bound/0 , dpor/0 , scheduling/0 , scheduling_bound_type/0 ]). -ifdef(DOC). -export([generate_option_docfiles/1]). -endif. %%%----------------------------------------------------------------------------- -include("concuerror.hrl"). %%%----------------------------------------------------------------------------- -type options() :: proplists:proplist(). %% Concuerror's configuration options are given as a `proplist()'. %% See the list of functions in this module for valid configuration %% options. -type bound() :: 'infinity' | non_neg_integer(). %% If you want to pass `infinity' as option from the command-line, use `-1'. -type dpor() :: 'none' | 'optimal' | 'persistent' | 'source'. %% See {@link dpor_option/0} for the meaning of values. -type scheduling() :: 'oldest' | 'newest' | 'round_robin'. %% See {@link scheduling_option/0} for the meaning of values. -type scheduling_bound_type() :: 'bpor' | 'delay' | 'none' | 'ubpor'. %% See {@link scheduling_bound_option/0} for the meaning of values. %%%----------------------------------------------------------------------------- -define(MINIMUM_TIMEOUT, 500). -define(DEFAULT_VERBOSITY, ?linfo). -define(DEFAULT_PRINT_DEPTH, 20). -define(DEFAULT_OUTPUT, "concuerror_report.txt"). %%%----------------------------------------------------------------------------- -define(ATTRIBUTE_OPTIONS, concuerror_options). -define(ATTRIBUTE_FORCED_OPTIONS, concuerror_options_forced). -define(ATTRIBUTE_TIP_THRESHOLD, 8). %%%----------------------------------------------------------------------------- -type long_name() :: atom(). -type keywords() :: [ 'advanced' | 'basic' | 'bound'| 'console' | 'erlang' | 'errors' | 'experimental' | 'input' | 'output' | 'por' | 'visual' ]. -type short_name() :: char() | undefined. -type extra_type() :: 'bound' | 'dpor' | 'scheduling' | 'scheduling_bound_type'. -type arg_spec() :: getopt:arg_spec() | extra_type() | {extra_type(), term()}. -type short_help() :: string(). -type long_help() :: string() | 'nolong'. -opaque option_spec() :: { long_name() , keywords() , short_name() , arg_spec() , short_help() , long_help() }. %% This is used internally to specify option components and is %% irrelevant for a user of Concuerror. -define(OPTION_KEY, 1). -define(OPTION_KEYWORDS, 2). -define(OPTION_SHORT, 3). -define(OPTION_GETOPT_TYPE_DEFAULT, 4). -define(OPTION_GETOPT_SHORT_HELP, 5). -define(OPTION_GETOPT_LONG_HELP, 6). -spec options() -> [option_spec()]. options() -> [ module_option() , test_option() , output_option() , no_output_option() , verbosity_option() , quiet_option() , graph_option() , symbolic_names_option() , print_depth_option() , show_races_option() , file_option() , pa_option() , pz_option() , log_all_option() , exclude_module_option() , depth_bound_option() , interleaving_bound_option() , dpor_option() , optimal_option() , scheduling_bound_type_option() , scheduling_bound_option() , disable_sleep_sets_option() , after_timeout_option() , instant_delivery_option() , use_receive_patterns_option() , observers_option() , scheduling_option() , strict_scheduling_option() , keep_going_option() , ignore_error_option() , treat_as_normal_option() , assertions_only_option() , first_process_errors_only_option() , timeout_option() , assume_racing_option() , non_racing_system_option() , help_option() , version_option() ]. %%%----------------------------------------------------------------------------- -ifdef(DOC). %% @private -spec generate_option_docfiles(filename:filename()) -> ok. generate_option_docfiles(Dir) -> lists:foreach(fun(O) -> generate_option_docfile(O, Dir) end, options()). -spec generate_option_docfile(option_spec(), filename:filename()) -> ok. generate_option_docfile(Option, Dir) -> OptionName = element(?OPTION_KEY, Option), OptionShortHelp = element(?OPTION_GETOPT_SHORT_HELP, Option), OptionShort = element(?OPTION_SHORT, Option), OptionArg = element(?OPTION_GETOPT_TYPE_DEFAULT, Option), OptionKeywords = element(?OPTION_KEYWORDS, Option), OptionLongHelp = element(?OPTION_GETOPT_LONG_HELP, Option), Filename = filename:join([Dir, atom_to_list(OptionName) ++ "_option.edoc"]), {ok, File} = file:open(Filename, [write]), print_docfile_preamble(File), io:format(File, "@doc ~s~n~n", [OptionShortHelp]), io:format(File, "
    ", []), item( File, "Name: `--~p Value' or `@{~p, Value@}'", [OptionName, OptionName]), case OptionShort =:= undefined of true -> ok; false -> item(File, "Short: `-~c'", [OptionShort]) end, {Type, DefaultVal} = case OptionArg of {T, D} -> {T, {true, D}}; T -> {T, false} end, item(File, "Argument type: {@type ~w()}", [Type]), case DefaultVal of {true, DV} -> item(File, "Default value: `~p'", [DV]); false -> ok end, AllowedInModuleAttributes = not lists:member(OptionName, not_allowed_in_module_attributes()), item( File, "Allowed in {@section Module Attributes}: ~p", [to_yes_or_no(AllowedInModuleAttributes)]), MultipleAllowed = lists:member(OptionName, multiple_allowed()), item( File, "{@section Multiple Arguments}: ~p", [to_yes_or_no(MultipleAllowed)]), case OptionKeywords =:= [] of true -> ok; false -> StringKeywords = string:join([atom_to_list(K) || K <- OptionKeywords], ", "), item(File, "{@section Keywords}: ~s", [StringKeywords]) end, io:format(File, "
", []), case OptionLongHelp =:= nolong of true -> ok; false -> io:format(File, OptionLongHelp ++ "~n", []) end, file:close(File). print_docfile_preamble(File) -> Format = "%% ATTENTION!~n" "%% This file is generated by ~w:generate_option_docfile/2~n" "~n", io:format(File, Format, [?MODULE]). item(File, Format, Args) -> io:format(File, "
  • " ++ Format ++ "
  • ", Args). to_yes_or_no(true) -> yes; to_yes_or_no(false) -> no. -endif. %%%----------------------------------------------------------------------------- %% @docfile "doc/module_option.edoc" -spec module_option() -> option_spec(). module_option() -> { module , [basic, input] , $m , atom , "Module containing the test function" , "Concuerror begins exploration from a test function located in the module" " specified by this option.~n" "~n" "There is no need to specify modules used in the test if they are in" " Erlang's code path. Otherwise use `--file', `--pa' or `--pz'." }. %% @docfile "doc/test_option.edoc" -spec test_option() -> option_spec(). test_option() -> { test , [basic, input] , $t , {atom, test} , "Name of test function" , "This must be a 0-arity function located in the module specified by" " `--module'. Concuerror will start the test by spawning a process that" " calls this function." }. %% @docfile "doc/output_option.edoc" -spec output_option() -> option_spec(). output_option() -> { output , [basic, output] , $o , {string, ?DEFAULT_OUTPUT} , "Filename to use for the analysis report" , "This is where Concuerror writes the results of the analysis." }. %% @docfile "doc/no_output_option.edoc" -spec no_output_option() -> option_spec(). no_output_option() -> { no_output , [basic, output] , undefined , boolean , "Do not produce an analysis report" , "Concuerror will not produce an analysis report." }. %% @docfile "doc/verbosity_option.edoc" -spec verbosity_option() -> option_spec(). verbosity_option() -> { verbosity , [advanced, basic, console] , $v , {integer, ?DEFAULT_VERBOSITY} , io_lib:format("Verbosity level (0-~w)", [?MAX_LOG_LEVEL]) , "The value of verbosity determines what is shown on standard error." " Messages up to info are always also shown in the output file." " The available levels are the following:~n~n" "0 (quiet) Nothing is printed (equivalent to `--quiet')~n" "1 (error) Critical, resulting in early termination~n" "2 (warn) Non-critical, notifying about weak support for a feature or~n" " the use of an option that alters the output~n" "3 (tip) Notifying of a suggested refactoring or option to make~n" " testing more efficient~n" "4 (info) Normal operation messages, can be ignored~n" "5 (time) Timing messages~n" "6 (debug) Used only during debugging~n" "7 (trace) Everything else" }. %% @docfile "doc/quiet_option.edoc" %% @see verbosity_option/0 -spec quiet_option() -> option_spec(). quiet_option() -> { quiet , [basic, console] , $q , boolean , "Synonym for `--verbosity 0'" , "Do not write anything to standard error." }. %% @docfile "doc/graph_option.edoc" -spec graph_option() -> option_spec(). graph_option() -> { graph , [output, visual] , $g , string , "Produce a DOT graph in the specified file" , "The DOT graph can be converted to an image with" " e.g. `dot -Tsvg -o graph.svg graph'" }. %% @docfile "doc/symbolic_names_option.edoc" -spec symbolic_names_option() -> option_spec(). symbolic_names_option() -> { symbolic_names , [erlang, output, visual] , $s , {boolean, true} , "Use symbolic process names" , "Replace PIDs with symbolic names in outputs. The format used is:~n" " `<[symbolic name]/[last registered name]>'~n" "where [symbolic name] is:~n~n" " - `P', for the first process and~n" " - `[parent symbolic name].[ordinal]', for any other process," " where [ordinal] shows the order of spawning (e.g. `' is the" " second process spawned by `

    ').~n" "The `[last registered name]' part is shown only if applicable." }. %% @docfile "doc/print_depth_option.edoc" -spec print_depth_option() -> option_spec(). print_depth_option() -> { print_depth , [output, visual] , undefined , {integer, ?DEFAULT_PRINT_DEPTH} , "Print depth for log/graph" , "Specifies the max depth for any terms printed in the log (behaves" " just as the additional argument of `~~W' and `~~P' argument of" " `io:format/3'). If you want more info about a particular piece" " of data in an interleaving, consider using `erlang:display/1'" " and checking the standard output section in the error reports" " of the analysis report instead." }. %% @docfile "doc/show_races_option.edoc" -spec show_races_option() -> option_spec(). show_races_option() -> { show_races , [output, por, visual] , undefined , {boolean, false} , "Show races in log/graph", "Determines whether information about pairs of racing instructions will be" " included in the logs of erroneous interleavings and the graph." }. %% @docfile "doc/file_option.edoc" -spec file_option() -> option_spec(). file_option() -> { file , [input] , $f , string , "Load specified file (.beam or .erl)" , "Explicitly load the specified file(s) (.beam or .erl)." " Source (.erl) files should not require any command line compile options." " Use a .beam file (preferably compiled with `+debug_info') if special" " compilation is needed.~n" "~n" "It is recommended to rely on Erlang's load path rather than using" " this option." }. %% @docfile "doc/pa_option.edoc" -spec pa_option() -> option_spec(). pa_option() -> { pa , [input] , undefined , string , "Add directory to Erlang's code path (front)" , "Works exactly like `erl -pa'." }. %% @docfile "doc/pz_option.edoc" -spec pz_option() -> option_spec(). pz_option() -> { pz , [input] , undefined , string , "Add directory to Erlang's code path (rear)" , "Works exactly like `erl -pz'." }. %% @docfile "doc/log_all_option.edoc" -spec log_all_option() -> option_spec(). log_all_option() -> { log_all , [output] , undefined , {boolean, false} , "Show all interleavings in log", "Determines whether correct interleavings will be also shown in the logs." }. %% @docfile "doc/exclude_module_option.edoc" -spec exclude_module_option() -> option_spec(). exclude_module_option() -> { exclude_module , [advanced, experimental, input] , $x , atom , "* Modules that should not be instrumented" , "Experimental. Concuerror needs to instrument all code in a test to be able" " to reset the state after each exploration. You can use this option to" " exclude a module from instrumentation, but you must ensure that any state" " is reset correctly, or Concuerror will complain that operations have" " unexpected results." }. %% @docfile "doc/depth_bound_option.edoc" -spec depth_bound_option() -> option_spec(). depth_bound_option() -> { depth_bound , [bound] , $d , {integer, 500} , "Maximum number of events" , "The maximum number of events allowed in an interleaving. Concuerror will" " stop exploring an interleaving that has events beyond this limit." }. %% @docfile "doc/interleaving_bound_option.edoc" -spec interleaving_bound_option() -> option_spec(). interleaving_bound_option() -> { interleaving_bound , [bound] , $i , {bound, infinity} , "Maximum number of interleavings" , "The maximum number of interleavings that will be explored. Concuerror will" " stop exploration beyond this limit." }. %% @docfile "doc/dpor_option.edoc" -spec dpor_option() -> option_spec(). dpor_option() -> { dpor , [por] , undefined , {dpor, optimal} , "DPOR technique" , "Specifies which Dynamic Partial Order Reduction technique will be used." " The available options are:~n" "- `none': Disable DPOR. Not recommended.~n" "- `optimal': Using source sets and wakeup trees.~n" "- `source': Using source sets only. Use this if the rate of~n" " exploration is too slow. Use `optimal' if a lot of~n" " interleavings are reported as sleep-set blocked.~n" "- `persistent': Using persistent sets. Not recommended." }. %% @docfile "doc/optimal_option.edoc" %% @see dpor_option/0 -spec optimal_option() -> option_spec(). optimal_option() -> { optimal , [por] , undefined , boolean , "Synonym for `--dpor optimal (true) | source (false)'" , nolong }. %% @docfile "doc/scheduling_bound_type_option.edoc" -spec scheduling_bound_type_option() -> option_spec(). scheduling_bound_type_option() -> { scheduling_bound_type , [bound, experimental] , $c , {scheduling_bound_type, none} , "* Schedule bounding technique" , "Enables scheduling rules that prevent interleavings from being explored." " The available options are:~n" "- `none': no bounding~n" "- `bpor': how many times per interleaving the scheduler is allowed~n" " to preempt a process.~n" " * Not compatible with Optimal DPOR.~n" "- `delay': how many times per interleaving the scheduler is allowed~n" " to skip the process chosen by default in order to schedule~n" " others.~n" "- `ubpor': same as `bpor' but without conservative backtrack points.~n" " * Experimental, unsound, not compatible with Optimal DPOR.~n" }. %% @docfile "doc/scheduling_bound_option.edoc" -spec scheduling_bound_option() -> option_spec(). scheduling_bound_option() -> { scheduling_bound , [bound] , $b , integer , "Scheduling bound value" , "The maximum number of times the rule specified in" " `--scheduling_bound_type' can be violated." }. %% @docfile "doc/disable_sleep_sets_option.edoc" -spec disable_sleep_sets_option() -> option_spec(). disable_sleep_sets_option() -> { disable_sleep_sets , [advanced, por] , undefined , {boolean, false} , "Disable sleep sets" , "This option is only available with `--dpor none'." }. %% @docfile "doc/after_timeout_option.edoc" -spec after_timeout_option() -> option_spec(). after_timeout_option() -> { after_timeout , [erlang] , $a , {bound, infinity} , "Threshold for treating timeouts as infinity" , "Assume that `after' clauses with timeouts higher or equal to the" " specified value cannot be triggered. Concuerror treats all" " lower values as triggerable" }. %% @docfile "doc/instant_delivery_option.edoc" -spec instant_delivery_option() -> option_spec(). instant_delivery_option() -> { instant_delivery , [erlang] , undefined , {boolean, true} , "Make messages and signals arrive instantly" , "Assume that messages and signals are delivered immediately." " Setting this to false enables \"true\" Erlang message-passing" " semantics, in which message delivery is distinct from sending." }. %% @docfile "doc/use_receive_patterns_option.edoc" -spec use_receive_patterns_option() -> option_spec(). use_receive_patterns_option() -> { use_receive_patterns , [advanced, erlang, por] , undefined , {boolean, true} , "Use receive patterns for racing sends" , "If true, Concuerror will only consider two" " message deliveries as racing when the first message is really" " received and the patterns used could also match the second" " message." }. %% @docfile "doc/observers_option.edoc" %% @see use_receive_patterns_option/0 -spec observers_option() -> option_spec(). observers_option() -> { observers , [advanced, erlang, por] , undefined , boolean , "Synonym for `--use_receive_patterns'" , nolong }. %% @docfile "doc/scheduling_option.edoc" -spec scheduling_option() -> option_spec(). scheduling_option() -> { scheduling , [advanced] , undefined , {scheduling, round_robin} , "Scheduling order" , "How Concuerror picks the next process to run. The available options are" " `oldest', `newest' and `round_robin', with the expected semantics." }. %% @docfile "doc/strict_scheduling_option.edoc" -spec strict_scheduling_option() -> option_spec(). strict_scheduling_option() -> { strict_scheduling , [advanced] , undefined , {boolean, false} , "Force preemptions when scheduling" , "Whether Concuerror should enforce the scheduling strategy strictly or let" " a process run until blocked before reconsidering the scheduling policy." }. %% @docfile "doc/keep_going_option.edoc" -spec keep_going_option() -> option_spec(). keep_going_option() -> { keep_going , [basic, errors] , $k , {boolean, false} , "Keep running after an error is found" , "Concuerror stops by default when the first error is found. Enable this" " option to keep looking for more errors.~n" "~n" " It is usually recommended to modify the test, or" " use the `--ignore_error' / `--treat_as_normal' options, instead of" " this one." }. %% @docfile "doc/ignore_error_option.edoc" -spec ignore_error_option() -> option_spec(). ignore_error_option() -> { ignore_error , [errors] , undefined , atom , "Error categories that should be ignored", "Concuerror will not report errors of the specified kind:~n" "- `abnormal_exit': processes exiting with any abnormal reason;" " check `-h treat_as_normal' and `-h assertions_only' for more refined" " control~n" "- `abnormal_halt': processes executing erlang:halt/1,2 with status /= 0~n" "- `deadlock': processes waiting at a receive statement~n" "- `depth_bound': reaching the depth bound; check `-h depth_bound'" }. %% @docfile "doc/treat_as_normal_option.edoc" -spec treat_as_normal_option() -> option_spec(). treat_as_normal_option() -> { treat_as_normal , [errors] , undefined , atom , "Exit reason treated as `normal' (i.e., not reported as an error)" , "A process that exits with the specified atom as reason, or with a reason" " that is a tuple with the specified atom as a first element, will not be" " reported as exiting abnormally. Useful e.g. when analyzing supervisors" " (`shutdown' is usually a normal exit reason in this case)." }. %% @docfile "doc/assertions_only_option.edoc" -spec assertions_only_option() -> option_spec(). assertions_only_option() -> { assertions_only , [errors] , undefined , {boolean, false} , "Report only abnormal exits due to `?asserts'", "Only processes that exit with a reason of form `{{assert*, _}, _}' are" " considered errors. Such exit reasons are generated e.g. by the" " macros defined in the `stdlib/include/assert.hrl' header file." }. %% @docfile "doc/first_process_errors_only_option.edoc" -spec first_process_errors_only_option() -> option_spec(). first_process_errors_only_option() -> { first_process_errors_only , [errors] , undefined , {boolean, false} , "Report only errors that involve the first process" , "All errors involving only children processes will be ignored." }. %% @docfile "doc/timeout_option.edoc" -spec timeout_option() -> option_spec(). timeout_option() -> { timeout , [advanced, erlang] , undefined , {bound, 5000} , "How long to wait for an event (>= " ++ integer_to_list(?MINIMUM_TIMEOUT) ++ "ms)" , "How many ms to wait before assuming that a process is stuck in an infinite" " loop between two operations with side-effects. Setting this to `infinity'" " will make Concuerror wait indefinitely. Otherwise must be >= " ++ integer_to_list(?MINIMUM_TIMEOUT) ++ "." }. %% @docfile "doc/assume_racing_option.edoc" -spec assume_racing_option() -> option_spec(). assume_racing_option() -> { assume_racing , [advanced, por] , undefined , {boolean, true} , "Do not crash if race info is missing" , "Concuerror has a list of operation pairs that are known to be non-racing." " If there is no info about whether a specific pair of built-in operations" " may race, assume that they do indeed race. If this option is set to" " false, Concuerror will exit instead. Useful only for detecting missing" " racing info." }. %% @docfile "doc/non_racing_system_option.edoc" -spec non_racing_system_option() -> option_spec(). non_racing_system_option() -> { non_racing_system , [erlang] , undefined , atom , "No races due to 'system' messages" , "Assume that any messages sent to the specified (by registered name) system" " process are not racing with each-other. Useful for reducing the number of" " interleavings when processes have calls to e.g. io:format/1,2 or" " similar." }. %% @docfile "doc/help_option.edoc" -spec help_option() -> option_spec(). help_option() -> { help , [basic] , $h , atom , "Display help (use `-h h' for more help)" , "Without an argument, prints info for basic options.~n~n" "With `all' as argument, prints info for all options.~n~n" "With `attributes' as argument, prints info about passing options using" " module attributes.~n~n" "With `progress' as argument, prints info about what the items in the" " progress info mean.~n~n" "With an option name as argument, prints more help for that option.~n~n" "Options have keywords associated with them (shown in their help)." " With a keyword as argument, prints a list of all the options that are" " associated with the keyword.~n~n" "If a boolean or integer argument is omitted, `true' or `1' is the implied" " value." }. %% @docfile "doc/version_option.edoc" -spec version_option() -> option_spec(). version_option() -> { version , [basic] , undefined , undefined , "Display version information" , nolong }. %%%----------------------------------------------------------------------------- synonyms() -> [ {{observers, true}, {use_receive_patterns, true}} , {{observers, false}, {use_receive_patterns, false}} , {{optimal, true}, {dpor, optimal}} , {{optimal, false}, {dpor, source}} , {{ignore_error, crash}, {ignore_error, abnormal_exit}} ]. groupable() -> [ exclude_module , ignore_error , non_racing_system , treat_as_normal ]. multiple_allowed() -> [ pa , pz ] ++ groupable(). not_allowed_in_module_attributes() -> [ exclude_module , file , help , module , pa , pz , version ]. derived_defaults() -> [ {{disable_sleep_sets, true}, [{dpor, none}]} , {scheduling_bound, [{scheduling_bound_type, delay}]} , {{scheduling_bound_type, bpor}, [{dpor, source}, {scheduling_bound, 1}]} , {{scheduling_bound_type, delay}, [{scheduling_bound, 1}]} , {{scheduling_bound_type, ubpor}, [{dpor, source}, {scheduling_bound, 1}]} ] ++ [{{dpor, NotObsDPOR}, [{use_receive_patterns, false}]} || NotObsDPOR <- [none, persistent, source]]. check_validity(Key) -> case Key of _ when Key =:= after_timeout; Key =:= depth_bound; Key =:= print_depth -> {fun(V) -> V > 0 end, "a positive integer"}; dpor -> [none, optimal, persistent, source]; ignore_error -> Valid = [abnormal_halt, abnormal_exit, deadlock, depth_bound], {fun(V) -> [] =:= (V -- Valid) end, io_lib:format("one or more of ~w", [Valid])}; scheduling -> [newest, oldest, round_robin]; scheduling_bound -> {fun(V) -> V >= 0 end, "a non-negative integer"}; scheduling_bound_type -> [bpor, delay, none, ubpor]; _ -> skip end. %%------------------------------------------------------------------------------ %% @doc Converts command-line arguments to a proplist using getopt %% %% This function also augments the interface of getopt, allowing %%

      %%
    • {@section Multiple Arguments} to options
    • %%
    • correction of common errors
    • %%
    -spec parse_cl([string()]) -> {'run', options()} | {'return', concuerror:analysis_result()}. parse_cl(CommandLineArgs) -> try %% CL parsing uses some version-dependent functions check_otp_version(), parse_cl_aux(CommandLineArgs) catch throw:opt_error -> options_fail() end. options_fail() -> [concuerror_logger:print_log_message(Level, Format, Args) || {Level, Format, Args} <- get_logs()], {return, fail}. parse_cl_aux([]) -> {run, [help]}; parse_cl_aux(RawCommandLineArgs) -> CommandLineArgs = fix_common_errors(RawCommandLineArgs), case getopt:parse(getopt_spec_no_default(), CommandLineArgs) of {ok, {Options, OtherArgs}} -> case OtherArgs of [] -> {run, Options}; [MaybeFilename] -> Msg = "Converting dangling argument to '--file ~s'", opt_info(Msg, [MaybeFilename]), {run, Options ++ [{file, MaybeFilename}]}; _ -> Msg = "Unknown argument(s)/option(s): ~s", opt_error(Msg, [?join(OtherArgs, " ")]) end; {error, Error} -> case Error of {missing_option_arg, help} -> cl_usage(basic), {return, ok}; {missing_option_arg, Option} -> opt_error("No argument given for '--~s'.", [Option], Option); _Other -> opt_error(getopt:format_error([], Error)) end end. fix_common_errors(RawCommandLineArgs) -> FixSingle = lists:map(fun fix_common_error/1, RawCommandLineArgs), fix_multiargs(FixSingle). fix_common_error("--" ++ [C] = Option) -> opt_info("\"~s\" converted to \"-~c\"", [Option, C]), "-" ++ [C]; fix_common_error("--" ++ Text = Option) -> Underscored = lists:map(fun dash_to_underscore/1, lowercase(Text)), case Text =:= Underscored of true -> Option; false -> opt_info("\"~s\" converted to \"--~s\"", [Option, Underscored]), "--" ++ Underscored end; fix_common_error("-p" ++ [A] = Option) when A =:= $a; A=:= $z -> opt_info("\"~s\" converted to \"-~s\"", [Option, Option]), fix_common_error("-" ++ Option); fix_common_error("-" ++ [Short|[_|_] = MaybeArg] = MaybeMispelledOption) -> maybe_warn_about_mispelled_option(Short, MaybeArg), MaybeMispelledOption; fix_common_error("infinity" = Infinity) -> %% We can't just convert, cause maybe a module or function is named infinity. opt_warn("use -1 instead of `infinity' for bounds on the command line", []), Infinity; fix_common_error(OptionOrArg) -> OptionOrArg. dash_to_underscore($-) -> $_; dash_to_underscore(Ch) -> Ch. maybe_warn_about_mispelled_option(Short, [_|_] = MaybeArg) -> ShortWithArgToLong = [{element(?OPTION_SHORT, O), element(?OPTION_KEY, O)} || O <- options(), element(?OPTION_SHORT, O) =/= undefined, not (option_type(O) =:= boolean), not (option_type(O) =:= integer), not (option_type(O) =:= bound) ], case lists:keyfind(Short, 1, ShortWithArgToLong) of {_, Long} -> opt_info( "Parsing '-~s' as '--~w ~s' (add a dash if this is not desired)", [[Short|MaybeArg], Long, MaybeArg]); _ -> ok end. option_type(Option) -> case element(?OPTION_GETOPT_TYPE_DEFAULT, Option) of {Type, _Default} -> Type; Type -> Type end. fix_multiargs(CommandLineArgs) -> fix_multiargs(CommandLineArgs, []). fix_multiargs([], Fixed) -> lists:reverse(Fixed); fix_multiargs([Option1, Arg1, Arg2 | Rest], Fixed) when hd(Option1) =:= $-, hd(Arg1) =/= $-, hd(Arg2) =/= $- -> opt_info( "\"~s ~s ~s\" converted to \"~s ~s ~s ~s\"", [Option1, Arg1, Arg2, Option1, Arg1, Option1, Arg2]), fix_multiargs([Option1, Arg2|Rest], [Arg1, Option1|Fixed]); fix_multiargs([Other|Rest], Fixed) -> fix_multiargs(Rest, [Other|Fixed]). %%%----------------------------------------------------------------------------- getopt_spec(Options) -> getopt_spec_map_type(Options, fun(X) -> X end). %% Defaults are stripped and inserted in the end to allow for overrides from an %% input file or derived defaults. getopt_spec_no_default() -> getopt_spec_map_type(options(), fun no_default/1). %% An option's long name is the same as the inner representation atom for %% consistency. getopt_spec_map_type(Options, Fun) -> [{Key, Short, atom_to_list(Key), Fun(Type), Help} || {Key, _Keywords, Short, Type, Help, _Long} <- Options]. no_default({Type, _Default}) -> no_default(Type); no_default(bound) -> integer; no_default(dpor) -> atom; no_default(scheduling) -> atom; no_default(scheduling_bound_type) -> atom; no_default(Type) -> Type. %%%----------------------------------------------------------------------------- cl_usage(all) -> Sort = fun(A, B) -> element(?OPTION_KEY, A) =< element(?OPTION_KEY, B) end, getopt:usage(getopt_spec(lists:sort(Sort, options())), "./concuerror"), print_suffix(all); cl_usage(Attribute) when Attribute =:= attribute; Attribute =:= attributes -> %% KEEP IN SYNC WITH MODULE'S OVERVIEW EDOC Msg = "~n" "Passing options using module attributes:~n" "----------------------------------------~n" "You can use the following attributes in the module specified by `--module'" " to pass options to Concuerror:~n" "~n" " -~s(Options).~n" " A list of Options that can be overriden by other options.~n" " -~s(Options).~n" " A list of Options that override any other options.~n" , to_stderr(Msg, [?ATTRIBUTE_OPTIONS, ?ATTRIBUTE_FORCED_OPTIONS]); cl_usage(progress) -> Msg = "~n" "Progress bar item explanations:~n" "-------------------------------~n" "~n" "~s" , to_stderr(Msg, [concuerror_logger:progress_help()]); cl_usage(Name) -> Optname = case lists:keyfind(Name, ?OPTION_KEY, options()) of false -> Str = atom_to_list(Name), Name =/= undefined andalso length(Str) =:= 1 andalso lists:keyfind(hd(Str), ?OPTION_SHORT, options()); R -> R end, case Optname of false -> MaybeKeyword = options(Name), case MaybeKeyword =/= [] of true -> KeywordWarningFormat = "~n" "NOTE: Only showing options with the keyword `~p'.~n" " Use `--help all' to see all available options.~n", to_stderr(KeywordWarningFormat, [Name]), getopt:usage(getopt_spec(MaybeKeyword), "./concuerror"), print_suffix(Name); false -> ListName = atom_to_list(Name), case [dash_to_underscore(L) || L <- ListName] of "_" ++ Rest -> cl_usage(list_to_atom(Rest)); Other when Other =/= ListName -> cl_usage(list_to_atom(Other)); _ -> Msg = "invalid option/keyword (as argument to `--help'): '~w'.", opt_error(Msg, [Name], help) end end; Tuple -> getopt:usage(getopt_spec([Tuple]), "./concuerror"), case element(?OPTION_GETOPT_LONG_HELP, Tuple) of nolong -> to_stderr("No additional help available.~n"); String -> to_stderr(String ++ "~n") end, {Keywords, Related} = get_keywords_and_related(Tuple), to_stderr("Option Keywords: ~p~n", [Keywords]), to_stderr("Related Options: ~p~n", [Related]), to_stderr("For general help use `-h' without an argument.~n") end. options(Keyword) -> [T || T <- options(), lists:member(Keyword, element(?OPTION_KEYWORDS, T))]. print_suffix(Keyword) -> case Keyword =:= basic of false -> to_stderr("Options with '*' are experimental.~n"); true -> ok end, to_stderr("More info & keywords about a specific option: `-h