================================================
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 [
[**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).
[**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).
[**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 ==
%%%
%%%
All options have a long name.
%%%
Some options also have a short name.
%%%
Options marked with an asterisk * are considered
%%% experimental and may be brittle and disappear in future versions.
%%%
%%%
%%% === 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:
%%%
%%%
%%%
A header line containing the version used and starting time.
%%%
A list of all the options used in the particular run.
%%%
Zero or more {@section Error Reports} about erroneous
%%% interleavings.
%%%
Diagnostic messages (see {@section Standard Error Printout}).
%%%
%%%
%%% === 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