├── .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://img.shields.io/hexpm/v/stacktrace_compat.svg?style=flat)](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 |
  1. removing, from `rebar.config': 19 | 23 |
  2. 24 |
  3. removing, from `your_app.app.src': 25 | 28 |
  4. 29 |
30 | 31 | ..and then: 32 |
    33 |
  1. search your code for calls to `erlang:get_stacktrace()';
  2. 34 |
  3. replace them 35 | with the current syntax 36 | for capturing stacktraces. 37 |
  4. 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 | --------------------------------------------------------------------------------