diff --git a/.gitignore b/.gitignore index b89b9a2..4932fcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ +*.swp .eunit deps *.o *.beam -*.plt \ No newline at end of file +*.plt +ebin +doc +.vim diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c7b4342 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +EAOP_HOME = . +EBIN = ${EAOP_HOME}/ebin +SRC = ${EAOP_HOME}/src + +MODS = eaop eaopcore eaoputil eaopweaver + +all: compile erl + +erl: + erl -pa ${EBIN} + +ebin: + mkdir ${EBIN} + +compile: ebin ${MODS:%=${EBIN}/%.beam} + +${EBIN}/eaop.beam: ${SRC}/eaop.erl + erlc -o ${EBIN} ${SRC}/eaop.erl + +${EBIN}/eaopcore.beam: ${SRC}/eaopcore.erl + erlc -o ${EBIN} ${SRC}/eaopcore.erl + +${EBIN}/eaoputil.beam: ${SRC}/eaoputil.erl + erlc -o ${EBIN} ${SRC}/eaoputil.erl + +${EBIN}/eaopweaver.beam: ${SRC}/eaopweaver.erl + erlc -o ${EBIN} ${SRC}/eaopweaver.erl +clean: + rm -rf ${EBIN}/*.beam ${EBIN}/erl_crash.dump + rmdir ${EBIN} diff --git a/README.md b/README.md index ea24038..b36edfd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ eaop ==== -Compile time AOP implementation for Erlang +Compile time AOP implementation for Erlang. + +This project was inspired by [ErlAOP](http://sourceforge.net/projects/erlaop/), which was written by Alexei Krasnopolski. Most of the ErlAOP documentation is relevant to this project. + +eaop re-implements the functionality of ErlAOP using [erl_syntax](http://erlang.org/doc/man/erl_syntax.html) and should not be dependent on the abstract forms generated by the Erlang compiler. However, both ErlAOP and eaop are implemented using Erlang's [parse_transform/2](http://www.erlang.org/doc/man/erl_id_trans.html) mechanism, making both of them limited to compile time weaving of code. + +eaop also extends the expressiveness of pointcut definitions, allowing the specification of send and receive operations in Erlang code to be eligible points for code weaving. diff --git a/include/eaopglobals.hrl b/include/eaopglobals.hrl new file mode 100644 index 0000000..d60c0bb --- /dev/null +++ b/include/eaopglobals.hrl @@ -0,0 +1,8 @@ +%% The weaver module. +-define(WEAVER, eaopweaver). + +%% The core module. +-define(CORE, eaopcore). + +%% The utility module. +-define(UTIL, eaoputil). diff --git a/include/unit_testing.hrl b/include/unit_testing.hrl new file mode 100644 index 0000000..1a36125 --- /dev/null +++ b/include/unit_testing.hrl @@ -0,0 +1,6 @@ +%% Commenting NOTEST will remove tests when compiling. Keep this definition before the inclusion of eunit.hrl. +-define(NOTEST, true). + +-ifdef(EUNIT). +-include_lib("eunit/include/eunit.hrl"). +-endif. diff --git a/src/eaop.erl b/src/eaop.erl new file mode 100644 index 0000000..66f18f6 --- /dev/null +++ b/src/eaop.erl @@ -0,0 +1,129 @@ +%% +%% Copyright (C) 2013 by calleja.justin@gmail.com (Justin Calleja) +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% + +%% Author: Justin Calleja +%% Description: This module is an interface to the user of eaop. Compilation is done through this module. +-module(eaop). + +-include("../include/eaopglobals.hrl"). +-include("../include/unit_testing.hrl"). + +%% +%% Exported Functions +%% +%% -export([]). +-compile(export_all). + +%% +%% API Functions +%% + +-spec compile(Srcs::[string()], Configs::[string()]) -> ok. +%% @todo Add the throws which can be thrown by functions executed within this function +%% ( e.g. {ambiguous_ptcut_type, [ptcut_t()]} ). +%% @doc +%% Srcs = list of directory names containing source files and/or file names. +%% Configs = list of directory names containing config files and/or config file names. +%% Compiles the source files (*.erl) explicitly listed or found in the directories listed in Srcs. +%% AOP weaving is done during the compilation stage using the configuration found in the config files +%% explicitly listed or found within the directories listed in Configs. The default file suffix to identify +%% a file as being a config file is .adf. +%% (Note: given any directory, all sub-directories are searched recursively for source/config files to use in +%% the compilation). +compile(Srcs, Configs) -> + compile(Srcs, Configs, []). + +-spec compile(Srcs::[string()], Configs::[string()], Options::[term()]) -> ok. +%% @throws TODO +%% @doc +%% Srcs = list of directory names containing source files and/or file names. +%% Configs = list of directory names containing config files and/or config file names. +%% Options = Options determine the behavior of the compiler as defined in compile:file/2. +%% Additional options include: +%% {config_suffix, Suffix}: used to change the default (.adf) suffix of aspect definition files for this compilation. +%% {print_gen_src, Dir}: this option prints the transformed source files after weaving in the directory Dir. +%% Same as eaop:compile/2 but takes a list of options. +compile([], _, _) -> + ok; +compile(Srcs, Configs, Options) -> + SrcFiles = get_files(Srcs, ".*\.erl$"), + Suffix = + case lists:keyfind(config_suffix, 1, Options) of + false -> + ".*\.adf$"; + {_, ConfigSuffix} -> + ".*\." ++ ConfigSuffix ++ "$" + end, + ConfigFiles = get_files(Configs, Suffix), + Aspects = read_aspects(ConfigFiles), + Opts = ?WEAVER:make_options(Aspects, Options), + compile_files(SrcFiles, Opts). + +%% @doc compile_aspects/2 can be used when you have the aspects themselves and don't need to process .adf files. If building the aspects programmatically, this is probably the function you'd want to use. +compile_aspects(Srcs, Aspects) -> + compile_aspects(Srcs, Aspects, []). + +%% @doc Similar to compile_aspects/2 but with options. +compile_aspects(Srcs, Aspects, Options) -> + SrcFiles = get_files(Srcs, ".*\.erl$"), + Weaver = get_weaver_module(Options), + Opts = Weaver:make_options(Aspects, Options), + compile_files(SrcFiles, Opts). + +get_weaver_module(Options) -> + proplists:get_value(weaver, Options, ?WEAVER). + +-type file_or_dir() :: filelib:filename() | filelib:dirname(). +-spec get_files([FileOrDirName::file_or_dir()], SuffixRegex::string()) -> [filelib:filename()]. +get_files(FilesAndOrDirs, Suffix) -> + lists:append( + [ case filelib:is_dir(FileOrDir) of + true -> + filelib:fold_files(FileOrDir, Suffix, true, fun(F, Acc) -> [F | Acc] end, []); + false -> + [FileOrDir] + end || FileOrDir <- FilesAndOrDirs ]). + +%% Note: the type compile_res() is actually whatever compile:file/2 is capable of returning. +-type compile_res() :: {ok, module()} | {ok, module(), Warnings::[term()]} | {ok, module(), binary()} | {ok, module(), binary(), Warnings::[term()]} | {error, Errors::[term()], Warnings::[term()]} | error. +-spec compile_files(SrcFiles::[file:name()], Options::[?WEAVER:eaop_compile_opt()]) -> [{SrcFile::file:name(), CompileResult::compile_res()}]. +compile_files(SrcFiles, Options) -> + lists:foldl( + fun(S, Results) -> + CompileRes = compile:file(S, Options), + [{S, CompileRes} | Results] + end, [], SrcFiles). + + +%% +%% Local Functions +%% + +-spec read_aspects(ConfigFiles::[file:name()]) -> [?CORE:aspect()]. +read_aspects(ConfigFiles) -> + ?CORE:prepare_aspects_for_weaving(read_globals(ConfigFiles)). + +-spec read_globals(ConfigFiles::[file:name()]) -> ?CORE:globals(). +read_globals(ConfigFiles) -> + ?CORE:get_globals(read_configs(ConfigFiles)). + +-spec read_configs([file:name()]) -> [?CORE:aop_el()]. +read_configs(Files) -> + lists:foldl( + fun(File, Adfs) -> + {ok, Adf} = file:consult(File), + lists:append(Adfs, Adf) + end, [], Files). diff --git a/src/eaopcore.erl b/src/eaopcore.erl new file mode 100644 index 0000000..0130b97 --- /dev/null +++ b/src/eaopcore.erl @@ -0,0 +1,701 @@ +%% +%% Copyright (C) 2013 by calleja.justin@gmail.com (Justin Calleja) +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% + +%% Author: Justin Calleja +%% Description: All other modules are meant to use this module for access/creation/manipulation of the +%% data structures used in this project. + +-module(eaopcore). + +%% -export([get_globals/1, prepare_aspects_for_weaving/1, is_instrumentable_fun/1, exists_send_ptcut/1]). + +-compile(export_all). + +-include("../include/eaopglobals.hrl"). +-include("../include/unit_testing.hrl"). + +-define(casetf(Expr, True, False), case Expr of true -> True; false -> False end). + +-export_type([aop_el/0, aspect/0, globals/0]). +-export_type([advice_t/0]). + +-record(id, {val :: string()}). +-record(mf, {m :: atom(), f :: atom()}). +-record(send_msg, {msg :: string()}). +-record(receive_msg, {msg :: string()}). +-record(functiondef, { + m :: string() | atom(), f :: string() | atom(), a :: string() | integer() +}). +-record(args, { + exp :: string(), condition :: str_undef() +}). +-record(within, {scope :: scope()}). +-record(not_within, {scope :: scope()}). +-record(spawned, {scope :: scope()}). +-record(registered, {names :: [str_atom()]}). +-record(trap_exit, {val = false :: boolean()}). +-record(visibility, {val = global :: visibility()}). +-record(type, {val :: advice_t()}). +-record(ptcuts, {val :: [str_pointcut()]}). + +-record(aspect, {attribs = [] :: [aspect_attr()]}). +-record(advice, {attribs = [] :: [advice_attr()]}). +-record(pointcut, {attribs = [] :: [pointcut_attr()]}). + +-record(globals, { + aspects = [] :: [aspect()], + advices = [] :: [advice()], + pointcuts = [] :: [pointcut()] +}). + +-type advice_t() :: before | 'after' | after_throw | after_final | around. + +-type str_atom() :: string() | atom(). +-type str_byte() :: string() | byte(). +-type str_undef() :: string() | undefined. +-type scope() :: [{Module::str_atom(), Function::str_atom(), Arity::str_byte()}]. +-type visibility() :: local | public | global. +-type str_pointcut() :: string() | pointcut(). +%% -type mf() :: #mf{}. + +-type aspect_attr() :: + #id{} | + advice() | + pointcut(). + +-type advice_attr() :: + #id{} | + #type{} | + #mf{} | + #ptcuts{}. + +%% Note: the following are mutually exclusive: call, send_msg, or receive_msg. They determine the pointcut's type. +%% Note: within, not_within, spawned, registered, trap_exit, and visibility are not yet implemented. They may be used but they will be ignored. +-type pointcut_attr() :: + #id{} | %% The id is used to refer to the pointcut. + #send_msg{} | %% Defines which sent messages to advice. + #receive_msg{} | %% Defines which received messages to advice. + #functiondef{} | %% Defines which function definitions to advise + #args{} | %% Defines the format in which the argument/s of the functiondef/send/recieve need to + %% be in in order for the advice to take place. Optionally, a condition may be + %% specified over the variables defined in the format. + #within{} | %% Defines the scope within which this pointcut applies. Any join points outside + %% of the defined scope which match this pointcut will be ignored. + #not_within{} | %% Defines the scope within which this pointcut is not allowed to apply. + %% Any join points inside the defined scope which match this pointcut will be ignored. + #spawned{} | %% Defines a set of functions. Assuming a join point matches this pointcut, + %% the join point will not be adviced unless the process executing the join point + %% was spawned by one of the functions defined in this set. + #registered{} | %% Defines a set of registered names. Assuming a join point matches this pointcut, + %% the join point will not be adviced unless the process executing the join point is + %% registered under a name in this set. + #trap_exit{} | %% Requires the process executing the join point to be a system process. + %% Assuming a join point matches this pointcut, the join point will not be + %% adviced unless the process executing the join point is trapping exits. + #visibility{}. %% Only applicable to functiondef pointcuts. Determines whether the functiondef being selected must be + %% local to the module, exported from the module (public), or it can be both (global). + +-opaque aspect() :: #aspect{}. +-opaque advice() :: #advice{}. +-opaque pointcut() :: #pointcut{}. + +-opaque aop_el() :: aspect() | advice() | pointcut(). +-opaque aop_attr() :: aspect_attr() | advice_attr() | pointcut_attr(). + +-opaque globals() :: #globals{}. + +%% +%% Exported functions: +%% + +get_advice_module(Advice) when is_record(Advice, advice) -> + Fun = fun_advice_attrib_selector(mf), + case Fun(Advice) of + no_attr -> + undefined; + MFAttr -> + MFAttr#mf.m + end. + +get_advice_function(Advice) when is_record(Advice, advice) -> + Fun = fun_advice_attrib_selector(mf), + case Fun(Advice) of + no_attr -> + undefined; + MFAttr -> + MFAttr#mf.f + end. + +-spec get_applicable_advices(StrModuleName::string(), StrFunctionName::string(), Arity::integer(), Aspects::[aspect()]) -> ApplicableAdvices::[advice()]. +%% @doc Returns the advices in Aspects which are applicable to the function with name StrFunctionName, in the module with name StrModuleName, with an arity of Arity. +%% Applicability is determined by the functiondef attribute in pointcuts. +get_applicable_advices(StrModuleName, StrFunctionName, Arity, Aspects) -> + FdAdvicePointcut = foldl_pointcuts(fun fd_advice_pointcut/4, Aspects), + lists:foldl( + fun({FdAdvice, FdPointcut}, ApplicableAdvicesAcc) -> + case mfa_satisfies_fdpointcut(StrModuleName, StrFunctionName, Arity, FdPointcut) of + true -> + [FdAdvice | ApplicableAdvicesAcc]; + false -> + ApplicableAdvicesAcc + end + end, [], FdAdvicePointcut). + +get_advices_by_type(Advices, Type) -> + Fun = fun_advice_attrib_selector(type), + lists:foldl( + fun(Advice, SameTypeAdvicesAcc) -> + case Fun(Advice) of + no_attr -> + SameTypeAdvicesAcc; + TypeAttr -> + case TypeAttr#type.val == Type of + true -> + [Advice | SameTypeAdvicesAcc]; + false -> + SameTypeAdvicesAcc + end + end + end, [], Advices). + +-spec fd_advice_pointcut(aspect(), advice(), pointcut(), [{advice(), pointcut()}]) -> [{advice(), pointcut()}]. +%% @doc This function is passed to foldl_pointcuts/2. If Pointcut is of type functiondef, {Advice, Pointcut} is appended to the accumulator PointcutAcc. Otherwise the accumulator is returned. +fd_advice_pointcut(_Aspect, Advice, Pointcut, PointcutAcc) -> + Fun = fun_pointcut_attrib_selector(functiondef), + case Fun(Pointcut) of + no_attr -> + PointcutAcc; + _FunctiondefAttr -> + [{Advice, Pointcut} | PointcutAcc] + end. + +mfa_satisfies_fdpointcut(StrModuleName, StrFunctionName, Arity, FdPointcut) -> + Fun = fun_pointcut_attrib_selector(functiondef), + FdAttr = Fun(FdPointcut), + case m_satisfies_fdpointcut(StrModuleName, FdAttr#functiondef.m) of + true -> + case f_satisfies_fdpointcut(StrFunctionName, FdAttr#functiondef.f) of + true -> + a_satisfies_fdpointcut(Arity, FdAttr#functiondef.a); + false -> + false + end; + false -> + false + end. + +m_satisfies_fdpointcut(StrModuleName, FdModule) when is_atom(FdModule) -> + list_to_atom(StrModuleName) == FdModule; +m_satisfies_fdpointcut(StrModuleName, FdModule) when is_list(FdModule) -> + match(StrModuleName, FdModule). + +f_satisfies_fdpointcut(StrFunctionName, FdFunction) when is_atom(FdFunction) -> + list_to_atom(StrFunctionName) == FdFunction; +f_satisfies_fdpointcut(StrFunctionName, FdFunction) when is_list(FdFunction) -> + match(StrFunctionName, FdFunction). + +-spec a_satisfies_fdpointcut(Arity::integer(), FdArity::integer()) -> boolean(); + (integer(), string()) -> boolean(). +%% @doc When FdArity is a string(), it is assumed that it is a regex +a_satisfies_fdpointcut(Arity, FdArity) when is_integer(FdArity) -> + Arity == FdArity; +a_satisfies_fdpointcut(Arity, FdArity) when is_list(FdArity) -> + match(integer_to_list(Arity), FdArity). + +match(Str, Regex) -> + {ok, Pattern} = re:compile(Regex), + Options = [notempty,{capture,none}], + re:run(Str, Pattern, Options) == match. + +%% @doc Applies Fun to every pointcut within every advice within every aspect in the given Aspects. +foldl_pointcuts(Fun, Aspects) -> + Result = lists:foldl( + fun(Aspect, AspectAcc) -> + Advices = aadvice(Aspect), + FoldlOverAdvices = lists:foldl( + fun(Advice, AdviceAcc) -> + Pointcuts = advpointcuts(Advice), + FoldlOverPointcuts = lists:foldl( + fun(Pointcut, PointcutAcc) -> + Fun(Aspect, Advice, Pointcut, PointcutAcc) + end, [], Pointcuts), + [FoldlOverPointcuts | AdviceAcc] + end, [], Advices), + [FoldlOverAdvices | AspectAcc] + end, [], Aspects), + lists:flatten(Result). + + +is_aspect(Aspect) -> is_record(Aspect, aspect). + +is_advice(Advice) -> is_record(Advice, advice). + +is_pointcut(Pointcut) -> is_record(Pointcut, pointcut). + +is_aspect_attrib(Attrib) when not is_tuple(Attrib) -> false; +is_aspect_attrib(Attrib) -> lists:member(element(1, Attrib), [id, advice, pointcut]). + +is_advice_attrib(Attrib) when not is_tuple(Attrib) -> false; +is_advice_attrib(Attrib) -> lists:member(element(1, Attrib), [id, type, mf, ptcuts]). + +is_pointcut_attrib(Attrib) when not is_tuple(Attrib) -> false; +is_pointcut_attrib(Attrib) -> lists:member(element(1, Attrib), [id, send_msg, receive_msg, functiondef, args, within, not_within, spawned, registered, trap_exit, visibility]). + +-spec add(AopEl::aop_el(), Attrib::aop_attr()) -> aop_el(); + (AopEl::aop_el(), Attribs::[aop_attr()]) -> aop_el(). +%% @doc Adds Attrib to AopEl if the Attrib is a valid attribute of AopEl, and returns the new aop_el(). +%% Returns AopEl otherwise. Can also handle a list of aop_attr(). In this case, the attributes are added +%% from left to right. +add(AopEl, []) -> + AopEl; +add(AopEl, [Attrib | Attribs]) -> + add(add(AopEl, Attrib), Attribs); +add(#aspect{attribs = AspectAttribs} = Aspect, AspectAttrib) -> + ?casetf(is_aspect_attrib(AspectAttrib), #aspect{attribs = [AspectAttrib | AspectAttribs]}, Aspect); +add(#advice{attribs = AdviceAttribs} = Advice, AdviceAttrib) -> + ?casetf(is_advice_attrib(AdviceAttrib), #advice{attribs = [AdviceAttrib | AdviceAttribs]}, Advice); +add(#pointcut{attribs = PointcutAttribs} = Pointcut, PointcutAttrib) -> + ?casetf(is_pointcut_attrib(PointcutAttrib), #pointcut{attribs = [PointcutAttrib | PointcutAttribs]}, Pointcut). + +-spec aspect() -> aspect(). +aspect() -> #aspect{}. + +-spec advice() -> advice(). +advice() -> #advice{}. + +-spec pointcut() -> pointcut(). +pointcut() -> #pointcut{}. + +aspect(Attribs) when is_list(Attribs) -> + ?casetf(lists:all(fun is_aspect_attrib/1, Attribs), #aspect{attribs = Attribs}, invalid). + +advice(Attribs) when is_list(Attribs) -> + ?casetf(lists:all(fun is_advice_attrib/1, Attribs), #advice{attribs = Attribs}, invalid). + +pointcut(Attribs) when is_list(Attribs) -> + ?casetf(lists:all(fun is_pointcut_attrib/1, Attribs), #pointcut{attribs = Attribs}, invalid). + +id(Id) when is_atom(Id) -> #id{val = atom_to_list(Id)}; +id(Id) when is_integer(Id) -> #id{val = integer_to_list(Id)}; +id(Id) -> #id{val = Id}. + +mf(Module, Function) -> #mf{m = Module, f = Function}. + +send_msg(Msg) -> #send_msg{msg = Msg}. + +receive_msg(Msg) -> #receive_msg{msg = Msg}. + +%% call(Module, Function, Arity) -> #call{m = Module, f = Function, a = Arity}. + +functiondef(Module, Function, Arity) -> #functiondef{m = Module, f = Function, a = Arity}. + +args(Exp, Condition) -> #args{exp = Exp, condition = Condition}. + +within(Scope) -> #within{scope = Scope}. + +not_within(Scope) -> #not_within{scope = Scope}. + +spawned(Scope) -> #spawned{scope = Scope}. + +registered(Names) -> #registered{names = Names}. + +trap_exit(Bool) -> #trap_exit{val = Bool}. + +visibility(Visibility) -> #visibility{val = Visibility}. + +type(Type) -> #type{val = Type}. + +ptcuts(Ptcuts) -> #ptcuts{val = Ptcuts}. + +%% -spec is_instrumentable_fun(Aspect::aspect()) -> fun((Form::erl_parse:abstract_form()) -> boolean()). +%% %% @doc Returns a function which when applied to an abstract_form() determines whether that form needs to be +%% %% instrumented. This decision is taken according to the information encapsulated within the given Aspect. +%% is_instrumentable_fun(Aspect = #aspect{attribs = AspectAttribs}) -> +%% fun({function, _LineNum, FunName, Arity, Clauses}) -> +%% todo; +%% (SendForm = {op, _LineNum, '!', Pid, Msg}) -> +%% %% Get all send_msg attribs in all pointcuts within all advices of given Aspect. +%% %% These send_msg attribs have a value - a string() encoding the format in which a message must be in order for it to be instrumented. +%% %% Pass these values to SRMATCHER and if the matcher returns true at least once, then SendForm needs to be instrumented. +%% Advices = aadvice(AspectAttribs), +%% FunAttribSelector = fun_pointcut_attrib_selector(send_msg), +%% SendMsgAttribs = apply_attr_selector(FunAttribSelector, Advices), +%% lists:any( +%% fun(#send_msg{msg = SendMsg}) -> +%% ?SRMATCHER:send_matcher(Msg, SendMsg, [srmatcher:send_form(SendForm)]) +%% end, SendMsgAttribs); +%% (ReceiveForm = {'receive', _LineNum, _Clauses}) -> +%% todo; +%% (_Form) -> +%% false +%% end. + +exists_send_ptcut(#aspect{attribs = AspectAttribs}) -> + lists:any(fun(Advice) -> exists_ptcut_attr(Advice, send_msg) end, aadvice(AspectAttribs)); +exists_send_ptcut(Advice) when is_record(Advice, advice) -> + exists_ptcut_attr(Advice, send_msg). + +exists_receive_ptcut(#aspect{attribs = AspectAttribs}) -> + lists:any(fun(Advice) -> exists_ptcut_attr(Advice, receive_msg) end, aadvice(AspectAttribs)); +exists_receive_ptcut(Advice) when is_record(Advice, advice) -> + exists_ptcut_attr(Advice, receive_msg). + +exists_functiondef_ptcut(#aspect{attribs = AspectAttribs}) -> + lists:any(fun(Advice) -> exists_ptcut_attr(Advice, functiondef) end, aadvice(AspectAttribs)); +exists_functiondef_ptcut(Advice) when is_record(Advice, advice) -> + exists_ptcut_attr(Advice, functiondef). + +-spec exists_ptcut_attr(Aspect::aspect(), Attr::atom()) -> boolean(); + (Advice::advice(), Attr::atom()) -> boolean(). +%% @doc Returns 'true' when at least one advice in the given Aspect has a pointcut which contains an attribute of type Attr. +%% Returns 'true' if the given Advice has a pointcut which contains an attribute of type Attr. Returns 'false' otherwise. +exists_ptcut_attr(#aspect{attribs = AspectAttribs}, Attr) -> + lists:any(fun(Advice) -> exists_ptcut_attr(Advice, Attr) end, aadvice(AspectAttribs)); +exists_ptcut_attr(#advice{attribs = AdviceAttribs}, Attr) -> + Fun_PtcutAttribSelector = fun_pointcut_attrib_selector(Attr), + Pointcuts = advpointcuts(AdviceAttribs), + Pred = + fun(Pointcut) -> + case Fun_PtcutAttribSelector(Pointcut) of + no_attr -> false; + _ -> true + end + end, + lists:any(Pred, Pointcuts). + +-spec remove_non_pointcuts(List::[term()]) -> [pointcut()]. +%% @doc Removes anything which is not a pointcut() in the given List and returns the result. +remove_non_pointcuts(StrPointcuts) -> + lists:filter( + fun(StrPointcut) -> is_record(StrPointcut, pointcut) end, + StrPointcuts). + +-spec get_globals([aop_el()]) -> globals(). +get_globals(AopEls) -> + get_globals(AopEls, #globals{}). + + +-spec prepare_aspects_for_weaving(globals()) -> [aspect()]. +prepare_aspects_for_weaving(Globals) -> + [prepare_aspect_for_weaving(Aspect, Globals) || Aspect <- gaspects(Globals)]. + +%% +%% Not sure whether to export or make local: +%% + +-spec gaspects(globals()) -> [aspect()]. +gaspects(#globals{aspects = Aspects}) -> + Aspects. + +-spec gadvices(globals()) -> [advice()]. +gadvices(#globals{advices = Advices}) -> + Advices. + +-spec gpointcuts(globals()) -> [pointcut()]. +gpointcuts(#globals{pointcuts = Pointcuts}) -> + Pointcuts. + +-spec apointcuts(aspect()) -> [pointcut()]; + ([aspect_attr()]) -> [pointcut()]. +apointcuts(#aspect{attribs = AspectAttribs}) -> + apointcuts(AspectAttribs); +apointcuts(AspectAttribs) -> + [P || P <- AspectAttribs, is_record(P, pointcut)]. + +-spec aadvice(aspect()) -> [advice()]; + ([aspect_attr()]) -> [advice()]. +aadvice(#aspect{attribs = AspectAttribs}) -> + aadvice(AspectAttribs); +aadvice(AspectAttribs) -> + [Adv || Adv <- AspectAttribs, is_record(Adv, advice)]. + +-spec advpointcuts_with_refs(advice()) -> [str_pointcut()]; + ([advice_attr()]) -> [str_pointcut()]. +%% @doc Extracts the value of the ptcuts attribute in the given advice. +advpointcuts_with_refs(#advice{attribs = AdviceAttribs}) -> + advpointcuts_with_refs(AdviceAttribs); +advpointcuts_with_refs(AdviceAttribs) -> + case lists:keyfind(ptcuts, 1, AdviceAttribs) of + #ptcuts{val = PtcutsValue} -> + PtcutsValue; + false -> + [] + end. + +-spec advpointcuts(advice()) -> [pointcut()]; + ([advice_attr()]) -> [pointcut()]. +%% @doc Extracts the value of the ptcuts attribute in the given advice. +advpointcuts(Advice) -> + remove_non_pointcuts(advpointcuts_with_refs(Advice)). + +-spec fun_pointcut_attrib_selector(Attrib::atom()) -> fun((Pointcut::pointcut()) -> pointcut_attr() | no_attr). +%% @doc Returns a function which takes a Pointcut as an argument and returns a pointcut_attr() or no_attr. +%% pointcut_attr() is returned if the given Pointcut contains an attribute with key Attrib. no_attr is returned +%% if Pointcut does not contain an attribute with key Attrib. +fun_pointcut_attrib_selector(AttribName) -> + fun(#pointcut{attribs = PointcutAttribs}) -> + case lists:keyfind(AttribName, 1, PointcutAttribs) of + false -> + no_attr; + Attrib -> + Attrib + end + end. + +fun_advice_attrib_selector(AttribName) -> + fun(#advice{attribs = AdviceAttribs}) -> + case lists:keyfind(AttribName, 1, AdviceAttribs) of + false -> + no_attr; + Attrib -> + Attrib + end + end. + +%% get_send_msg/1 and get_receive_msg/1 can probably be deleted later. Their use is suppose to have been substituted by get_msg_format/1. +-spec get_send_msg(Pointcut::pointcut()) -> string() | undefined. +%% @doc Returns the given Pointcut's send_msg format string or 'undefined' if the Pointcut does not have the send_msg attribute. +get_send_msg(Pointcut) when is_record(Pointcut, pointcut) -> + Selector = fun_pointcut_attrib_selector(send_msg), + case Selector(Pointcut) of + no_attr -> + undefined; + SendMsg -> + SendMsg#send_msg.msg + end. + +-spec get_msg_format(Pointcut::pointcut()) -> string() | undefined. +%% @doc Returns the message format of the given Pointcut's first occurrence of send_msg or receive_msg or undefined if Pointcut does not contain neither a send_msg attribute nor a receive_msg attribute. +get_msg_format(#pointcut{attribs = PointcutAttribs} = Pointcut) when is_record(Pointcut, pointcut) -> + case ?UTIL:find_first(fun(Attrib) -> is_record(Attrib, send_msg) orelse is_record(Attrib, receive_msg) end, PointcutAttribs) of + undefined -> + undefined; + FoundAttrib when is_record(FoundAttrib, send_msg) -> + FoundAttrib#send_msg.msg; + FoundAttrib when is_record(FoundAttrib, receive_msg) -> + FoundAttrib#receive_msg.msg + end. + +-spec get_type(Advice::advice()) -> advice_t() | undefined. +get_type(Advice) when is_record(Advice, advice) -> + Selector = fun_advice_attrib_selector(type), + case Selector(Advice) of + no_attr -> + undefined; + Type -> + Type#type.val + end. + +-spec get_mf(Advice::advice()) -> {Module::atom(), Function::atom()} | undefined. +get_mf(Advice) when is_record(Advice, advice) -> + Selector = fun_advice_attrib_selector(mf), + case Selector(Advice) of + no_attr -> + undefined; + Mf -> + {Mf#mf.m, Mf#mf.f} + end. + +%% +%% Local functions: +%% + +%% fun((Elem::T) -> boolean()), + +%% apply_attr_selector + +-spec to_remove_apply_attr_selector(AttrSelector::fun((pointcut()) -> pointcut_attr() | no_attr), Advices::[advice()]) -> [pointcut_attr()]. +%% @doc Applies AttrSelector to each pointcut within each advice of the given Advices and returns a list containing +%% the result of each application of AttrSelector when such a result is not the atom 'no_attr'. +to_remove_apply_attr_selector(Fun, Advices) -> + DirtyPointcutAttribs = + [hd(hd([[Fun(Pointcut) || Pointcut <- Pointcuts, is_record(Pointcut, pointcut)] || Pointcuts <- advpointcuts(Advice)])) + || Advice <- Advices], + lists:filter(fun(PointcutAttrib) -> PointcutAttrib =/= no_attr end, DirtyPointcutAttribs). + +-spec get_globals([aop_el()], globals()) -> globals(). +get_globals([], Globals) -> + Globals; +get_globals([GAspect | AopEls], Globals) when is_record(GAspect, aspect) -> + get_globals(AopEls, Globals#globals{aspects = lists:append(gaspects(Globals), [GAspect])}); +get_globals([GAdvice | AopEls], Globals) when is_record(GAdvice, advice) -> + get_globals(AopEls, Globals#globals{advices = lists:append(gadvices(Globals), [GAdvice])}); +get_globals([GPointcut | AopEls], Globals) when is_record(GPointcut, pointcut) -> + get_globals(AopEls, Globals#globals{pointcuts = lists:append(gpointcuts(Globals), [GPointcut])}). + +-spec prepare_aspect_for_weaving(aspect(), globals()) -> aspect(). +%% @doc Preparing an aspect for weaving involves carrying out two operations on an aspect. +%%