├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── doc
├── .gitignore
├── custom_stylesheet.css
└── overview.edoc
├── rebar.config
├── rebar.lock
├── src
├── stacktrace_compat.app.src
└── stacktrace_transform.erl
└── test
├── stacktrace_compat_SUITE.erl
└── test_module.erl
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: build
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 | jobs:
11 | ci:
12 | name: >
13 | Run checks and tests over ${{matrix.otp_vsn}} and ${{matrix.os}}
14 | runs-on: ${{matrix.os}}
15 | container:
16 | image: erlang:${{matrix.otp_vsn}}
17 | strategy:
18 | matrix:
19 | otp_vsn: [19.0, 19.1, 19.2, 19.3,
20 | 20.0, 20.1, 20.2, 20.3,
21 | 21.0, 21.1, 21.2, 21.3,
22 | 22.0, 22.1, 22.2, 22.3,
23 | 23.0, 23.1, 23.2, 23.3]
24 | os: [ubuntu-latest]
25 | steps:
26 | - uses: actions/checkout@v2
27 | - run: RUNNING_ON_CI=yes make check test
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .rebar3
2 | _*
3 | .eunit
4 | *.o
5 | *.beam
6 | *.plt
7 | *.swp
8 | *.swo
9 | .erlang.cookie
10 | ebin
11 | log
12 | erl_crash.dump
13 | .rebar
14 | logs
15 | _build
16 | .idea
17 | *.iml
18 | rebar3.crashdump
19 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 | ## [1.2.2] - 2021-04-02
8 | ### Fixed
9 | - test cases that broke on OTP 24.0-rc1
10 |
11 | ## [1.2.1] - 2020-10-28
12 | ### Fixed
13 | - occasional errors upon performing Dialyzer and xref checks concurrently
14 | - Dialyzer-related warnings on recent versions of `rebar3`
15 | - compiler warnings when running tests on OTP versions post- `get_stacktrace()` deprecation
16 |
17 | ## [1.2.0] - 2020-05-26
18 | ### Removed
19 | - compatibility with OTP 18
20 |
21 | ### Fixed
22 | - test cases that broke under OTP 23
23 | - library version mentioned in documentation example
24 |
25 | ## [1.1.2] - 2019-11-11
26 | ### Changed
27 | - generated documentation as to (tentatively) make it prettier
28 |
29 | ## [1.1.1] - 2019-06-20
30 | ### Fixed
31 | - library version mentioned in documentation example
32 |
33 | ## [1.1.0] - 2019-06-13
34 | ### Removed
35 | - OTP 17 support
36 |
37 | ## [1.0.2] - 2019-01-19
38 | ### Fixed
39 | - unwarranted import of rebar3_hex plugin in library consumers
40 |
41 | ## [1.0.1] - 2018-07-11
42 | ### Fixed
43 | - generation of unsafe stacktrace variables upon the existence of
44 | multiple try-catch blocks, within a function, with the same hierarchy
45 | (and equivalent but more contrived cases.)
46 | E.g, in the following transformed code:
47 | ```
48 | function() ->
49 | try error(foobar)
50 | catch
51 | error:foobar:StacktraceCompat444353487_1 ->
52 | {foobar, StacktraceCompat444353487_1}
53 | end,
54 |
55 | try error(foobar)
56 | catch
57 | error:foobar:StacktraceCompat444353487_1 ->
58 | % ^^ This unsafe use of 'StacktraceCompat444353487_1'
59 | % no longer occurs; the generated variable name
60 | % would now be 'StacktraceCompat444353487_2'.
61 | {foobar, StacktraceCompat444353487_1}
62 | end.
63 | ```
64 |
65 | ## [1.0.0] - 2018-06-23
66 | ### Added
67 | - ability of replacing most erlang:get_stacktrace() instances with variable captures in OTP 21+
68 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2021 Guilherme Andrade
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | REBAR3_URL=https://s3.amazonaws.com/rebar3/rebar3
2 |
3 | ifeq ($(wildcard rebar3),rebar3)
4 | REBAR3 = $(CURDIR)/rebar3
5 | endif
6 |
7 | ifdef RUNNING_ON_CI
8 | REBAR3 = ./rebar3
9 | else
10 | REBAR3 ?= $(shell test -e `which rebar3` 2>/dev/null && which rebar3 || echo "./rebar3")
11 | endif
12 |
13 | ifeq ($(REBAR3),)
14 | REBAR3 = $(CURDIR)/rebar3
15 | endif
16 |
17 | .PHONY: all build clean check dialyzer xref test cover console doc publish
18 |
19 | .NOTPARALLEL: check
20 |
21 | all: build
22 |
23 | build: $(REBAR3)
24 | @$(REBAR3) compile
25 |
26 | $(REBAR3):
27 | wget $(REBAR3_URL) || curl -Lo rebar3 $(REBAR3_URL)
28 | @chmod a+x rebar3
29 |
30 | clean: $(REBAR3)
31 | @$(REBAR3) clean
32 |
33 | check: dialyzer xref
34 |
35 | dialyzer: $(REBAR3)
36 | @$(REBAR3) dialyzer
37 |
38 | xref: $(REBAR3)
39 | @$(REBAR3) xref
40 |
41 | test: $(REBAR3)
42 | @$(REBAR3) as test ct
43 |
44 | cover: test
45 | @$(REBAR3) as test cover
46 |
47 | console: $(REBAR3)
48 | @$(REBAR3) as development shell --apps stacktrace_compat
49 |
50 | doc: $(REBAR3)
51 | @$(REBAR3) edoc
52 |
53 | README.md: doc
54 | # non-portable dirty hack follows (pandoc 2.11.0.4 used)
55 | # gfm: "github-flavoured markdown"
56 | @pandoc --from html --to gfm doc/overview-summary.html -o README.md
57 | @tail -n +11 <"README.md" >"README.md_"
58 | @head -n -12 <"README.md_" >"README.md"
59 | @tail -n 2 <"README.md_" >>"README.md"
60 | @rm "README.md_"
61 |
62 | publish: $(REBAR3)
63 | @$(REBAR3) as publishing hex publish
64 | @$(REBAR3) as publishing hex docs
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # stacktrace\_compat
2 |
3 | [](https://hex.pm/packages/stacktrace_compat)
4 |
5 | Given the upcoming release of Erlang/OTP 24, `stacktrace_compat` is no
6 | longer maintained as of April 2nd, 2021.
7 |
8 | ⚠️ **You should discontinue any use of this library** unless you
9 | strictly need to maintain compatibility with Erlang/OTP versions older
10 | than 21 (released back in 2018.)
11 |
12 | ##### How do I do that?
13 |
14 | Start by:
15 |
16 | 1. removing, from `rebar.config`:
17 | - under erl\_opts, `{parse_transform, stacktrace_transform}`
18 | - under deps, `stacktrace_compat`
19 | 2. removing, from `your_app.app.src`:
20 | - under applications, `stacktrace_compat` (it needn't be there,
21 | but it may be)
22 |
23 | ..and then:
24 |
25 | 1. search your code for calls to `erlang:get_stacktrace()`;
26 | 2. replace them with [the current
27 | syntax](https://erlang.org/doc/reference_manual/expressions.html#try)
28 | for capturing stacktraces.
29 |
30 | ##### History
31 |
32 | `stacktrace_compat` defined a parse transform (`stacktrace_transform`)
33 | which, when applied to modules on OTP 21+, replaced calls to
34 | `erlang:get_stacktrace()` with instances of the stacktrace binding that
35 | was to be captured on the closest catch pattern up the [abstract syntax
36 | tree](http://erlang.org/doc/man/erl_syntax.html) (within the same named
37 | function.)
38 |
39 | If no binding had been defined, a generated name would have been used
40 | that was likely to be conflict free.
41 |
42 | If no catch pattern was found, no replacement was made.
43 |
44 | -----
45 |
46 | *Generated by EDoc*
47 |
--------------------------------------------------------------------------------
/doc/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 |
--------------------------------------------------------------------------------
/doc/custom_stylesheet.css:
--------------------------------------------------------------------------------
1 | /* copied and modified from standard EDoc style sheet */
2 | body {
3 | font-family: Verdana, Arial, Helvetica, sans-serif;
4 | margin-left: .25in;
5 | margin-right: .2in;
6 | margin-top: 0.2in;
7 | margin-bottom: 0.2in;
8 | color: #010d2c;
9 | background-color: #f9f9f9;
10 | max-width: 1024px;
11 | }
12 | h1,h2 {
13 | }
14 | div.navbar, h2.indextitle, h3.function, h3.typedecl {
15 | background-color: #e8e8e8;
16 | }
17 | div.navbar, h2.indextitle {
18 | background-image: linear-gradient(to right, #e8e8e8, #f9f9f9);
19 | }
20 | div.navbar {
21 | padding: 0.2em;
22 | border-radius: 3px;
23 | }
24 | h2.indextitle {
25 | padding: 0.4em;
26 | border-radius: 3px;
27 | }
28 | h3.function, h3.typedecl {
29 | display: inline;
30 | }
31 | div.spec {
32 | margin-left: 2em;
33 | background-color: #eeeeee;
34 | border-radius: 3px;
35 | }
36 | a.module {
37 | text-decoration:none
38 | }
39 | a.module:hover {
40 | background-color: #eeeeee;
41 | }
42 | ul.definitions {
43 | list-style-type: none;
44 | }
45 | ul.index {
46 | list-style-type: none;
47 | background-color: #eeeeee;
48 | }
49 |
50 | /*
51 | * Minor style tweaks
52 | */
53 | ul {
54 | list-style-type: disc;
55 | }
56 | table {
57 | border-collapse: collapse;
58 | }
59 | td {
60 | padding: 3
61 | }
62 |
63 | /*
64 | * Extra style tweaks
65 | */
66 | code, pre {
67 | background-color: #ececec;
68 | font: Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;
69 | }
70 | code {
71 | padding-left: 2px;
72 | padding-right: 2px;
73 | }
74 | pre {
75 | color: #00000;
76 | padding: 5px;
77 | border: 0.5px dotted grey;
78 | border-radius: 2px;
79 | }
80 |
--------------------------------------------------------------------------------
/doc/overview.edoc:
--------------------------------------------------------------------------------
1 | @title stacktrace_compat
2 | @doc
3 |
4 |
5 |
6 |
7 | Given the upcoming release of Erlang/OTP 24, `stacktrace_compat' is no longer
8 | maintained as of April 2nd, 2021.
9 |
10 | ⚠️ You should discontinue any use of this library
11 | unless you strictly need to maintain compatibility with Erlang/OTP versions
12 | older than 21 (released back in 2018.)
13 |
14 |
How do I do that?
15 |
16 | Start by:
17 |
18 | - removing, from `rebar.config':
19 |
20 | - under erl_opts, `{parse_transform, stacktrace_transform}'
21 | - under deps, `stacktrace_compat'
22 |
23 |
24 | - removing, from `your_app.app.src':
25 |
26 | - under applications, `stacktrace_compat' (it needn't be there, but it may be)
27 |
28 |
29 |
30 |
31 | ..and then:
32 |
33 | - search your code for calls to `erlang:get_stacktrace()';
34 | - replace them
35 | with the current syntax
36 | for capturing stacktraces.
37 |
38 |
39 |
40 | History
41 |
42 | `stacktrace_compat' defined a parse transform (`stacktrace_transform') which, when
43 | applied to modules on OTP 21+, replaced calls to `erlang:get_stacktrace()' with instances
44 | of the stacktrace binding that was to be captured on the closest catch pattern up the
45 | abstract syntax tree
46 | (within the same named function.)
47 |
48 | If no binding had been defined, a generated name would have been used that was likely to be conflict free.
49 |
50 | If no catch pattern was found, no replacement was made.
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | {erl_opts,
2 | [debug_info,
3 | warn_export_all,
4 | warn_export_vars,
5 | warn_missing_spec,
6 | warn_obsolete_guards,
7 | warn_shadow_vars,
8 | warn_unused_import,
9 | %warnings_as_errors,
10 | {platform_define, "^2[1-9]", 'POST_OTP_20'},
11 | {platform_define, "^[3-9]", 'POST_OTP_20'},
12 | {platform_define, "^2[3-9]", 'POST_OTP_22'},
13 | {platform_define, "^[3-9]", 'POST_OTP_22'},
14 | {platform_define, "^2[4-9]", 'POST_OTP_23'},
15 | {platform_define, "^[3-9]", 'POST_OTP_23'}
16 | ]}.
17 |
18 | {minimum_otp_vsn, "19"}.
19 |
20 | {dialyzer,
21 | [{plt_include_all_deps, true},
22 | {warnings,
23 | [unmatched_returns,
24 | error_handling,
25 | race_conditions,
26 | underspecs
27 | ]}
28 | ]}.
29 |
30 | {xref_checks,
31 | [undefined_function_calls,
32 | undefined_functions,
33 | locals_not_used,
34 | exports_not_used,
35 | deprecated_function_calls,
36 | deprecated_functions
37 | ]}.
38 |
39 | {profiles,
40 | [{development,
41 | [{erl_opts,
42 | [nowarn_missing_spec,
43 | nowarnings_as_errors]}
44 | ]},
45 |
46 | {publishing,
47 | [{plugins,
48 | [{rebar3_hex, "6.10.3"}
49 | ]}
50 | ]},
51 |
52 | {test,
53 | [{erl_opts,
54 | [debug_info,
55 | nowarn_export_all,
56 | nowarn_missing_spec,
57 | nowarnings_as_errors]}
58 | ]}
59 | ]}.
60 |
61 | {cover_enabled, true}.
62 |
63 | {edoc_opts,
64 | [{stylesheet_file, "doc/custom_stylesheet.css"}
65 | ]}.
66 |
--------------------------------------------------------------------------------
/rebar.lock:
--------------------------------------------------------------------------------
1 | [].
2 |
--------------------------------------------------------------------------------
/src/stacktrace_compat.app.src:
--------------------------------------------------------------------------------
1 | {application, stacktrace_compat,
2 | [{description, ":get_stacktrace() compatibility in OTP 21+"},
3 | {vsn, "git"},
4 | {registered, []},
5 | {applications,
6 | [kernel,
7 | stdlib
8 | ]},
9 | {env,[]},
10 | {modules, []},
11 |
12 | {licenses, ["MIT"]},
13 | {links, [{"GitHub", "https://github.com/g-andrade/stacktrace_compat"},
14 | {"GitLab", "https://gitlab.com/g-andrade/stacktrace_compat"}
15 | ]}
16 | ]}.
17 |
--------------------------------------------------------------------------------
/src/stacktrace_transform.erl:
--------------------------------------------------------------------------------
1 | %% @copyright (c) 2018-2021 Guilherme Andrade
2 | %% @private
3 | %%
4 | %% Permission is hereby granted, free of charge, to any person obtaining a
5 | %% copy of this software and associated documentation files (the "Software"),
6 | %% to deal in the Software without restriction, including without limitation
7 | %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 | %% and/or sell copies of the Software, and to permit persons to whom the
9 | %% Software is furnished to do so, subject to the following conditions:
10 | %%
11 | %% The above copyright notice and this permission notice shall be included in
12 | %% all copies or substantial portions of the Software.
13 | %%
14 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 | %% DEALINGS IN THE SOFTWARE.
21 |
22 | -module(stacktrace_transform).
23 |
24 | -deprecated(module).
25 |
26 | %%-------------------------------------------------------------------
27 | %% API Function Exports
28 | %%-------------------------------------------------------------------
29 |
30 | -export([parse_transform/2]).
31 |
32 | -ignore_xref([parse_transform/2]).
33 |
34 | %%-------------------------------------------------------------------
35 | %% API Function Definitions
36 | %%-------------------------------------------------------------------
37 |
38 | -spec parse_transform(erl_syntax:forms(), []) -> erl_syntax:forms().
39 |
40 | -ifdef(POST_OTP_20).
41 | parse_transform(AST, _Options) ->
42 | %write_terms("ast_before.txt", AST),
43 | MappedAST = lists:map(fun map_ast_statement/1, AST),
44 | %write_terms("ast_after.txt", MappedAST),
45 | MappedAST.
46 | -else.
47 | parse_transform(AST, _Options) ->
48 | AST.
49 | -endif.
50 |
51 | %%-------------------------------------------------------------------
52 | %% Record and Type Definitions
53 | %%-------------------------------------------------------------------
54 |
55 | -ifdef(POST_OTP_20).
56 | -record(state, {
57 | % amount of assigned stacktrace vars within a function (used or not)
58 | var_counter :: non_neg_integer(),
59 | % stacktrace vars within nested try catch blocks (assigned or not)
60 | var_stack :: [var()]
61 | }).
62 |
63 | -record(var, {
64 | name :: atom(),
65 | used :: boolean()
66 | }).
67 | -type var() :: #var{}.
68 | -endif.
69 |
70 | %%-------------------------------------------------------------------
71 | %% Internal Function Definitions
72 | %%-------------------------------------------------------------------
73 |
74 | -ifdef(POST_OTP_20).
75 | map_ast_statement({function, Line, Name, Arity, Clauses}) ->
76 | InitialState =
77 | #state{
78 | var_counter = 0,
79 | var_stack = []
80 | },
81 | MappedClauses = [element(1, walk_statements(Clause, InitialState))
82 | || Clause <- Clauses],
83 | {function, Line, Name, Arity, MappedClauses};
84 | map_ast_statement(Statement) ->
85 | Statement.
86 |
87 | walk_statements({'try', Line, % try
88 | Expression, % expression
89 | ResultPatterns, % of ...
90 | CatchPatterns, % catch ...
91 | AfterExpressions % after ...
92 | },
93 | State) ->
94 | {MappedExpression, State2} = walk_statements(Expression, State),
95 | {MappedResultPatterns, State3} = walk_statements(ResultPatterns, State2),
96 | {MappedCatchPatterns, State4} =
97 | lists:mapfoldl(fun mapfoldl_catch_pattern/2, State3, CatchPatterns),
98 | {MappedAfterExpressions, State5} = walk_statements(AfterExpressions, State4),
99 | {{'try',
100 | Line,
101 | MappedExpression,
102 | MappedResultPatterns,
103 | MappedCatchPatterns,
104 | MappedAfterExpressions
105 | },
106 | State5};
107 | walk_statements({call, Line,
108 | % erlang:get_stacktrace()
109 | {remote, _RemoteLine,
110 | {atom, _ModuleLine, erlang},
111 | {atom, _FunctionLine, get_stacktrace}},
112 | [] = _Args} = Statement,
113 | State) ->
114 | case State#state.var_stack of
115 | [Var | StackTail] ->
116 | % replace call to erlang:get_stacktrace() with latest assigned stacktrace var
117 | MappedStatement = {var, Line, Var#var.name},
118 | UpdatedVar = Var#var{ used = true },
119 | UpdatedState = State#state{ var_stack = [UpdatedVar | StackTail] },
120 | {MappedStatement, UpdatedState};
121 | [] ->
122 | % no vars available for replacement
123 | {Statement, State}
124 | end;
125 | walk_statements(Statement, State) when is_tuple(Statement) ->
126 | % very lazy way of walking the whole thing without explicit patterning
127 | % of all children formats
128 | StatementParts = tuple_to_list(Statement),
129 | {MappedStatementParts, UpdatedState} =
130 | walk_statements(StatementParts, State),
131 | MappedStatement = list_to_tuple(MappedStatementParts),
132 | {MappedStatement, UpdatedState};
133 | walk_statements(Statements, State) when is_list(Statements) ->
134 | lists:mapfoldl(fun walk_statements/2, State, Statements);
135 | walk_statements(StatementPart, State) ->
136 | {StatementPart, State}.
137 |
138 | mapfoldl_catch_pattern({clause, Line,
139 | % catch Class:Reason:Stacktrace?
140 | [{tuple, TupleLine,
141 | [ClassExpression,
142 | ReasonExpression,
143 | StExpression
144 | ]}],
145 | % when ...
146 | Guards,
147 | % ->
148 | Body
149 | },
150 | State) ->
151 |
152 | {MappedStExpression, FinalMappedBody, UpdatedState} =
153 | case StExpression of
154 | {var, StExpressionLine, '_'} ->
155 | % Let's tentatively assign stacktrace to a var
156 | VarCounter = State#state.var_counter,
157 | VarStack = State#state.var_stack,
158 | VarCounter2 = VarCounter + 1,
159 | NewVarName = generate_st_var(VarCounter2),
160 | NewVar = #var{ name = NewVarName, used = false },
161 | VarStack2 = [NewVar | VarStack],
162 | State2 = State#state{ var_counter = VarCounter2, var_stack = VarStack2 },
163 | {MappedBody, State3} = walk_statements(Body, State2),
164 |
165 | case State3#state.var_stack of
166 | [#var{ name = NewVarName, used = false } | VarStack] ->
167 | % Stacktrace var was not used
168 | {StExpression, MappedBody,
169 | State3#state{ var_stack = VarStack }};
170 | [#var{ name = NewVarName, used = true } | VarStack] ->
171 | % Stacktrace var was used
172 | {{var, StExpressionLine, NewVarName}, MappedBody,
173 | State3#state{ var_stack = VarStack }}
174 | end;
175 | {var, _StExpressionLine, ExplicitStVar} ->
176 | % Stacktrace has been explicitly assigned
177 | VarStack = State#state.var_stack,
178 | ExplicitVar = #var{ name = ExplicitStVar, used = false },
179 | VarStack2 = [ExplicitVar | VarStack],
180 | State2 = State#state{ var_stack = VarStack2 },
181 | {MappedBody, State3} = walk_statements(Body, State2),
182 | [#var{} | VarStack] = State3#state.var_stack,
183 | {StExpression, MappedBody,
184 | State3#state{ var_stack = VarStack }}
185 | end,
186 |
187 | %%%%%%%
188 | MappedClause =
189 | {clause, Line,
190 | % catch Class:Reason:Stacktrace
191 | [{tuple, TupleLine,
192 | [ClassExpression,
193 | ReasonExpression,
194 | MappedStExpression
195 | ]}],
196 | % when ...
197 | Guards,
198 | % ->
199 | FinalMappedBody
200 | },
201 | {MappedClause, UpdatedState}.
202 |
203 | generate_st_var(Nr) ->
204 | Prefix = "StacktraceCompat444353487_",
205 | Suffix = integer_to_list(Nr),
206 | list_to_atom(Prefix ++ Suffix).
207 |
208 | %write_terms(FilenameSuffix, List) ->
209 | % {attribute, _Line, module, Module} = lists:keyfind(module, 3, List),
210 | % Filename = atom_to_list(Module) ++ "." ++ FilenameSuffix,
211 | % Format = fun(Term) -> io_lib:format("~tp.~n", [Term]) end,
212 | % Text = lists:map(Format, List),
213 | % file:write_file(Filename, Text).
214 | -endif.
215 |
--------------------------------------------------------------------------------
/test/stacktrace_compat_SUITE.erl:
--------------------------------------------------------------------------------
1 | %% Copyright (c) 2018-2021 Guilherme Andrade
2 | %%
3 | %% Permission is hereby granted, free of charge, to any person obtaining a
4 | %% copy of this software and associated documentation files (the "Software"),
5 | %% to deal in the Software without restriction, including without limitation
6 | %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | %% and/or sell copies of the Software, and to permit persons to whom the
8 | %% Software is furnished to do so, subject to the following conditions:
9 | %%
10 | %% The above copyright notice and this permission notice shall be included in
11 | %% all copies or substantial portions of the Software.
12 | %%
13 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO WORK SHALL THE
16 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | %% DEALINGS IN THE SOFTWARE.
20 |
21 | -module(stacktrace_compat_SUITE).
22 | -compile(export_all).
23 |
24 | -include_lib("eunit/include/eunit.hrl").
25 |
26 | %% ------------------------------------------------------------------
27 | %% Boilerplate
28 | %% ------------------------------------------------------------------
29 |
30 | all() ->
31 | [{group, GroupName} || {GroupName, _Options, _TestCases} <- groups()].
32 |
33 | groups() ->
34 | GroupNames = [individual_tests],
35 | [{GroupName, [], individual_test_cases()} || GroupName <- GroupNames].
36 |
37 | individual_test_cases() ->
38 | ModuleInfo = ?MODULE:module_info(),
39 | {exports, Exports} = lists:keyfind(exports, 1, ModuleInfo),
40 | [Name || {Name, 1} <- Exports, lists:suffix("_test", atom_to_list(Name))].
41 |
42 | %% ------------------------------------------------------------------
43 | %% Initialization
44 | %% ------------------------------------------------------------------
45 |
46 | init_per_group(_Name, Config) ->
47 | {ok, _} = application:ensure_all_started(sasl),
48 | {ok, _} = application:ensure_all_started(stacktrace_compat),
49 | Config.
50 |
51 | end_per_group(_Name, Config) ->
52 | Config.
53 |
54 | %% ------------------------------------------------------------------
55 | %% Definition
56 | %% ------------------------------------------------------------------
57 |
58 | naked_capture_test(_Config) ->
59 | assert_expected_result_for(raise, naked_capture).
60 |
61 | throw_capture_pattern_test(_Config) ->
62 | assert_expected_result_for(raise, throw_capture_pattern).
63 |
64 | capture_after_variable_export_test(_Config) ->
65 | assert_expected_result_for(raise, capture_after_variable_export).
66 |
67 | no_capture_test(_Config) ->
68 | assert_expected_result_for(raise, no_capture).
69 |
70 | function_capture_test(_Config) ->
71 | assert_expected_result_for(raise, function_capture).
72 |
73 | nested_function_capture_test(_Config) ->
74 | assert_expected_result_for(raise, nested_function_capture).
75 |
76 | nested_function_capture_with_both_test(_Config) ->
77 | assert_expected_result_for(raise, nested_function_capture_with_both).
78 |
79 | function_capture_in_expression_test(_Config) ->
80 | assert_expected_result_for(raise, function_capture_in_expression).
81 |
82 | function_capture_in_result_handler_test(_Config) ->
83 | assert_expected_result_for(raise, function_capture_in_result_handler).
84 |
85 | helper_capture_test(_Config) ->
86 | assert_expected_result_for(raise, helper_capture).
87 |
88 | -ifdef(POST_OTP_20).
89 | unused_var_with_function_capture_test(_Config) ->
90 | assert_expected_result_for(raise21, unused_var_with_function_capture).
91 |
92 | var_capture_test(_Config) ->
93 | assert_expected_result_for(raise21, var_capture).
94 |
95 | nested_var_capture_with_both_test(_Config) ->
96 | assert_expected_result_for(raise21, nested_var_capture_with_both).
97 | -endif.
98 |
99 | %% ------------------------------------------------------------------
100 | %% Internal
101 | %% ------------------------------------------------------------------
102 |
103 | -ifdef(POST_OTP_23).
104 | assert_expected_result_for(Function, CaseName) ->
105 | {ok, Cwd} = file:get_cwd(),
106 | TestModuleBeamPath = filename:join(Cwd, "test_module"),
107 | ct:pal("TestModuleBeamPath: ~p", [TestModuleBeamPath]),
108 |
109 | compile_and_load_test_module([]),
110 | assert_test_module_has_no_transform(),
111 | WithoutTransformST
112 | = case lists:member(CaseName, [no_capture,
113 | var_capture,
114 | nested_var_capture_with_both]) of
115 | true ->
116 | {CaseName, StacktraceBefore} = test_module:Function(CaseName),
117 | StacktraceBefore;
118 | false ->
119 | ?assertError(undef, test_module:Function(CaseName)),
120 | crash_following_dropped_support
121 | end,
122 |
123 | compile_and_load_test_module([{parse_transform, stacktrace_transform}]),
124 | assert_test_module_has_transform(),
125 | WithTransformST
126 | = case lists:member(CaseName, [naked_capture,
127 | helper_capture]) of
128 | true ->
129 | ?assertError(undef, test_module:Function(CaseName)),
130 | crash_following_dropped_support;
131 | false ->
132 | {CaseName, StacktraceAfter} = test_module:Function(CaseName),
133 | StacktraceAfter
134 | end,
135 |
136 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST).
137 |
138 | -else.
139 | assert_expected_result_for(Function, CaseName) ->
140 | {ok, Cwd} = file:get_cwd(),
141 | TestModuleBeamPath = filename:join(Cwd, "test_module"),
142 | ct:pal("TestModuleBeamPath: ~p", [TestModuleBeamPath]),
143 |
144 | compile_and_load_test_module([]),
145 | assert_test_module_has_no_transform(),
146 | {CaseName, WithoutTransformST} = test_module:Function(CaseName),
147 |
148 | compile_and_load_test_module([{parse_transform, stacktrace_transform}]),
149 | assert_test_module_has_transform(),
150 | {CaseName, WithTransformST} = test_module:Function(CaseName),
151 |
152 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST).
153 |
154 | -endif. % -idef(POST_OTP_23).
155 |
156 | compile_and_load_test_module(ExtraOptions) ->
157 | _ = code:purge(test_module),
158 | {ok, test_module, Beam} = compile_test_module(ExtraOptions),
159 | {module, test_module} = code:load_binary(test_module, "test_module.erl", Beam),
160 | ok.
161 |
162 | compile_test_module(ExtraOptions) ->
163 | Options =
164 | [binary,
165 | report_errors,
166 | report_warnings,
167 | debug_info,
168 | {d, 'COMPILING_WITHIN_TEST_SUITE'}
169 | | case erlang:system_info(otp_release) of
170 | [$2,V|_] when V >= $1, V =< $9 ->
171 | [{d, 'POST_OTP_20'}];
172 | _ ->
173 | []
174 | end
175 | ]
176 | ++ ExtraOptions,
177 | TestModulePath = "../../lib/stacktrace_compat/test/test_module.erl",
178 | compile:file(TestModulePath, Options).
179 |
180 | assert_test_module_has_no_transform() ->
181 | CompileAttr = test_module:module_info(compile),
182 | CompileOptions = proplists:get_value(options, CompileAttr, []),
183 | ?assertNot(lists:member({parse_transform, stacktrace_transform}, CompileOptions)).
184 |
185 | assert_test_module_has_transform() ->
186 | CompileAttr = test_module:module_info(compile),
187 | CompileOptions = proplists:get_value(options, CompileAttr, []),
188 | ?assert(lists:member({parse_transform, stacktrace_transform}, CompileOptions)).
189 |
190 | -ifdef(POST_OTP_23).
191 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST)
192 | when Function =:= raise, CaseName =:= no_capture ->
193 | ?assertEqual([], WithoutTransformST),
194 | ?assertEqual([], WithTransformST);
195 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST)
196 | when Function =:= raise, CaseName =:= naked_capture;
197 | Function =:= raise, CaseName =:= helper_capture ->
198 | ?assertEqual(crash_following_dropped_support, WithoutTransformST),
199 | ?assertEqual(crash_following_dropped_support, WithTransformST);
200 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST)
201 | when Function =:= raise21, (CaseName =:= var_capture orelse
202 | CaseName =:= nested_var_capture_with_both) ->
203 | assert_stacktrace_equivalence(WithoutTransformST, WithTransformST);
204 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST)
205 | when Function =/= raise; CaseName =/= naked_capture ->
206 | ?assertEqual(crash_following_dropped_support, WithoutTransformST),
207 | ?assertMatch(
208 | [{_Module, _Function, _ArtityOrArgs, _Info} | _],
209 | WithTransformST).
210 |
211 | -else.
212 | -ifdef(POST_OTP_22).
213 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST)
214 | when Function =:= raise, (CaseName =/= naked_capture andalso
215 | CaseName =/= no_capture andalso
216 | CaseName =/= helper_capture);
217 | Function =:= raise21, (CaseName =:= unused_var_with_function_capture) ->
218 | % OTP 23 made `:get_stacktrace()` always return an empty list;
219 | % OTP 24 will remove it entirely.
220 | ?assertEqual([], WithoutTransformST),
221 | ?assertMatch(
222 | [{_Module, _Function, _ArtityOrArgs, _Info} | _],
223 | WithTransformST);
224 | assert_expected_stacktraces(Function, CaseName, WithoutTransformST, WithTransformST)
225 | when Function =:= raise21, (CaseName =:= nested_var_capture_with_both orelse
226 | CaseName =:= var_capture) ->
227 | assert_stacktrace_equivalence(WithoutTransformST, WithTransformST);
228 | assert_expected_stacktraces(_Function, _CaseName, WithoutTransformST, WithTransformST) ->
229 | ?assertEqual([], WithoutTransformST),
230 | ?assertEqual([], WithTransformST).
231 |
232 | -else.
233 | assert_expected_stacktraces(_, _CaseName, WithoutTransformST, WithTransformST) ->
234 | assert_stacktrace_equivalence(WithoutTransformST, WithTransformST).
235 |
236 | -endif. % -ifdef(POST_OTP_22)
237 | -endif. % -ifdef(POST_OTP_23).
238 |
239 | assert_stacktrace_equivalence([{ModuleA, FunctionA, ArityOrArgsA, _InfoA} | NextA],
240 | [{ModuleB, FunctionB, ArityOrArgsB, _InfoB} | NextB]) ->
241 | ?assertEqual(ModuleA, ModuleB),
242 | ?assertEqual(FunctionA, FunctionB),
243 | ?assertEqual(ArityOrArgsA, ArityOrArgsB),
244 | assert_stacktrace_equivalence(NextA, NextB);
245 | assert_stacktrace_equivalence([], []) ->
246 | ok.
247 |
--------------------------------------------------------------------------------
/test/test_module.erl:
--------------------------------------------------------------------------------
1 | %% Copyright (c) 2018-2021 Guilherme Andrade
2 | %%
3 | %% Permission is hereby granted, free of charge, to any person obtaining a
4 | %% copy of this software and associated documentation files (the "Software"),
5 | %% to deal in the Software without restriction, including without limitation
6 | %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | %% and/or sell copies of the Software, and to permit persons to whom the
8 | %% Software is furnished to do so, subject to the following conditions:
9 | %%
10 | %% The above copyright notice and this permission notice shall be included in
11 | %% all copies or substantial portions of the Software.
12 | %%
13 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO WORK SHALL THE
16 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 | %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | %% DEALINGS IN THE SOFTWARE.
20 |
21 | -module(test_module).
22 |
23 | % Keep `rebar3' away from warning-inducing code
24 | % on recent versions of OTP (due to
25 | % `erlang:get_stacktrace()' deprecation.)
26 | %
27 | -ifdef(COMPILING_WITHIN_TEST_SUITE).
28 |
29 | %% ------------------------------------------------------------------
30 | %% API function Exports
31 | %% ------------------------------------------------------------------
32 |
33 | -export([raise/1]).
34 |
35 | -ifdef(POST_OTP_20).
36 | -export([raise21/1]).
37 | -endif.
38 |
39 | %% ------------------------------------------------------------------
40 | %% API Function Definitions
41 | %% ------------------------------------------------------------------
42 |
43 | raise(naked_capture) ->
44 | {naked_capture, erlang:get_stacktrace()};
45 | raise(throw_capture_pattern) ->
46 | try
47 | throw(throw_capture_pattern)
48 | catch
49 | throw_capture_pattern ->
50 | {throw_capture_pattern, erlang:get_stacktrace()}
51 | end;
52 | raise(capture_after_variable_export) ->
53 | try
54 | error(capture_after_variable_export)
55 | catch
56 | error:capture_after_variable_export ->
57 | {capture_after_variable_export, erlang:get_stacktrace()}
58 | end,
59 |
60 | try
61 | error(capture_after_variable_export)
62 | catch
63 | error:capture_after_variable_export ->
64 | {capture_after_variable_export, erlang:get_stacktrace()}
65 | end;
66 | raise(Reason) ->
67 | try
68 | error(Reason)
69 | catch
70 | error:no_capture ->
71 | {no_capture, []};
72 | error:function_capture ->
73 | {function_capture, erlang:get_stacktrace()};
74 | error:nested_function_capture ->
75 | try
76 | error({nested, Reason})
77 | catch
78 | error:{nested, Reason} ->
79 | {Reason, erlang:get_stacktrace()}
80 | end;
81 | error:nested_function_capture_with_both ->
82 | Stacktrace1 = erlang:get_stacktrace(),
83 | try
84 | error({nested, Reason})
85 | catch
86 | error:{nested, Reason} ->
87 | Stacktrace2 = erlang:get_stacktrace(),
88 | {Reason, Stacktrace1 ++ Stacktrace2}
89 | end;
90 | error:function_capture_in_expression ->
91 | try
92 | try
93 | error(Reason)
94 | catch
95 | error:Reason ->
96 | {Reason, erlang:get_stacktrace()}
97 | end
98 | catch
99 | error:Reason ->
100 | no_no
101 | end;
102 | error:function_capture_in_result_handler ->
103 | try 1 of
104 | 1 ->
105 | try
106 | error(Reason)
107 | catch
108 | error:Reason ->
109 | {Reason, erlang:get_stacktrace()}
110 | end
111 | catch
112 | error:Reason ->
113 | no_no
114 | end;
115 | error:helper_capture ->
116 | {helper_capture, helper()}
117 | end.
118 |
119 | -ifdef(POST_OTP_20).
120 | raise21(Reason) ->
121 | try
122 | error(Reason)
123 | catch
124 | error:unused_var_with_function_capture:Stacktrace ->
125 | {unused_var_with_function_capture, erlang:get_stacktrace()};
126 | error:var_capture:Stacktrace ->
127 | {var_capture, Stacktrace};
128 | error:nested_var_capture_with_both:Stacktrace1 ->
129 | try
130 | error(Reason)
131 | catch
132 | error:Reason:Stacktrace2 ->
133 | {Reason, Stacktrace1 ++ Stacktrace2}
134 | end
135 | end.
136 | -endif.
137 |
138 | helper() ->
139 | erlang:get_stacktrace().
140 |
141 | -endif. % COMPILING_WITHIN_TEST_SUITE
142 |
--------------------------------------------------------------------------------