diff --git a/compiler2/AST.cc b/compiler2/AST.cc index da8696b515b231f9316405abc92fe19291a929f2..4c74e61dd1d7f987bdd819e16f393d3eb57b25e8 100644 --- a/compiler2/AST.cc +++ b/compiler2/AST.cc @@ -815,8 +815,9 @@ namespace Common { // pre_init function bool has_pre_init = false; bool profiled = MOD_TTCN == get_moduletype() && is_file_profiled(get_filename()); + bool debugged = debugger_active && MOD_TTCN == get_moduletype(); // always generate pre_init_module if the file is profiled - if (output->functions.pre_init || profiled) { + if (output->functions.pre_init || profiled || debugged) { output->source.static_function_prototypes = mputstr(output->source.static_function_prototypes, "static void pre_init_module();\n"); @@ -848,6 +849,10 @@ namespace Common { "TTCN3_Stack_Depth stack_depth;\n" "ttcn3_prof.execute_line(\"%s\", 0);\n", get_modid().get_name().c_str(), get_filename()); } + if (debugged) { + output->source.static_function_bodies = mputprintf(output->source.static_function_bodies, + "%s::init_ttcn3_debugger();\n", get_modid().get_name().c_str()); + } } output->source.static_function_bodies = mputstr(output->source.static_function_bodies, output->functions.pre_init); @@ -1564,7 +1569,7 @@ namespace Common { "{\n"); char* function_name = 0; int line_no = -1; - while(get_profiler_code_line(get_filename(), &function_name, &line_no)) { + while (get_profiler_code_line(get_filename(), &function_name, &line_no)) { output->source.global_vars = mputprintf(output->source.global_vars, " ttcn3_prof.create_line(ttcn3_prof.get_element(\"%s\"), %d);\n", get_filename(), line_no); @@ -1574,7 +1579,15 @@ namespace Common { get_filename(), line_no, function_name); } } - output->source.global_vars = mputstr(output->source.global_vars, "}\n\n"); + output->source.global_vars = mputstr(output->source.global_vars, "}\n"); + } + /* TTCN-3 debugger: + generate the printing function for the types defined in this module + and initialize the debugger with this module's global variables, + component types and the components' variables */ + if (debugger_active) { + generate_debugger_functions(output); + generate_debugger_init(output); } } diff --git a/compiler2/AST.hh b/compiler2/AST.hh index 894d20571586b1955dbc08520ed8b3e218f61645..f92c90a625345134f996cae6efc8ea5d90994ff4 100644 --- a/compiler2/AST.hh +++ b/compiler2/AST.hh @@ -268,6 +268,24 @@ namespace Common { * module object to output->source.global_vars */ void generate_functions(output_struct *output); void generate_conversion_functions(output_struct *output); + + /** Generates the debugger initialization function for this module. + * The function creates the global debug scope associated with this module, + * and initializes it with all the global variables visible in the module + * (including imported variables). + * The debug scopes of all component types defined in the module are also + * created and initialized with their variables. */ + virtual void generate_debugger_init(output_struct *output) = 0; + + /** Generates the variable adding code for all global variables defined + * in this module. This function is called by generate_debugger_init() + * for both the current module and all imported modules. */ + virtual char* generate_debugger_global_vars(char* str, Common::Module* current_mod) = 0; + + /** Generates the debugger variable printing function, which can print values + * and templates of all types defined in this module (excluding subtypes). */ + virtual void generate_debugger_functions(output_struct *output) = 0; + private: /** Copy constructor not implemented */ Module(const Module& p); diff --git a/compiler2/DebuggerStuff.cc b/compiler2/DebuggerStuff.cc new file mode 100644 index 0000000000000000000000000000000000000000..c34bd6ebc89b9196123ba9a10995231cd96498f9 --- /dev/null +++ b/compiler2/DebuggerStuff.cc @@ -0,0 +1,405 @@ +/****************************************************************************** + * Copyright (c) 2000-2016 Ericsson Telecom AB + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Baranyi, Botond – initial implementation + * + ******************************************************************************/ + +#include "DebuggerStuff.hh" +#include "AST.hh" +#include "Type.hh" +#include "ttcn3/AST_ttcn3.hh" +#include "ttcn3/ArrayDimensions.hh" + +namespace Common { + +/** Returns a string, that contains the template parameters of the debugger's + * port array or timer array printing function. + * + * Recursive (handles one array dimension per call). + * + * @param p_dims the array's dimensions + * @param p_element_type name of the array's element C++ class + * @param p_array_type name of the array's C++ class ("PORT_ARRAY" or "TIMER_ARRAY") + * @param index the index of the currently handled dimension in \a p_dims + */ +string function_params_for_array_dims(Ttcn::ArrayDimensions* p_dims, + string p_element_type, + string p_array_type, + size_t index = 0) +{ + if (index == p_dims->get_nof_dims()) { + return p_element_type; + } + string ret_val; + if (index != 0) { + ret_val = p_array_type + string("<"); + } + ret_val += function_params_for_array_dims(p_dims, p_element_type, p_array_type, index + 1); + Ttcn::ArrayDimension* dim = p_dims->get_dim_byIndex(index); + ret_val += string(", ") + Int2string(dim->get_size()) + + string(", ") + Int2string(dim->get_offset()); + if (index != 0) { + ret_val += string(">"); + } + return ret_val; +} + +/** Returns a string, that contains the template parameters of the debugger's + * value array or template array printing function. + * + * Recursive (handles one array dimension per call). + * + * @param p_type the current type (either an array or the array element) + * @param p_scope the current scope + * @param p_templ indicates whether it's a template array or value array + * @param p_first indicates whether this is the first call or a recursive call + */ +string function_params_for_array_type(Type* p_type, + Scope* p_scope, + bool p_templ, + bool p_first = true) +{ + string ret_val; + if (p_type->get_typetype() != Type::T_ARRAY) { + ret_val = p_type->get_genname_value(p_scope); + if (p_templ) { + ret_val += "_template"; + } + } + else { + if (!p_first) { + if (p_templ) { + ret_val = "TEMPLATE_ARRAY<"; + } + else { + ret_val = "VALUE_ARRAY<"; + } + } + Type* elem_type = p_type->get_ofType()->get_type_refd_last(); + if (p_templ) { + ret_val += function_params_for_array_type(elem_type, p_scope, false, false) + + ", " + function_params_for_array_type(elem_type, p_scope, true, false); + } + else { + ret_val += function_params_for_array_type(elem_type, p_scope, false, false); + } + Ttcn::ArrayDimension* dim = p_type->get_dimension(); + ret_val += string(", ") + Int2string(dim->get_size()) + + string(", ") + Int2string(dim->get_offset()); + if (!p_first) { + ret_val += string(">"); + } + } + return ret_val; +} + +/** Appends the string representations of the specified array dimensions. */ +string array_dimensions_to_string(Ttcn::ArrayDimensions* p_dims) +{ + string ret_val; + for (size_t i = 0; i < p_dims->get_nof_dims(); ++i) { + ret_val += p_dims->get_dim_byIndex(i)->get_stringRepr(); + } + return ret_val; +} + +void calculate_type_name_and_print_function_from_type(Type* p_type, + Type* p_type_last, + Module* p_module, + string& p_type_name, + string& p_print_function) +{ + if (p_type_last->get_typetype() == Type::T_COMPONENT) { + p_type_name = "component"; + } + else if (p_type_last->is_structured_type() || + p_type_last->get_typetype() == Type::T_ENUM_A || + p_type_last->get_typetype() == Type::T_ENUM_T || + p_type_last->get_typetype() == Type::T_PORT || + p_type_last->get_typetype() == Type::T_SIGNATURE || + p_type_last->get_typetype() == Type::T_FUNCTION || + p_type_last->get_typetype() == Type::T_ALTSTEP || + p_type_last->get_typetype() == Type::T_TESTCASE) { + // user-defined type + if (p_type_last->is_pard_type_instance()) { + // if the referenced type is an instance of an ASN.1 parameterized type, + // then use the last non-parameterized type in the reference chain to + // calculate the type name + Type* t = p_type; + while (t->is_ref() && !t->is_pard_type_instance()) { + p_type_name = t->get_dispname(); + t = t->get_type_refd(); + } + } + else { + p_type_name = p_type_last->get_dispname(); + } + const Module* var_type_mod = p_type_last->get_my_scope()->get_scope_mod(); + if (var_type_mod != p_module) { + p_print_function = var_type_mod->get_modid().get_name() + "::"; + } + else { + p_print_function.clear(); + } + p_print_function += "print_var_" + var_type_mod->get_modid().get_ttcnname(); + } + else { + // built-in type, get the TTCN-3 version of the type if possible + switch (p_type_last->get_typetype()) { + case Type::T_GENERALSTRING: + case Type::T_GRAPHICSTRING: + case Type::T_TELETEXSTRING: + case Type::T_VIDEOTEXSTRING: + // these ASN.1 string types are not converted right by Type::get_typetype_ttcn3() + p_type_name = "universal charstring"; + break; + case Type::T_UNRESTRICTEDSTRING: + case Type::T_EMBEDDED_PDV: + case Type::T_EXTERNAL: + // these are converted to T_SEQ_T by Type::get_typetype_ttcn3() + p_type_name = Type::get_typename_builtin(p_type_last->get_typetype()); + break; + default: + p_type_name = Type::get_typename_builtin(p_type_last->get_typetype_ttcn3()); + break; + } + } +} + +char* generate_code_debugger_add_var(char* str, Common::Assignment* var_ass, + Module* current_mod /* = NULL */, + const char* scope_name /* = NULL */) +{ + if (current_mod == NULL) { + current_mod = var_ass->get_my_scope()->get_scope_mod(); + } + + bool is_lazy_param = false; + switch (var_ass->get_asstype()) { + case Common::Assignment::A_PAR_VAL: + case Common::Assignment::A_PAR_VAL_IN: + case Common::Assignment::A_PAR_TEMPL_IN: + if (var_ass->get_lazy_eval()) { + // lazy parameters have their own printing function + is_lazy_param = true; + } + break; + default: + break; + } + + // recreate the TTCN-3 version of the type name and determine the type's printing function + string type_name, print_function; + print_function = is_lazy_param ? "TTCN3_Debugger::print_lazy_param<" : + "TTCN3_Debugger::print_base_var"; + if (var_ass->get_asstype() == Common::Assignment::A_TIMER || + var_ass->get_asstype() == Common::Assignment::A_PAR_TIMER) { + type_name = "timer"; + if (var_ass->get_Dimensions() != NULL) { + // timer array + type_name += array_dimensions_to_string(var_ass->get_Dimensions()); + print_function = string("TTCN3_Debugger::print_timer_array<") + + function_params_for_array_dims(var_ass->get_Dimensions(), + string("TIMER"), string("TIMER_ARRAY")) + + string(">"); + } + } + else { + Common::Type* var_type = var_ass->get_Type(); + // get the type at the end of the reference chain, but don't go through + // CHARACTER STRINGs, EMBEDDED PDVs and EXTERNALs + while (var_type->is_ref() && var_type->get_typetype() != Type::T_EXTERNAL && + var_type->get_typetype() != Type::T_EMBEDDED_PDV && + var_type->get_typetype() != Type::T_UNRESTRICTEDSTRING) { + var_type = var_type->get_type_refd(); + } + if (is_lazy_param) { + print_function += var_type->get_genname_value(current_mod); + } + if (var_type->get_typetype() == Type::T_PORT && var_ass->get_Dimensions() != NULL) { + // port array + type_name = var_type->get_dispname() + + array_dimensions_to_string(var_ass->get_Dimensions()); + if (!is_lazy_param) { + print_function = string("TTCN3_Debugger::print_port_array<") + + function_params_for_array_dims(var_ass->get_Dimensions(), + var_type->get_genname_value(current_mod), + string("PORT_ARRAY")) + + string(">"); + } + } + else if (var_type->get_typetype() == Type::T_ARRAY) { + string dims_str; + Type* t = var_type; + while (t->get_typetype() == Type::T_ARRAY) { + dims_str += t->get_dimension()->get_stringRepr(); + t = t->get_ofType()->get_type_refd_last(); + } + string dummy; + calculate_type_name_and_print_function_from_type(t, t, current_mod, type_name, dummy); + type_name += dims_str; + if (!is_lazy_param) { + switch (var_ass->get_asstype()) { + case Common::Assignment::A_MODULEPAR_TEMP: + case Common::Assignment::A_TEMPLATE: + case Common::Assignment::A_VAR_TEMPLATE: + case Common::Assignment::A_PAR_TEMPL_IN: + case Common::Assignment::A_PAR_TEMPL_OUT: + case Common::Assignment::A_PAR_TEMPL_INOUT: + // template array + print_function = string("TTCN3_Debugger::print_template_array<") + + function_params_for_array_type(var_type, current_mod, true) + + string(">"); + break; + default: + // value array + print_function = string("TTCN3_Debugger::print_value_array<") + + function_params_for_array_type(var_type, current_mod, false) + + string(">"); + break; + } + } + } + else { + string dummy; + calculate_type_name_and_print_function_from_type(var_ass->get_Type(), + var_type, current_mod, type_name, is_lazy_param ? dummy : print_function); + } + } + + switch (var_ass->get_asstype()) { + case Common::Assignment::A_MODULEPAR_TEMP: + case Common::Assignment::A_TEMPLATE: + case Common::Assignment::A_VAR_TEMPLATE: + case Common::Assignment::A_PAR_TEMPL_IN: + case Common::Assignment::A_PAR_TEMPL_OUT: + case Common::Assignment::A_PAR_TEMPL_INOUT: + // add a suffix, if it's a template + type_name += " template"; + if (is_lazy_param) { + print_function += "_template"; + } + break; + default: + break; + } + + if (is_lazy_param) { + print_function += ">"; + } + + return mputprintf(str, "%s%s_scope%sadd_variable(&%s, \"%s\", \"%s\", %s);\n", + scope_name != NULL ? " " : "", // add indenting for global variables + scope_name != NULL ? scope_name : "debug", // the prefix of the debugger scope: + // ("global" for global variables, "debug" for local variables, + // or the component name for component variables) + scope_name != NULL ? "->" : ".", // global scopes are pointers, local scopes + // are local variables + var_ass->get_genname_from_scope(current_mod, "").c_str(), // variable name in C++ + // (HACK: an empty string is passed as the prefix parameter to get_genname_from_scope, + // so the lazy parameter evaluation code is not generated) + var_ass->get_id().get_ttcnname().c_str(), // variable name in TTCN-3 + type_name.c_str(), // variable type in TTCN-3, with a suffix if it's a template + print_function.c_str()); // variable printing function +} + +char* generate_code_debugger_function_init(char* str, Common::Assignment* func_ass) +{ + string comp_str = func_ass->get_RunsOnType() == NULL ? string("NULL") : + string("\"") + func_ass->get_RunsOnType()->get_dispname() + string("\""); + const char* func_type_str = NULL; + switch (func_ass->get_asstype()) { + case Common::Assignment::A_FUNCTION: + case Common::Assignment::A_FUNCTION_RVAL: + case Common::Assignment::A_FUNCTION_RTEMP: + func_type_str = "function"; + break; + case Common::Assignment::A_EXT_FUNCTION: + case Common::Assignment::A_EXT_FUNCTION_RVAL: + case Common::Assignment::A_EXT_FUNCTION_RTEMP: + func_type_str = "external function"; + break; + case Common::Assignment::A_TESTCASE: + func_type_str = "testcase"; + break; + case Common::Assignment::A_ALTSTEP: + func_type_str = "altstep"; + break; + case Common::Assignment::A_TEMPLATE: // parameterized template + func_type_str = "template"; + break; + default: + break; + } + Ttcn::FormalParList* fp_list = func_ass != NULL ? func_ass->get_FormalParList() : NULL; + if (fp_list != NULL && fp_list->get_nof_fps() != 0) { + // has parameters + char* fp_names_str = NULL; + char* fp_types_str = NULL; + char* fp_add_var_str = NULL; + for (size_t i = 0; i < fp_list->get_nof_fps(); ++i) { + // gather everything needed for this parameter in sub-strings + Ttcn::FormalPar* fp = fp_list->get_fp_byIndex(i); + const char* fp_type_str = NULL; + switch (fp->get_asstype()) { + case Common::Assignment::A_PAR_VAL: + case Common::Assignment::A_PAR_VAL_IN: + case Common::Assignment::A_PAR_TEMPL_IN: + fp_type_str = "in"; + break; + case Common::Assignment::A_PAR_VAL_INOUT: + case Common::Assignment::A_PAR_TEMPL_INOUT: + case Common::Assignment::A_PAR_TIMER: // treat timers and ports as 'inout' parameters + case Common::Assignment::A_PAR_PORT: + fp_type_str = "inout"; + break; + case Common::Assignment::A_PAR_VAL_OUT: + case Common::Assignment::A_PAR_TEMPL_OUT: + fp_type_str = "out"; + break; + default: + break; + } + fp_names_str = mputprintf(fp_names_str, + "param_names[%d] = \"%s\";\n", (int)i, fp->get_id().get_ttcnname().c_str()); + fp_types_str = mputprintf(fp_types_str, + "param_types[%d] = \"%s\";\n", (int)i, fp_type_str); + fp_add_var_str = generate_code_debugger_add_var(fp_add_var_str, fp); + } + str = mputprintf(str, + "charstring_list param_names;\n" + "%s" + "charstring_list param_types;\n" + "%s" + "TTCN3_Debug_Function debug_scope(\"%s\", \"%s\", \"%s\", param_names, param_types, %s);\n" + "%s" + "debug_scope.initial_snapshot();\n" + , fp_names_str, fp_types_str + , func_ass->get_id().get_dispname().c_str(), func_type_str + , func_ass->get_my_scope()->get_scope_mod()->get_modid().get_ttcnname().c_str() + , comp_str.c_str(), fp_add_var_str); + Free(fp_names_str); + Free(fp_types_str); + Free(fp_add_var_str); + } + else { + // no parameters + str = mputprintf(str, + "charstring_list no_params = NULL_VALUE;\n" + "TTCN3_Debug_Function debug_scope(\"%s\", \"%s\", \"%s\", no_params, no_params, %s);\n" + "debug_scope.initial_snapshot();\n" + , func_ass->get_id().get_dispname().c_str(), func_type_str + , func_ass->get_my_scope()->get_scope_mod()->get_modid().get_ttcnname().c_str() + , comp_str.c_str()); + } + return str; +} + +} \ No newline at end of file diff --git a/compiler2/DebuggerStuff.hh b/compiler2/DebuggerStuff.hh new file mode 100644 index 0000000000000000000000000000000000000000..55cc7c8354394ca6e12ffdf11e90f59ba7e51fdc --- /dev/null +++ b/compiler2/DebuggerStuff.hh @@ -0,0 +1,44 @@ +/****************************************************************************** + * Copyright (c) 2000-2016 Ericsson Telecom AB + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Baranyi, Botond – initial implementation + * + ******************************************************************************/ + +#ifndef DEBUGGERSTUFF_HH +#define DEBUGGERSTUFF_HH + +#include <stddef.h> + +// forward declarations +namespace Common { + class Assignment; + class Module; + +/** Generates code, that adds a variable to a debugger scope object. + * @param str code generation buffer + * @param var_ass the variable's definition + * @param current_mod scope object's module (NULL means the module is the same, + * where the variable is defined) + * @param scope_name the prefix of the debugger scope object (NULL for local + * variables) */ +extern char* generate_code_debugger_add_var(char* str, Common::Assignment* var_ass, + Common::Module* current_mod = NULL, const char* scope_name = NULL); + +/** Generates code, that creates a debugger function object, adds its parameters + * to be tracked by the debugger, and takes the function's initial snapshot. + * @param str code generation buffer + * @param func_ass the function's definition */ +extern char* generate_code_debugger_function_init(char* str, + Common::Assignment* func_ass); + +} + +#endif /* DEBUGGERSTUFF_HH */ + diff --git a/compiler2/Makefile b/compiler2/Makefile index 74c5e36138da4aea8a10a00606407b355817499e..a2789d57a5d100302f73a5b7bf6c3402518d95b7 100644 --- a/compiler2/Makefile +++ b/compiler2/Makefile @@ -86,7 +86,8 @@ PredefFunc.cc AST.cc Code.cc Constraint.cc CompilerError.cc \ CompField.cc CompType.cc EnumItem.cc Identifier.cc Int.cc \ main.cc Real.cc Setting.cc SigParam.cc string.cc subtype.cc Stopwatch.cc \ Type.cc Type_chk.cc Type_codegen.cc TypeCompat.cc \ -Typestuff.cc ustring.cc Value.cc Valuestuff.cc XerAttributes.cc subtypestuff.cc CodeGenHelper.cc +Typestuff.cc ustring.cc Value.cc Valuestuff.cc XerAttributes.cc subtypestuff.cc \ +CodeGenHelper.cc DebuggerStuff.cc MFGEN_SOURCES := makefile.c xpather.cc ProjectGenHelper.cc diff --git a/compiler2/Setting.cc b/compiler2/Setting.cc index 9f71f58eff3ceea2a80c2a152a0bb416989ac437..5f1add8b48e4982d21237cef6e28e195420bf5f7 100644 --- a/compiler2/Setting.cc +++ b/compiler2/Setting.cc @@ -293,6 +293,9 @@ namespace Common { mputprintf(effective_module_lines, "%s%d", (effective_module_lines ? ", " : ""), yyloc.first_line); } + if (debugger_active) { + str = mputprintf(str, "ttcn3_debugger.breakpoint_entry(%d);\n", yyloc.first_line); + } } if (include_line_info) diff --git a/compiler2/Type.hh b/compiler2/Type.hh index de1e6022d076ceca5a0faec3548abc19b52132bc..96ea75acc58051a2be3860d5c9b53880ef9fe5ea 100644 --- a/compiler2/Type.hh +++ b/compiler2/Type.hh @@ -24,6 +24,7 @@ * Zalanyi, Balazs Andor * ******************************************************************************/ + #ifndef _Common_Type_HH #define _Common_Type_HH diff --git a/compiler2/asn1/AST_asn1.cc b/compiler2/asn1/AST_asn1.cc index 639e914e4ef685d15fc602a21b4e517fdad3170b..cd87a28f38d2866c34e67334e9ab120aee811a77 100644 --- a/compiler2/asn1/AST_asn1.cc +++ b/compiler2/asn1/AST_asn1.cc @@ -28,6 +28,7 @@ #include "../main.hh" #include "../CodeGenHelper.hh" #include "../../common/JSON_Tokenizer.hh" +#include "../DebuggerStuff.hh" /* defined in asn1p.y */ extern int asn1_parse_string(const char* p_str); @@ -529,22 +530,23 @@ namespace Asn { // cycle through all type assignments, insert schema segments and references // when needed for (size_t i = 0; i < asss->get_nof_asss(); ++i) { - Common::Assignment* ass = asss->get_ass_byIndex(i); - if (Common::Assignment::A_TYPE == ass->get_asstype()) { - Asn::Assignment* asn_ass = dynamic_cast<Asn::Assignment*>(ass); - // skip parameterized types and their instances - if (NULL == asn_ass || NULL == asn_ass->get_ass_pard()) { - Type* t = ass->get_Type(); - if (!t->is_pard_type_instance() && t->has_encoding(Type::CT_JSON)) { - // insert type's schema segment - t->generate_json_schema(json, false, false); - - if (json_refs_for_all_types && !json_refs.has_key(t)) { - // create JSON schema reference for the type - JSON_Tokenizer* json_ref = new JSON_Tokenizer; - json_refs.add(t, json_ref); - t->generate_json_schema_ref(*json_ref); - } + Asn::Assignment* asn_ass = dynamic_cast<Asn::Assignment*>(asss->get_ass_byIndex(i)); + if (asn_ass == NULL || asn_ass->get_ass_pard() != NULL) { + // skip parameterized types + continue; + } + if (Common::Assignment::A_TYPE == asn_ass->get_asstype()) { + Type* t = asn_ass->get_Type(); + // skip instances of parameterized types + if (!t->is_pard_type_instance() && t->has_encoding(Type::CT_JSON)) { + // insert type's schema segment + t->generate_json_schema(json, false, false); + + if (json_refs_for_all_types && !json_refs.has_key(t)) { + // create JSON schema reference for the type + JSON_Tokenizer* json_ref = new JSON_Tokenizer; + json_refs.add(t, json_ref); + t->generate_json_schema_ref(*json_ref); } } } @@ -553,6 +555,77 @@ namespace Asn { // end of type definitions json.put_next_token(JSON_TOKEN_OBJECT_END); } + + void Module::generate_debugger_init(output_struct *output) + { + // no debugging in ASN.1 modules + } + + char* Module::generate_debugger_global_vars(char* str, Common::Module* current_mod) + { + for (size_t i = 0; i < asss->get_nof_asss(); ++i) { + Asn::Assignment* asn_ass = dynamic_cast<Asn::Assignment*>(asss->get_ass_byIndex(i)); + if (asn_ass->get_ass_pard() != NULL) { + // this check must be done before get_asstype() is called + continue; + } + if (asn_ass->get_asstype() == Common::Assignment::A_CONST) { + str = generate_code_debugger_add_var(str, asn_ass, current_mod, "global"); + } + } + return str; + } + + void Module::generate_debugger_functions(output_struct *output) + { + char* str = NULL; + for (size_t i = 0; i < asss->get_nof_asss(); ++i) { + Asn::Assignment* asn_ass = dynamic_cast<Asn::Assignment*>(asss->get_ass_byIndex(i)); + if (asn_ass->get_ass_pard() != NULL) { + // skip parameterized types + // this check must be done before get_asstype() is called + continue; + } + if (Common::Assignment::A_TYPE == asn_ass->get_asstype()) { + Type* t = asn_ass->get_Type(); + if (!t->is_pard_type_instance() && (t->is_structured_type() || + t->get_typetype() == Type::T_ENUM_A || + (t->is_ref() && t->get_type_refd()->is_pard_type_instance()))) { + // only structured types and enums are needed + // for instances of parameterized types, the last reference, which is + // not itself an instance of a parameterized type, holds the type's display name + str = mputprintf(str, + " %sif (!strcmp(p_var.type_name, \"%s\")) {\n" + " ((const %s*)p_var.value)->log();\n" + " }\n" + " else if (!strcmp(p_var.type_name, \"%s template\")) {\n" + " ((const %s_template*)p_var.value)->log();\n" + " }\n" + , (str != NULL) ? "else " : "" + , t->get_dispname().c_str(), t->get_genname_value(this).c_str() + , t->get_dispname().c_str(), t->get_genname_value(this).c_str()); + } + } + } + if (str != NULL) { + // don't generate an empty printing function + output->header.class_defs = mputprintf(output->header.class_defs, + "/* Debugger printing function for types declared in this module */\n\n" + "extern CHARSTRING print_var_%s(const TTCN3_Debugger::variable_t& p_var);\n", + get_modid().get_ttcnname().c_str()); + output->source.global_vars = mputprintf(output->source.global_vars, + "\n/* Debugger printing function for types declared in this module */\n" + "CHARSTRING print_var_%s(const TTCN3_Debugger::variable_t& p_var)\n" + "{\n" + " TTCN_Logger::begin_event_log2str();\n" + "%s" + " else {\n" + " TTCN_Logger::log_event_str(\"<unrecognized value or template>\");\n" + " }\n" + " return TTCN_Logger::end_event_log2str();\n" + "}\n", get_modid().get_ttcnname().c_str(), str); + } + } // ================================= // ===== Assignments diff --git a/compiler2/asn1/AST_asn1.hh b/compiler2/asn1/AST_asn1.hh index 28a264c9bd424349ddf86cd73e52c6f3d130ddb9..5d115acac051b6198f5e87a5ef62bf53ec3780e3 100644 --- a/compiler2/asn1/AST_asn1.hh +++ b/compiler2/asn1/AST_asn1.hh @@ -263,6 +263,19 @@ namespace Asn { * the types will be inserted here * @param json_refs map of JSON documents containing the references to each type */ virtual void generate_json_schema(JSON_Tokenizer& json, map<Type*, JSON_Tokenizer>& json_refs); + + /** Does nothing. Debugger initialization functions are not generated for + * ASN.1 modules. */ + virtual void generate_debugger_init(output_struct *output); + + /** Generates the variable adding code for all global variables defined + * in this module. This function is called by generate_debugger_init() + * for both the current module and all imported modules. */ + virtual char* generate_debugger_global_vars(char* str, Common::Module* current_mod); + + /** Generates the debugger variable printing function, which can print values + * and templates of all types defined in this module (excluding subtypes). */ + virtual void generate_debugger_functions(output_struct *output); }; /** diff --git a/compiler2/main.cc b/compiler2/main.cc index 41d6f4bad30a19562331cfbcb6dbdec5ca1463a0..46890454c902db3c42ff467f256e78ded52939e5 100644 --- a/compiler2/main.cc +++ b/compiler2/main.cc @@ -94,7 +94,7 @@ boolean generate_skeleton = FALSE, force_overwrite = FALSE, check_subtype = TRUE, suppress_context = FALSE, display_up_to_date = FALSE, implicit_json_encoding = FALSE, json_refs_for_all_types = TRUE, force_gen_seof = FALSE, omit_in_value_list = FALSE, - warnings_for_bad_variants = FALSE; + warnings_for_bad_variants = FALSE, debugger_active = FALSE; // Default code splitting mode is set to 'no splitting'. CodeGenHelper::split_type code_splitting_mode = CodeGenHelper::SPLIT_NONE; @@ -479,8 +479,8 @@ int main(int argc, char *argv[]) dflag = false, Xflag = false, Rflag = false, gflag = false, aflag = false, s0flag = false, Cflag = false, yflag = false, Uflag = false, Qflag = false, Sflag = false, Kflag = false, jflag = false, zflag = false, Fflag = false, - Mflag = false, Eflag = false, errflag = false, print_usage = false, - ttcn2json = false; + Mflag = false, Eflag = false, nflag = false, errflag = false, + print_usage = false, ttcn2json = false; CodeGenHelper cgh; @@ -572,7 +572,7 @@ int main(int argc, char *argv[]) if (!ttcn2json) { for ( ; ; ) { - int c = getopt(argc, argv, "aA:bcC:dEfFgijK:lLMo:pP:qQ:rRsStT:uU:vV:wxXyYz:0-"); + int c = getopt(argc, argv, "aA:bcC:dEfFgijK:lLMno:pP:qQ:rRsStT:uU:vV:wxXyYz:0-"); if (c == -1) break; switch (c) { case 'a': @@ -743,6 +743,10 @@ int main(int argc, char *argv[]) SET_FLAG(E); warnings_for_bad_variants = TRUE; break; + case 'n': + SET_FLAG(n); + debugger_active = TRUE; + break; case 'Q': { long max_errs; @@ -787,7 +791,8 @@ int main(int argc, char *argv[]) if (Aflag || Lflag || Pflag || Tflag || Vflag || Yflag || bflag || fflag || iflag || lflag || oflag || pflag || qflag || rflag || sflag || tflag || uflag || wflag || xflag || Xflag || Rflag || - Uflag || yflag || Kflag || jflag || zflag || Fflag || Mflag || Eflag) { + Uflag || yflag || Kflag || jflag || zflag || Fflag || Mflag || Eflag || + nflag) { errflag = true; print_usage = true; } @@ -807,6 +812,10 @@ int main(int argc, char *argv[]) ERROR("Source line information `-L' is necessary for profiling `-z'."); errflag = true; } + if (nflag && !Lflag) { + ERROR("Source line information `-L' is necessary for debugging `-n'."); + errflag = true; + } if (iflag && gflag) { WARNING("Option `-g' overrides `-i'."); iflag = false; // -g gives more information diff --git a/compiler2/main.hh b/compiler2/main.hh index 5565413e5f775dff90f72446431f889440ab48ca..2192b32dfc1d51df62e85c765567a017b90692e8 100644 --- a/compiler2/main.hh +++ b/compiler2/main.hh @@ -49,7 +49,7 @@ extern boolean generate_skeleton, force_overwrite, include_line_info, output_only_linenum, default_as_optional, use_runtime_2, gcc_compat, asn1_xer, check_subtype, suppress_context, enable_set_bound_out_param, display_up_to_date, implicit_json_encoding, json_refs_for_all_types, force_gen_seof, - omit_in_value_list, warnings_for_bad_variants; + omit_in_value_list, warnings_for_bad_variants, debugger_active; extern const char *expected_platform; diff --git a/compiler2/ttcn3/AST_ttcn3.cc b/compiler2/ttcn3/AST_ttcn3.cc index edb3e2d2989c2831f866457f8502d4fba97ab481..d1afdbcb167f1e7ffb0a9a4d94a8297f7b1d495f 100644 --- a/compiler2/ttcn3/AST_ttcn3.cc +++ b/compiler2/ttcn3/AST_ttcn3.cc @@ -43,6 +43,7 @@ #include "../../common/version_internal.h" #include "../CodeGenHelper.hh" #include "../../common/JSON_Tokenizer.hh" +#include "../DebuggerStuff.hh" #include <limits.h> // implemented in coding_attrib_p.y @@ -1673,6 +1674,10 @@ namespace Ttcn { { target->header.includes = mputstr(target->header.includes, "#include <TTCN3.hh>\n"); + /*if (debugger_active) { + target->header.includes = mputstr(target->header.includes, + "#include \"init_debug.inc\"\n"); + }*/ for (size_t i = 0; i < impmods_v.size(); i++) { ImpMod *im = impmods_v[i]; Common::Module *m = im->get_mod(); @@ -2091,6 +2096,12 @@ namespace Ttcn { module_dispname); target->functions.control = mputprintf(target->functions.control, "TTCN_Runtime::begin_controlpart(\"%s\");\n", module_dispname); + if (debugger_active) { + target->functions.control = mputprintf(target->functions.control, + "charstring_list no_params = NULL_VALUE;\n" + "TTCN3_Debug_Function debug_scope(NULL, \"control\", \"%s\", no_params, no_params, NULL);\n" + "debug_scope.initial_snapshot();\n", module_dispname); + } target->functions.control = block->generate_code(target->functions.control); target->functions.control = mputstr(target->functions.control, @@ -2805,6 +2816,132 @@ namespace Ttcn { } } } + + void Module::generate_debugger_init(output_struct* output) + { + // create the initializer function + output->source.global_vars = mputstr(output->source.global_vars, + "\n/* Initializing TTCN-3 debugger */\n" + "void init_ttcn3_debugger()\n" + "{\n" + /*" debugger_manual_init();\n"*/); + + // initialize global scope and variables (including imported variables) + char* str_glob = generate_debugger_global_vars(NULL, this); + for (int i = 0; i < imp->get_imports_size(); ++i) { + str_glob = imp->get_impmod(i)->get_mod()->generate_debugger_global_vars(str_glob, this); + } + if (str_glob != NULL) { + // only add the global scope if it actually has variables + output->source.global_vars = mputprintf(output->source.global_vars, + " /* global variables */\n" + " TTCN3_Debug_Scope* global_scope = ttcn3_debugger.add_global_scope(\"%s\");\n" + "%s", + get_modid().get_dispname().c_str(), str_glob); + Free(str_glob); + } + + // initialize components' scopes and their variables + for (size_t i = 0; i < asss->get_nof_asss(); ++i) { + Def_Type* def = dynamic_cast<Def_Type*>(asss->get_ass_byIndex(i)); + if (def != NULL) { + Type* comp_type = def->get_Type(); + if (comp_type->get_typetype() == Type::T_COMPONENT) { + char* str_comp = NULL; + ComponentTypeBody* comp_body = comp_type->get_CompBody(); + for (size_t j = 0; j < comp_body->get_nof_asss(); ++j) { + str_comp = generate_code_debugger_add_var(str_comp, comp_body->get_ass_byIndex(j), + this, comp_type->get_dispname().c_str()); + } + if (str_comp != NULL) { + // only add the component if it actually has variables + output->source.global_vars = mputprintf(output->source.global_vars, + " /* variables of component %s */\n" + " TTCN3_Debug_Scope* %s_scope = ttcn3_debugger.add_component_scope(\"%s\");\n" + "%s" + , comp_type->get_dispname().c_str(), comp_type->get_dispname().c_str() + , comp_type->get_dispname().c_str(), str_comp); + Free(str_comp); + } + } + } + } + + // close the initializer function + output->source.global_vars = mputstr(output->source.global_vars, "}\n"); + } + + char* Module::generate_debugger_global_vars(char* str, Common::Module* current_mod) + { + for (size_t i = 0; i < asss->get_nof_asss(); ++i) { + Common::Assignment* ass = asss->get_ass_byIndex(i); + switch (ass->get_asstype()) { + case Common::Assignment::A_TEMPLATE: + if (ass->get_FormalParList() != NULL) { + // don't add parameterized templates, since they are functions in C++ + break; + } + // else fall through + case Common::Assignment::A_CONST: + //case Common::Assignment::A_EXT_CONST: TODO: handle unused ext_const + case Common::Assignment::A_MODULEPAR: + case Common::Assignment::A_MODULEPAR_TEMP: + str = generate_code_debugger_add_var(str, ass, current_mod, "global"); + break; + default: + break; + } + } + return str; + } + + void Module::generate_debugger_functions(output_struct *output) + { + char* str = NULL; + for (size_t i = 0; i < asss->get_nof_asss(); ++i) { + Def_Type* def = dynamic_cast<Def_Type*>(asss->get_ass_byIndex(i)); + if (def != NULL) { + Type* t = def->get_Type(); + if (!t->is_ref() && t->get_typetype() != Type::T_COMPONENT) { + // don't generate code for subtypes + if (t->get_typetype() != Type::T_SIGNATURE) { + str = mputprintf(str, + " %sif (!strcmp(p_var.type_name, \"%s\")) {\n" + " ((const %s*)p_var.value)->log();\n" + " }\n" + , (str != NULL) ? "else " : "" + , t->get_dispname().c_str(), t->get_genname_value(this).c_str()); + } + if (t->get_typetype() != Type::T_PORT) { + str = mputprintf(str, + " %sif (!strcmp(p_var.type_name, \"%s template\")) {\n" + " ((const %s_template*)p_var.value)->log();\n" + " }\n" + , (str != NULL) ? "else " : "" + , t->get_dispname().c_str(), t->get_genname_value(this).c_str()); + } + } + } + } + if (str != NULL) { + // don't generate an empty printing function + output->header.class_defs = mputprintf(output->header.class_defs, + "/* Debugger printing function for types declared in this module */\n\n" + "extern CHARSTRING print_var_%s(const TTCN3_Debugger::variable_t& p_var);\n", + get_modid().get_ttcnname().c_str()); + output->source.global_vars = mputprintf(output->source.global_vars, + "\n/* Debugger printing function for types declared in this module */\n" + "CHARSTRING print_var_%s(const TTCN3_Debugger::variable_t& p_var)\n" + "{\n" + " TTCN_Logger::begin_event_log2str();\n" + "%s" + " else {\n" + " TTCN_Logger::log_event_str(\"<unrecognized value or template>\");\n" + " }\n" + " return TTCN_Logger::end_event_log2str();\n" + "}\n", get_modid().get_ttcnname().c_str(), str); + } + } // ================================= // ===== Definition @@ -3381,6 +3518,9 @@ namespace Ttcn { // the value is assigned using subsequent statements str = value->generate_code_init(str, genname_str); } + if (debugger_active) { + str = generate_code_debugger_add_var(str, this); + } return str; } @@ -4230,6 +4370,9 @@ namespace Ttcn { size_t nof_base_pars = 0; char* function_body = create_location_object(memptystr(), "TEMPLATE", template_dispname); + if (debugger_active) { + function_body = generate_code_debugger_function_init(function_body, this); + } if (base_template) { // modified template function_body = mputprintf(function_body, "%s ret_val(%s", @@ -4260,6 +4403,11 @@ namespace Ttcn { if (template_restriction!=TR_NONE && gen_restriction_check) function_body = Template::generate_restriction_check_code(function_body, "ret_val", template_restriction); + if (debugger_active) { + function_body = mputstr(function_body, + "ttcn3_debugger.set_return_value((TTCN_Logger::begin_event_log2str(), " + "ret_val.log(), TTCN_Logger::end_event_log2str()));\n"); + } function_body = mputstr(function_body, "return ret_val;\n"); // if the template modifies a parameterized template, then the inherited // formal parameters must always be displayed, otherwise generate a smart @@ -4403,6 +4551,9 @@ namespace Ttcn { str = Template::generate_restriction_check_code(str, genname_str, template_restriction); } + if (debugger_active) { + str = generate_code_debugger_add_var(str, this); + } return str; } @@ -4626,6 +4777,9 @@ namespace Ttcn { str = initial_value->generate_code_init(str, genname_str); } } + if (debugger_active) { + str = generate_code_debugger_add_var(str, this); + } return str; } @@ -4853,6 +5007,9 @@ namespace Ttcn { && gen_restriction_check) str = Template::generate_restriction_check_code(str, genname_str, template_restriction); + if (debugger_active) { + str = generate_code_debugger_add_var(str, this); + } return str; } @@ -5371,6 +5528,9 @@ namespace Ttcn { } } } + if (debugger_active) { + str = generate_code_debugger_add_var(str, this); + } return str; } @@ -6072,6 +6232,9 @@ namespace Ttcn { if (!enable_set_bound_out_param) body = fp_list->generate_code_set_unbound(body); // conform the standard out parameter is unbound body = fp_list->generate_shadow_objects(body); + if (debugger_active) { + body = generate_code_debugger_function_init(body, this); + } body = block->generate_code(body); // smart formal parameter list (names of unused parameters are omitted) char *formal_par_list = fp_list->generate_code(memptystr()); @@ -6637,7 +6800,14 @@ namespace Ttcn { "TTCN_Logger::end_event();\n" "}\n", result_name, function_name, result_name); // returning the result stream if necessary - if (prototype == PROTOTYPE_CONVERT) str = mputstr(str, "return ret_val;\n"); + if (prototype == PROTOTYPE_CONVERT) { + if (debugger_active) { + str = mputstr(str, + "ttcn3_debugger.set_return_value((TTCN_Logger::begin_event_log2str(), " + "ret_val.log(), TTCN_Logger::end_event_log2str()));\n"); + } + str = mputstr(str, "return ret_val;\n"); + } return str; } @@ -6718,12 +6888,28 @@ namespace Ttcn { "}\n", input_type->get_genname_value(my_scope).c_str(), function_name); // closing the block and returning the appropriate result or status code if (prototype == PROTOTYPE_BACKTRACK) { - str = mputstr(str, "return 0;\n" - "} else return 1;\n"); + if (debugger_active) { + str = mputstr(str, "ttcn3_debugger.set_return_value(\"0\");\n"); + } + str = mputstr(str, + "return 0;\n" + "} else {\n"); + if (debugger_active) { + str = mputstr(str, "ttcn3_debugger.set_return_value(\"1\");\n"); + } + str = mputstr(str, + "return 1;\n" + "}\n"); } else { str = mputstr(str, "}\n"); - if (prototype == PROTOTYPE_CONVERT) + if (prototype == PROTOTYPE_CONVERT) { + if (debugger_active) { + str = mputstr(str, + "ttcn3_debugger.set_return_value((TTCN_Logger::begin_event_log2str(), " + "ret_val.log(), TTCN_Logger::end_event_log2str()));\n"); + } str = mputstr(str, "return ret_val;\n"); + } } } else { // result handling and debug printout for sliding decoders @@ -6738,13 +6924,16 @@ namespace Ttcn { "%s.log();\n" "TTCN_Logger::end_event();\n" "}\n" - "return 0;\n" + "%sreturn 0;\n" "case TTCN_EncDec::ET_INCOMPL_MSG:\n" "case TTCN_EncDec::ET_LEN_ERR:\n" - "return 2;\n" + "%sreturn 2;\n" "default:\n" - "return 1;\n" - "}\n", first_par_name, function_name, first_par_name); + "%sreturn 1;\n" + "}\n", first_par_name, function_name, first_par_name, + debugger_active ? "ttcn3_debugger.set_return_value(\"0\");\n" : "", + debugger_active ? "ttcn3_debugger.set_return_value(\"2\");\n" : "", + debugger_active ? "ttcn3_debugger.set_return_value(\"1\");\n" : ""); } return str; } @@ -6786,6 +6975,9 @@ namespace Ttcn { "%s %s(%s)\n" "{\n" , return_type_str, genname_str, formal_par_list); + if (debugger_active) { + body = generate_code_debugger_function_init(body, this); + } switch (function_type) { case EXTFUNC_ENCODE: body = generate_code_encode(body); @@ -7067,6 +7259,9 @@ namespace Ttcn { // are never used) char* body = create_location_object(memptystr(), "ALTSTEP", dispname_str); body = fp_list->generate_shadow_objects(body); + if (debugger_active) { + body = generate_code_debugger_function_init(body, this); + } body = sb->generate_code(body); body = ags->generate_code_altstep(body); // generate a smart formal parameter list (omits unused parameter names) @@ -7342,6 +7537,9 @@ namespace Ttcn { body = system_type->get_CompBody()->generate_code_comptype_name(body); else body = runs_on_body->generate_code_comptype_name(body); body = mputstr(body, ", has_timer, timer_value);\n"); + if (debugger_active) { + body = generate_code_debugger_function_init(body, this); + } body = block->generate_code(body); body = mputprintf(body, "} catch (const TC_Error& tc_error) {\n" @@ -8283,8 +8481,8 @@ namespace Ttcn { { // the name of the parameter should not be displayed if the parameter is not // used (to avoid a compiler warning) - bool display_name = (usage_found || display_unused || (!enable_set_bound_out_param && - (asstype == A_PAR_VAL_OUT || asstype == A_PAR_TEMPL_OUT))); + bool display_name = (usage_found || display_unused || debugger_active || + (!enable_set_bound_out_param && (asstype == A_PAR_VAL_OUT || asstype == A_PAR_TEMPL_OUT))); const char *name_str = display_name ? id->get_name().c_str() : ""; switch (asstype) { case A_PAR_VAL_IN: diff --git a/compiler2/ttcn3/AST_ttcn3.hh b/compiler2/ttcn3/AST_ttcn3.hh index 0ea723c316810c7b422234035192ec9c0d520039..e694c26b295bfad8639470b4ee4c754149c85ce6 100644 --- a/compiler2/ttcn3/AST_ttcn3.hh +++ b/compiler2/ttcn3/AST_ttcn3.hh @@ -653,6 +653,23 @@ namespace Ttcn { * @param json_refs map of JSON documents containing the references and function * info related to each type */ virtual void generate_json_schema(JSON_Tokenizer& json, map<Type*, JSON_Tokenizer>& json_refs); + + /** Generates the debugger initialization function for this module. + * The function creates the global debug scope associated with this module, + * and initializes it with all the global variables visible in the module + * (including imported variables). + * The debug scopes of all component types defined in the module are also + * created and initialized with their variables. */ + virtual void generate_debugger_init(output_struct *output); + + /** Generates the variable adding code for all global variables defined + * in this module. This function is called by generate_debugger_init() + * for both the current module and all imported modules. */ + virtual char* generate_debugger_global_vars(char* str, Common::Module* current_mod); + + /** Generates the debugger variable printing function, which can print values + * and templates of all types defined in this module (excluding subtypes). */ + virtual void generate_debugger_functions(output_struct *output); }; /** diff --git a/compiler2/ttcn3/Statement.cc b/compiler2/ttcn3/Statement.cc index 81e5cab5077be5eeda2a1204abbb1e9194f172ca..8610a6520a67100da9b358c58a4f3d60af36e4e9 100644 --- a/compiler2/ttcn3/Statement.cc +++ b/compiler2/ttcn3/Statement.cc @@ -5819,6 +5819,9 @@ error: } if (block->get_nof_stmts() > 0 || block->get_exception_handling()!=StatementBlock::EH_NONE) { str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = block->generate_code(str); str = mputstr(str, "}\n"); } else str = mputstr(str, "/* empty block */;\n"); @@ -5905,6 +5908,9 @@ error: if(!eachfalse) str=mputstr(str, "else "); eachfalse=false; str=mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } blockcount++; str=if_stmt.elseblock->generate_code(str); } @@ -5937,6 +5943,9 @@ error: // generate code for them anyway if (loop.for_stmt.varinst) { str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = loop.for_stmt.init_varinst->generate_code_str(str); } else { str = loop.for_stmt.init_ass->update_location_object(str); @@ -5965,6 +5974,9 @@ error: while (blockcount-- > 0) str = mputstr(str, "}\n"); } if (loop.label_next) str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = loop.block->generate_code(str); if (loop.label_next) str = mputprintf(str, "}\n" @@ -6001,6 +6013,9 @@ error: str = mputstr(str, ") break;\n"); while(blockcount-- > 0) str = mputstr(str, "}\n"); } + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = loop.block->generate_code(str); str = mputstr(str, "}\n"); } @@ -6018,6 +6033,9 @@ error: } if (loop.iterate_once && !loop.has_brk && !loop.has_cnt) { str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = loop.block->generate_code(str); } else { str = mputstr(str, "for ( ; ; ) {\n"); @@ -6027,6 +6045,9 @@ error: if (loop.label_next && is_infinite_loop) str = mputprintf(str, "%s:\n", loop.label_next->c_str()); if (loop.label_next && !is_infinite_loop) str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = loop.block->generate_code(str); // do not generate the exit condition for infinite loops if (!is_infinite_loop) { @@ -6432,10 +6453,31 @@ error: Definition *my_def = my_sb->get_my_def(); if (returnexpr.v) { expr.expr = mputc(expr.expr, ' '); + if (debugger_active) { + // the debugger's return value storing macro requires a temporary, + // so the returned expression isn't evaluated twice + string tmp_id = my_def->get_my_scope()->get_scope_mod_gen()->get_temporary_id(); + expr.preamble = mputprintf(expr.preamble, "%s %s;\n", + returnexpr.v->get_expr_governor_last()->get_genname_value(my_def->get_my_scope()).c_str(), + tmp_id.c_str()); + expr.expr = mputprintf(expr.expr, "DEBUGGER_STORE_RETURN_VALUE(%s, ", tmp_id.c_str()); + } returnexpr.v->generate_code_expr_mandatory(&expr); + if (debugger_active) { + expr.expr = mputc(expr.expr, ')'); + } } else if (returnexpr.t) { expr.expr = mputc(expr.expr, ' '); if (!my_def) FATAL_ERROR("Statement::generate_code_return()"); + if (debugger_active) { + // the debugger's return value storing macro requires a temporary, + // so the returned expression isn't evaluated twice + string tmp_id = my_def->get_my_scope()->get_scope_mod_gen()->get_temporary_id(); + expr.preamble = mputprintf(expr.preamble, "%s_template %s;\n", + returnexpr.t->get_my_governor()->get_genname_value(my_def->get_my_scope()).c_str(), + tmp_id.c_str()); + expr.expr = mputprintf(expr.expr, "DEBUGGER_STORE_RETURN_VALUE(%s, ", tmp_id.c_str()); + } Def_Function_Base* dfb = dynamic_cast<Def_Function_Base*>(my_def); if (!dfb) FATAL_ERROR("Statement::generate_code_return()"); if (dfb->get_template_restriction() != TR_NONE && @@ -6445,6 +6487,9 @@ error: } else { returnexpr.t->generate_code_expr(&expr, TR_NONE); } + if (debugger_active) { + expr.expr = mputc(expr.expr, ')'); + } } else { if (my_def && my_def->get_asstype() == Definition::A_ALTSTEP) expr.expr = mputstr(expr.expr, " ALT_YES"); @@ -8953,6 +8998,9 @@ error: } eachfalse = false; str=mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str=block->generate_code(str); str=mputstr(str, "}\n"); return str; @@ -9282,6 +9330,9 @@ error: if(unreach) return str; if(!tis) unreach=true; str=mputprintf(str, "%s_%lu:\n{\n", tmp_prefix, (unsigned long) idx); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str=block->generate_code(str); str=mputprintf(str, "goto %s_end;\n}\n", tmp_prefix); return str; @@ -10055,6 +10106,9 @@ error: StatementBlock *block = ag->get_block(); if (block->get_nof_stmts() > 0) { str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = block->generate_code(str); str = mputstr(str, "}\n"); } @@ -10134,6 +10188,9 @@ error: StatementBlock *block = ag->get_block(); if (block && block->get_nof_stmts() > 0) { str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = block->generate_code(str); if (block->has_return() != StatementBlock::RS_YES) str = mputstr(str, "break;\n"); @@ -10192,6 +10249,9 @@ error: StatementBlock *block = ag->get_block(); if (block->get_nof_stmts() > 0) { str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = block->generate_code(str); str = mputstr(str, "}\n"); } @@ -10264,6 +10324,9 @@ error: StatementBlock *block = ag->get_block(); if (block && block->get_nof_stmts() > 0) { str = mputstr(str, "{\n"); + if (debugger_active) { + str = mputstr(str, "TTCN3_Debug_Scope debug_scope;\n"); + } str = block->generate_code(str); str = mputstr(str, "}\n"); } diff --git a/core/Basetype.hh b/core/Basetype.hh index 9ea3e191cacb73ad0d782bd53cd03843ec6bf604..f18f4434c02a5a038b7243c7e02a773dbf17efca 100644 --- a/core/Basetype.hh +++ b/core/Basetype.hh @@ -28,6 +28,7 @@ #include "Encdec.hh" #include "RInt.hh" #include "JSON_Tokenizer.hh" +#include "Logger.hh" #ifdef TITAN_RUNTIME_2 #include "Struct_of.hh" #include "XER.hh" @@ -1076,6 +1077,14 @@ public: return expr_cache; } virtual ~Lazy_Param() {} + void log() const { + if (!expr_evaluated) { + TTCN_Logger::log_event_str("<not evaluated>"); + } + else { + expr_cache.log(); + } + } }; #endif diff --git a/core/Debugger.cc b/core/Debugger.cc new file mode 100644 index 0000000000000000000000000000000000000000..7e86c0c4ee36f94fe27f96c36a6dda33e59ca100 --- /dev/null +++ b/core/Debugger.cc @@ -0,0 +1,878 @@ +/****************************************************************************** + * Copyright (c) 2000-2016 Ericsson Telecom AB + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Baranyi, Botond – initial implementation + * + ******************************************************************************/ + +#include "Debugger.hh" + +////////////////////////////////////////////////////// +////////////////// TTCN3_Debugger //////////////////// +////////////////////////////////////////////////////// + +TTCN3_Debugger ttcn3_debugger; + +void TTCN3_Debugger::switch_off() +{ + if (!active) { + print("The debugger is already switched off.\n"); + } + else { + print("Debugger switched off.\n"); + } + active = false; +} + +void TTCN3_Debugger::switch_on() +{ + if (active) { + print("The debugger is already switched on.\n"); + } + else { + print("Debugger switched on.\n"); + } + active = true; +} + +void TTCN3_Debugger::add_breakpoint(const char* p_module, int p_line /*const char* batch_file*/) +{ + if (find_breakpoint(p_module, p_line) == breakpoints.size()) { + breakpoint_t bp; + bp.module = mcopystr(p_module); + bp.line = p_line; + breakpoints.push_back(bp); + print("Breakpoint added in module '%s' at line %d.\n", p_module, p_line); + } + else { + print("Breakpoint already set in module '%s' at line %d.\n", p_module, p_line); + } +} + +void TTCN3_Debugger::remove_breakpoint(const char* p_module, int p_line) +{ + size_t pos = find_breakpoint(p_module, p_line); + if (pos != breakpoints.size()) { + Free(breakpoints[pos].module); + breakpoints.erase_at(pos); + print("Breakpoint removed in module '%s' from line %d.\n", p_module, p_line); + } + else { + print("No breakpoint found in module '%s' at line %d.\n", p_module, p_line); + } +} + +void TTCN3_Debugger::set_special_breakpoint(special_breakpoint_t p_type, const char* p_state_str) +{ + bool new_state; + if (!strcmp(p_state_str, "yes")) { + new_state = true; + } + else if(!strcmp(p_state_str, "no")) { + new_state = false; + } + // else if "batch" + else { + print("Argument 1 is invalid.\n"); + return; + } + const char* sbp_type_str; + bool state_changed; + switch (p_type) { + case SBP_FAIL_VERDICT: + state_changed = (fail_behavior != new_state); + fail_behavior = new_state; + sbp_type_str = "Fail"; + break; + case SBP_ERROR_VERDICT: + state_changed = (error_behavior != new_state); + error_behavior = new_state; + sbp_type_str = "Error"; + break; + default: + // should never happen + return; + } + print("%s verdict behavior %sset to %s.\n", sbp_type_str, + state_changed ? "" : "was already ", + new_state ? "halt the program" : "do nothing"); +} + +void TTCN3_Debugger::print_call_stack() +{ + for (size_t i = call_stack.size(); i != 0; --i) { + print("%d.\t", (int)call_stack.size() - (int)i + 1); + call_stack[i - 1]->print_function(); + } +} + +void TTCN3_Debugger::set_stack_level(int new_level) +{ + if (new_level < 0 || (size_t)new_level > call_stack.size()) { + print("Invalid new stack level.\n"); + } + else { + stack_level = new_level; + } +} + +void TTCN3_Debugger::print_variable(const TTCN3_Debugger::variable_t* p_var) const +{ + print("%s := %s\n", p_var->name, (const char*)p_var->print_function(*p_var)); +} + +void TTCN3_Debugger::set_output(const char* p_output_type, const char* p_file_name) +{ + FILE* new_fp; + if (!strcmp(p_output_type, "stdout")) { + new_fp = stdout; + } + else if (!strcmp(p_output_type, "stderr")) { + new_fp = stderr; + } + else if (!strcmp(p_output_type, "file")) { + if (p_file_name == NULL) { + print("Missing output file name.\n"); + return; + } + new_fp = fopen(p_file_name, "w"); + if (new_fp == NULL) { + print("Failed to open file '%s' for writing.\n"); + return; + } + } + else { + print("Argument 1 is invalid.\n"); + return; + } + // don't close the previous file, if the command's parameters are invalid + if (output != stdout && output != stderr) { + fclose(output); + } + output = new_fp; +} + +size_t TTCN3_Debugger::find_breakpoint(const char* p_module, int p_line) const +{ + for (size_t i = 0; i < breakpoints.size(); ++i) { + if (!strcmp(breakpoints[i].module, p_module) && breakpoints[i].line == p_line) { + return i; + } + } + return breakpoints.size(); +} + +TTCN3_Debugger::variable_t* TTCN3_Debugger::find_variable(const void* p_value) const +{ + for (size_t i = 0; i < variables.size(); ++i) { + if (variables[i]->value == p_value) { + return variables[i]; + } + } + return NULL; +} + +TTCN3_Debugger::TTCN3_Debugger() +{ + active = false; + output = stderr; + snapshots = NULL; + last_breakpoint_entry.module = NULL; + last_breakpoint_entry.line = 0; + stack_level = -1; + fail_behavior = false; + error_behavior = false; +} + +TTCN3_Debugger::~TTCN3_Debugger() +{ + if (output != stdout && output != stderr) { + fclose(output); + } + for (size_t i = 0; i < breakpoints.size(); ++i) { + Free(breakpoints[i].module); + } + for (size_t i = 0; i < global_scopes.size(); ++i) { + delete global_scopes[i].scope; + } + for (size_t i = 0; i < component_scopes.size(); ++i) { + delete component_scopes[i].scope; + } + for (size_t i = 0; i < variables.size(); ++i) { + delete variables[i]; + } + Free(snapshots); +} + +TTCN3_Debug_Scope* TTCN3_Debugger::add_global_scope(const char* p_module) +{ + named_scope_t global_scope; + global_scope.name = p_module; + global_scope.scope = new TTCN3_Debug_Scope(); + global_scopes.push_back(global_scope); + return global_scope.scope; +} + +TTCN3_Debug_Scope* TTCN3_Debugger::add_component_scope(const char* p_component) +{ + named_scope_t component_scope; + component_scope.name = p_component; + component_scope.scope = new TTCN3_Debug_Scope(); + component_scopes.push_back(component_scope); + return component_scope.scope; +} + +void TTCN3_Debugger::set_return_value(const CHARSTRING& p_value) +{ + if (active) { + call_stack[call_stack.size() - 1]->set_return_value(p_value); + } +} + +void TTCN3_Debugger::breakpoint_entry(int p_line /*bool p_stepping_helper*/) +{ + if (active && !call_stack.empty()) { + const char* module_name = call_stack[call_stack.size() - 1]->get_module_name(); + bool trigger = false; + const char* trigger_type; + int actual_line; + switch (p_line) { + case SBP_FAIL_VERDICT: + trigger = fail_behavior; + trigger_type = "Fail verdict"; + actual_line = last_breakpoint_entry.line; + break; + case SBP_ERROR_VERDICT: + trigger = error_behavior; + trigger_type = "Error verdict"; + actual_line = last_breakpoint_entry.line; + break; + default: + // code lines + trigger = (last_breakpoint_entry.line == 0 || p_line != last_breakpoint_entry.line || + module_name != last_breakpoint_entry.module) && + find_breakpoint(module_name, p_line) != breakpoints.size(); + trigger_type = "Breakpoint"; + actual_line = p_line; + break; + } + // make sure it's not the same breakpoint entry as last time + if (trigger) { + stack_level = call_stack.size() - 1; + print("%s reached in module '%s' at line %d.\n", trigger_type, + module_name, actual_line); + /////////////////////////////////////////////////////////////////////////////////// + /*print("##################################################\n"); + print("Call stack:\n"); + charstring_list params = NULL_VALUE; + execute_command(D_PRINT_CALL_STACK, params); + print("##################################################\n"); + print("Variables: "); + params[0] = "global"; + execute_command(D_LIST_VARIABLES, params); + params.set_size(0); + size_t idx = 0; + const TTCN3_Debug_Scope* glob_scope = get_global_scope(module_name); + for (size_t i = 0; i < variables.size(); ++i) { + if (glob_scope->find_variable(variables[i]->name) != NULL) { + params[idx++] = variables[i]->name; + } + } + execute_command(D_PRINT_VARIABLE, params); + print("##################################################\n"); + print("Function call snapshots:\n"); + params.set_size(0); + execute_command(D_PRINT_SNAPSHOTS, params);*/ + /////////////////////////////////////////////////////////////////////////////////// + } + last_breakpoint_entry.module = (char*)module_name; + last_breakpoint_entry.line = p_line; + } +} + +CHARSTRING TTCN3_Debugger::print_base_var(const TTCN3_Debugger::variable_t& p_var) +{ + TTCN_Logger::begin_event_log2str(); + if (!strcmp(p_var.type_name, "bitstring")) { + ((const BITSTRING*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "bitstring template")) { + ((const BITSTRING_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "boolean")) { + ((const BOOLEAN*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "boolean template")) { + ((const BOOLEAN_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "charstring")) { + ((const CHARSTRING*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "charstring template")) { + ((const CHARSTRING_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "float")) { + ((const FLOAT*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "float template")) { + ((const FLOAT_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "hexstring")) { + ((const HEXSTRING*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "hexstring template")) { + ((const HEXSTRING_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "integer")) { + ((const INTEGER*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "integer template")) { + ((const INTEGER_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "objid")) { + ((const OBJID*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "objid template")) { + ((const OBJID_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "octetstring")) { + ((const OCTETSTRING*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "octetstring template")) { + ((const OCTETSTRING_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "universal charstring")) { + ((const UNIVERSAL_CHARSTRING*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "universal charstring template")) { + ((const UNIVERSAL_CHARSTRING_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "verdicttype")) { + ((const VERDICTTYPE*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "verdicttype template")) { + ((const VERDICTTYPE_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "component")) { + ((const COMPONENT*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "component template")) { + ((const COMPONENT_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "default")) { + ((const DEFAULT*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "default template")) { + ((const DEFAULT_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "timer")) { + ((const TIMER*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "NULL")) { + ((const ASN_NULL*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "NULL template")) { + ((const ASN_NULL_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "CHARACTER STRING")) { + ((const CHARACTER_STRING*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "CHARACTER STRING template")) { + ((const CHARACTER_STRING_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "EMBEDDED PDV")) { + ((const EMBEDDED_PDV*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "EMBEDDED PDV template")) { + ((const EMBEDDED_PDV_template*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "EXTERNAL")) { + ((const EXTERNAL*)p_var.value)->log(); + } + else if (!strcmp(p_var.type_name, "EXTERNAL template")) { + ((const EXTERNAL_template*)p_var.value)->log(); + } + else { + TTCN_Logger::log_event_str("<unrecognized value or template>"); + } + return TTCN_Logger::end_event_log2str(); +} + +void TTCN3_Debugger::print(const char* fmt, ...) const +{ + va_list parameters; + va_start(parameters, fmt); + vfprintf(output, fmt, parameters); + va_end(parameters); + fflush(output); +} + +void TTCN3_Debugger::add_function(TTCN3_Debug_Function* p_function) +{ + if (active) { + call_stack.push_back(p_function); + } +} + +void TTCN3_Debugger::add_scope(TTCN3_Debug_Scope* p_scope) +{ + if (active && !call_stack.empty()) { + call_stack[call_stack.size() - 1]->add_scope(p_scope); + } +} + +void TTCN3_Debugger::remove_function(TTCN3_Debug_Function* p_function) +{ + if (!call_stack.empty() && call_stack[call_stack.size() - 1] == p_function) { + call_stack.erase_at(call_stack.size() - 1); + } +} + +void TTCN3_Debugger::remove_scope(TTCN3_Debug_Scope* p_scope) +{ + if (!call_stack.empty()) { + call_stack[call_stack.size() - 1]->remove_scope(p_scope); + } +} + +const TTCN3_Debugger::variable_t* TTCN3_Debugger::add_variable(const void* p_value, + const char* p_name, + const char* p_type, + CHARSTRING (*p_print_function)(const TTCN3_Debugger::variable_t&)) +{ + + if (call_stack.empty()) { + // no call stack yet, so this is a global or component variable + variable_t* var = find_variable(p_value); + if (var == NULL) { + var = new TTCN3_Debugger::variable_t; + var->value = p_value; + var->name = p_name; + var->type_name = p_type; + var->print_function = p_print_function; + variables.push_back(var); + } + return var; + } + else if (active) { + // it's a local variable for the top-most function + return call_stack[call_stack.size() - 1]->add_variable(p_value, p_name, p_type, p_print_function); + } + return NULL; +} + +void TTCN3_Debugger::remove_variable(const variable_t* p_var) +{ + if (active && !call_stack.empty()) { + call_stack[call_stack.size() - 1]->remove_variable(p_var); + } +} + +const TTCN3_Debug_Scope* TTCN3_Debugger::get_global_scope(const char* p_module) const +{ + for (size_t i = 0; i < global_scopes.size(); ++i) { + if (strcmp(global_scopes[i].name, p_module) == 0) { + return global_scopes[i].scope; + } + } + return NULL; +} + +const TTCN3_Debug_Scope* TTCN3_Debugger::get_component_scope(const char* p_component) const +{ + for (size_t i = 0; i < component_scopes.size(); ++i) { + if (strcmp(component_scopes[i].name, p_component) == 0) { + return component_scopes[i].scope; + } + } + return NULL; +} + +void TTCN3_Debugger::add_snapshot(const char* p_snapshot) +{ + snapshots = mputstr(snapshots, p_snapshot); +} + +#define CHECK_NOF_ARGUMENTS(exp_num) \ + if (exp_num != p_arguments.size_of()) { \ + print("Invalid number of arguments. Expected %d, got %d.\n", \ + (int)exp_num, (int)p_arguments.size_of()); \ + return; \ + } + +#define CHECK_NOF_ARGUMENTS_RANGE(min, max) \ + if ((int)min > p_arguments.size_of() || (int)max < p_arguments.size_of()) { \ + print("Invalid number of arguments. Expected at least %d and at most %d, got %d.\n", \ + (int)min, (int)max, p_arguments.size_of()); \ + return; \ + } + +#define CHECK_NOF_ARGUMENTS_MIN(min) \ + if ((int)min > p_arguments.size_of()) { \ + print("Invalid number of arguments. Expected at least %d, got %d.\n", \ + (int)min, p_arguments.size_of()); \ + return; \ + } + +#define CHECK_INT_ARGUMENT(arg_idx) \ + { \ + const char* str = (const char*)p_arguments[arg_idx]; \ + for (int i = 0; i < p_arguments[arg_idx].lengthof(); ++i) { \ + if (str[i] < '0' || str[i] > '9') { \ + print("Argument %d is not an integer.\n", (int)(arg_idx + 1)); \ + return; \ + } \ + } \ + } + +#define CHECK_CALL_STACK \ + if (call_stack.empty()) { \ + print("This command can only be executed when the program is running.\n"); \ + return; \ + } + +void TTCN3_Debugger::execute_command(TTCN3_Debugger::debug_command_t p_command, + const charstring_list& p_arguments) +{ + if (!active && p_command != D_SWITCH_ON && p_command != D_SWITCH_OFF) { + print("Cannot run debug commands while the debugger is switched off.\n"); + return; + } + for (int i = 0; i < p_arguments.size_of(); ++i) { + if (!p_arguments[i].is_bound()) { + print("Argument %d is unbound.\n", i + 1); + return; + } + } + switch (p_command) { + case D_SWITCH_OFF: + CHECK_NOF_ARGUMENTS(0) + switch_off(); + break; + case D_SWITCH_ON: + CHECK_NOF_ARGUMENTS(0) + switch_on(); + break; + case D_ADD_BREAKPOINT: + CHECK_NOF_ARGUMENTS(2) + CHECK_INT_ARGUMENT(1) + add_breakpoint(p_arguments[0], str2int(p_arguments[1])); + break; + case D_REMOVE_BREAKPOINT: + CHECK_NOF_ARGUMENTS(2) + CHECK_INT_ARGUMENT(1) + remove_breakpoint(p_arguments[0], str2int(p_arguments[1])); + break; + case D_SET_ERROR_BEHAVIOR: + CHECK_NOF_ARGUMENTS(1) + set_special_breakpoint(SBP_ERROR_VERDICT, p_arguments[0]); + break; + case D_SET_FAIL_BEHAVIOR: + CHECK_NOF_ARGUMENTS(1) + set_special_breakpoint(SBP_FAIL_VERDICT, p_arguments[0]); + break; + // ... + case D_SET_OUTPUT: + CHECK_NOF_ARGUMENTS_RANGE(1, 2) + set_output(p_arguments[0], (p_arguments.size_of() == 2) ? (const char*)p_arguments[1] : NULL); + break; + // ... + case D_PRINT_CALL_STACK: + CHECK_CALL_STACK + CHECK_NOF_ARGUMENTS(0) + print_call_stack(); + break; + case D_SET_STACK_LEVEL: + CHECK_CALL_STACK + CHECK_NOF_ARGUMENTS(1) + CHECK_INT_ARGUMENT(0) + set_stack_level(str2int(p_arguments[0])); + break; + case D_LIST_VARIABLES: + CHECK_CALL_STACK + CHECK_NOF_ARGUMENTS_RANGE(1, 2) + call_stack[stack_level]->list_variables(p_arguments[0], + (p_arguments.size_of() == 2) ? (const char*)p_arguments[1] : NULL); + break; + case D_PRINT_VARIABLE: + CHECK_CALL_STACK + CHECK_NOF_ARGUMENTS_MIN(1) + for (int i = 0; i < p_arguments.size_of(); ++i) { + const variable_t* var = call_stack[stack_level]->find_variable(p_arguments[i]); + if (var != NULL) { + print_variable(var); + } + else { + print("Variable '%s' not found.\n", (const char*)p_arguments[i]); + } + } + break; + // ... + case D_PRINT_SNAPSHOTS: + CHECK_NOF_ARGUMENTS(0) + print("%s", snapshots); + break; + // ... + default: + print("Command not implemented.\n"); + break; + } +} + +////////////////////////////////////////////////////// +//////////////// TTCN3_Debug_Scope /////////////////// +////////////////////////////////////////////////////// + +TTCN3_Debug_Scope::TTCN3_Debug_Scope() +{ + ttcn3_debugger.add_scope(this); +} + +TTCN3_Debug_Scope::~TTCN3_Debug_Scope() +{ + for (size_t i = 0; i < variables.size(); ++i) { + ttcn3_debugger.remove_variable(variables[i]); + } + ttcn3_debugger.remove_scope(this); +} + +void TTCN3_Debug_Scope::add_variable(const void* p_value, + const char* p_name, + const char* p_type, + CHARSTRING (*p_print_function)(const TTCN3_Debugger::variable_t&)) +{ + const TTCN3_Debugger::variable_t* var = ttcn3_debugger.add_variable(p_value, p_name, p_type, p_print_function); + if (var != NULL) { + variables.push_back(var); + } +} + +const TTCN3_Debugger::variable_t* TTCN3_Debug_Scope::find_variable(const char* p_name) const +{ + for (size_t i = 0; i < variables.size(); ++i) { + if (strcmp(variables[i]->name, p_name) == 0) { + return variables[i]; + } + } + return NULL; +} + +void TTCN3_Debug_Scope::list_variables(const char* p_filter, bool& p_first) const +{ + for (size_t i = 0; i < variables.size(); ++i) { + // the filter is currently ignored + ttcn3_debugger.print("%s%s", p_first ? "" : " ", variables[i]->name); + p_first = false; + } +} + +////////////////////////////////////////////////////// +/////////////// TTCN3_Debug_Function ///////////////// +////////////////////////////////////////////////////// + +TTCN3_Debug_Function::TTCN3_Debug_Function(const char* p_name, + const char* p_type, + const char* p_module, + const charstring_list& p_parameter_names, + const charstring_list& p_parameter_types, + const char* p_component_name) +: function_name(p_name), function_type(p_type), module_name(p_module) +, parameter_names(new charstring_list(p_parameter_names)) +, parameter_types(new charstring_list(p_parameter_types)) +{ + ttcn3_debugger.add_function(this); + global_scope = ttcn3_debugger.get_global_scope(p_module); + component_scope = (p_component_name != NULL) ? + ttcn3_debugger.get_component_scope(p_component_name) : NULL; + if (function_name == NULL) { + function_name = p_module; // for control parts + } +} + +TTCN3_Debug_Function::~TTCN3_Debug_Function() +{ + if (ttcn3_debugger.is_on()) { + char* snapshot = mprintf("[%s]\tfinished\t%s(", function_type, function_name); + if (parameter_names->size_of() > 0) { + for (int i = 0; i < parameter_names->size_of(); ++i) { + if (i > 0) { + snapshot = mputstr(snapshot, ", "); + } + snapshot = mputprintf(snapshot, "[%s] %s := ", (const char*)((*parameter_types)[i]), + (const char*)((*parameter_names)[i])); + if ((*parameter_types)[i] == "out" || (*parameter_types)[i] == "inout") { + const TTCN3_Debugger::variable_t* parameter = find_variable((*parameter_names)[i]); + snapshot = mputstr(snapshot, parameter->print_function(*parameter)); + } + else { + snapshot = mputc(snapshot, '-'); + } + } + } + snapshot = mputc(snapshot, ')'); + if (return_value.is_bound()) { + snapshot = mputprintf(snapshot, " returned %s", (const char*)return_value); + } + snapshot = mputc(snapshot, '\n'); + ttcn3_debugger.add_snapshot(snapshot); + Free(snapshot); + } + for (size_t i = 0; i < variables.size(); ++i) { + delete variables[i]; + } + delete parameter_names; + delete parameter_types; + ttcn3_debugger.remove_function(this); +} + +const TTCN3_Debugger::variable_t* TTCN3_Debug_Function::add_variable(const void* p_value, + const char* p_name, + const char* p_type, + CHARSTRING (*p_print_function)(const TTCN3_Debugger::variable_t&)) +{ + if (ttcn3_debugger.is_on()) { + TTCN3_Debugger::variable_t* var = new TTCN3_Debugger::variable_t; + var->value = p_value; + var->name = p_name; + var->type_name = p_type; + var->print_function = p_print_function; + variables.push_back(var); + return var; + } + return NULL; +} + +void TTCN3_Debug_Function::set_return_value(const CHARSTRING& p_value) +{ + return_value = p_value; +} + +void TTCN3_Debug_Function::initial_snapshot() const +{ + if (ttcn3_debugger.is_on()) { + char* snapshot = mprintf("[%s]\tstarted \t%s(", function_type, function_name); + if (parameter_names->size_of() > 0) { + for (int i = 0; i < parameter_names->size_of(); ++i) { + if (i > 0) { + snapshot = mputstr(snapshot, ", "); + } + snapshot = mputprintf(snapshot, "[%s] %s := ", (const char*)((*parameter_types)[i]), + (const char*)((*parameter_names)[i])); + if ((*parameter_types)[i] == "in" || (*parameter_types)[i] == "inout") { + const TTCN3_Debugger::variable_t* parameter = find_variable((*parameter_names)[i]); + snapshot = mputstr(snapshot, parameter->print_function(*parameter)); + } + else { + snapshot = mputc(snapshot, '-'); + } + } + } + snapshot = mputstr(snapshot, ")\n"); + ttcn3_debugger.add_snapshot(snapshot); + Free(snapshot); + } +} + +void TTCN3_Debug_Function::add_scope(TTCN3_Debug_Scope* p_scope) +{ + scopes.push_back(p_scope); +} + +void TTCN3_Debug_Function::remove_scope(TTCN3_Debug_Scope* p_scope) +{ + if (scopes[scopes.size() - 1] == p_scope) { + scopes.erase_at(scopes.size() - 1); + } +} + +void TTCN3_Debug_Function::remove_variable(const TTCN3_Debugger::variable_t* p_var) +{ + for (size_t i = 0; i < variables.size(); ++i) { + if (variables[i] == p_var) { + variables.erase_at(i); + delete p_var; + break; + } + } +} + +const TTCN3_Debugger::variable_t* TTCN3_Debug_Function::find_variable(const char* p_name) const +{ + for (size_t i = 0; i < variables.size(); ++i) { + if (strcmp(variables[i]->name, p_name) == 0) { + return variables[i]; + } + } + // it's not a local variable, it might still be a global or component variable + if (component_scope != NULL) { + const TTCN3_Debugger::variable_t* res = component_scope->find_variable(p_name); + if (res != NULL) { + return res; + } + } + return (global_scope != NULL) ? global_scope->find_variable(p_name) : NULL; +} + +void TTCN3_Debug_Function::print_function() const +{ + ttcn3_debugger.print("[%s]\t%s(", function_type, function_name); + if (parameter_names->size_of() > 0) { + for (int i = 0; i < parameter_names->size_of(); ++i) { + if (i > 0) { + ttcn3_debugger.print(", "); + } + const TTCN3_Debugger::variable_t* parameter = find_variable((*parameter_names)[i]); + ttcn3_debugger.print("[%s] %s := %s", (const char*)(*parameter_types)[i], + (const char*)(*parameter_names)[i], (const char*)parameter->print_function(*parameter)); + } + } + ttcn3_debugger.print(")\n"); +} + +void TTCN3_Debug_Function::list_variables(const char* p_scope, const char* p_filter) const +{ + bool first = true; + bool list_local = false; + bool list_global = false; + bool list_comp = false; + if (!strcmp(p_scope, "local")) { + list_local = true; + } + else if (!strcmp(p_scope, "global")) { + list_global = true; + } + else if (!strcmp(p_scope, "comp") || !strcmp(p_scope, "component")) { + list_comp = true; + } + else { + if (strcmp(p_scope, "all")) { + ttcn3_debugger.print("Invalid scope. Listing variables in all scopes.\n"); + } + list_local = true; + list_global = true; + list_comp = true; + } + if (list_local) { + for (size_t i = 0; i < variables.size(); ++i) { + ttcn3_debugger.print("%s%s", first ? "" : " ", variables[i]->name); + first = false; + } + } + if (list_global && global_scope != NULL && global_scope->has_variables()) { + global_scope->list_variables(p_filter, first); + } + if (list_comp && component_scope != NULL && component_scope->has_variables()) { + component_scope->list_variables(p_filter, first); + } + if (first) { + ttcn3_debugger.print("No variables found."); + } + ttcn3_debugger.print("\n"); +} + diff --git a/core/Debugger.hh b/core/Debugger.hh new file mode 100644 index 0000000000000000000000000000000000000000..42271c68fa72dfeb0b17c69505a9622d116c71a3 --- /dev/null +++ b/core/Debugger.hh @@ -0,0 +1,527 @@ +/****************************************************************************** + * Copyright (c) 2000-2016 Ericsson Telecom AB + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Baranyi, Botond – initial implementation + * + ******************************************************************************/ + +#ifndef DEBUGGER_HH +#define DEBUGGER_HH + +#include "Vector.hh" +#include "Basetype.hh" +#include "Charstring.hh" +#ifdef TITAN_RUNTIME_2 +#include "RT2/PreGenRecordOf.hh" +#else +#include "RT1/PreGenRecordOf.hh" +#endif + +/** alias for record of charstring */ +typedef PreGenRecordOf::PREGEN__RECORD__OF__CHARSTRING charstring_list; + +// forward declarations +class TTCN3_Debug_Scope; +class TTCN3_Debug_Function; + + +////////////////////////////////////////////////////// +////////////////// TTCN3_Debugger //////////////////// +////////////////////////////////////////////////////// + +/** main debugger class + * + * instantiated once per process at the beginning of the current process, + * destroyed at the end of the current process */ +class TTCN3_Debugger { +public: + + /** type for keeping track of a variable */ + struct variable_t { + /** pointer to the variable object, not owned */ + const void* value; + /** variable name (used for looking up variables), not owned */ + const char* name; + /** name of the variable's type, not owned */ + const char* type_name; + /** variable printing function (using the variable object's log() function) */ + CHARSTRING (*print_function)(const variable_t&); + }; + + /** this type pairs a debug scope with a name, used for storing global and + * component scopes */ + struct named_scope_t { + /** scope name (module name for global scopes, or component type name for + * component scopes), not owned*/ + const char* name; + /** scope pointer, owned */ + TTCN3_Debug_Scope* scope; + }; + + /** type for storing breakpoints */ + struct breakpoint_t { + /** module name, owned */ + char* module; + /** line number */ + int line; + // const char* batch_file; + }; + + /** list of commands coming from the user interface (parameters listed in comments) */ + enum debug_command_t { + // on/off switch + D_SWITCH_OFF, // 0 + D_SWITCH_ON, // 0 + // breakpoints + D_ADD_BREAKPOINT, // 2, module name and line number + D_REMOVE_BREAKPOINT, // 2, module name and line number + D_SET_ERROR_BEHAVIOR, // 1, "yes" or "no" + D_SET_FAIL_BEHAVIOR, // 1, "yes" or "no" + // printing and overwriting data + D_SET_OUTPUT, // 1-2, "stdout", "stderr" or "file" + file name + D_SET_PROCESS, // 1, 'mtc' or component reference + D_PRINT_CALL_STACK, // 0 + D_SET_STACK_LEVEL, // 1, stack level + D_LIST_VARIABLES, // 1-2, "local", "global", "comp", "component" or "all", + optional filter (pattern) + D_PRINT_VARIABLE, // 1+, list of variable names + D_OVERWRITE_VARIABLE, // 2, variable name, new value (in module parameter syntax) + D_PRINT_SNAPSHOTS, // 0 + D_SET_SNAPSHOT_BEHAVIOR, // TBD + // stepping + D_STEP_OVER, // 0 + D_STEP_INTO, // 0 + D_STEP_OUT, // 0 + D_RUN_TO_CURSOR, // 2, module name and line number + // ending the halted state + D_CONTINUE, // 0 + D_EXIT, // 0 + // batch files + D_SET_HALTING_BATCH_FILE // TBD + }; + + /** special breakpoint types, passed to breakpoint_entry() as the line parameter, + * so these need to be 0 or negative, to never conflict with any line number */ + enum special_breakpoint_t { + /** triggered when the local verdict is set to ERROR (by dynamic test case errors) */ + SBP_ERROR_VERDICT = 0, + /** triggered when the local verdict is set to FAIL (by the user) */ + SBP_FAIL_VERDICT = -1 + }; + +private: + + /** the debugger's on/off switch */ + bool active; + + /** the debugger's output file handler */ + FILE* output; + + /** list of all global and component variables, elements are owned */ + Vector<variable_t*> variables; + + /** list of global scopes */ + Vector<named_scope_t> global_scopes; + + /** list of component scopes */ + Vector<named_scope_t> component_scopes; + + /** pointers to debug function objects (resembling a call stack), elements are not owned + * the current function is always the last element in the array (the top element in the stack) */ + Vector<TTCN3_Debug_Function*> call_stack; + + /** list of breakpoints */ + Vector<breakpoint_t> breakpoints; + + /** string containing function call snapshots, owned */ + char* snapshots; + + /** stores the last line hit by breakpoint_entry() */ + breakpoint_t last_breakpoint_entry; + + /** current stack level (reset whenever a breakpoint is reached) */ + int stack_level; + + /** behavior triggered by setting the local verdict to FAIL + * (a breakpoint is activated if set to true) */ + bool fail_behavior; + + /** behavior triggered by setting the local verdict to ERROR + * (a breakpoint is activated if set to true) */ + bool error_behavior; + + ////////////////////////////////////////////////////// + ///////////////// internal functions ///////////////// + ////////////////////////////////////////////////////// + + /** switches the debugger off + * handles the D_SWITCH_OFF command */ + void switch_off(); + + /** switches the debugger on + * handles the D_SWITCH_ON command */ + void switch_on(); + + /** adds a new breakpoint at the specified module and line + * handles the D_ADD_BREAKPOINT command */ + void add_breakpoint(const char* p_module, int p_line /*const char* batch_file*/); + + /** removes the breakpoint from the specified module/line, if it exists + * handles the D_REMOVE_BREAKPOINT command */ + void remove_breakpoint(const char* p_module, int p_line); + + /** sets the behavior of a special breakpoint type + * @param p_state_str "yes" turns the special breakpoint on (as if it was an + * actual breakpoint), "no" turns it off + * handles the D_SET_ERROR_BEHAVIOR and D_SET_FAIL_BEHAVIOR commands */ + void set_special_breakpoint(special_breakpoint_t p_type, const char* p_state_str); + + /** prints the current call stack + * handles the D_PRINT_CALL_STACK command */ + void print_call_stack(); + + /** sets the current stack level to the specified level + * handles the D_SET_STACK_LEVEL command */ + void set_stack_level(int new_level); + + /** prints the specified variable + * handles (one parameter of) the D_PRINT_VARIABLE command */ + void print_variable(const variable_t* p_var) const; + + /** sets the debugger's output to a different stream + * handles the D_SET_OUTPUT command + * @param p_output_type "stdout", "stderr" or "file" + * @param p_file_name output file name, if the output is a file, or NULL */ + void set_output(const char* p_output_type, const char* p_file_name); + + /** returns the index of the specified breakpoint, if found, + * otherwise returns breakpoints.size() */ + size_t find_breakpoint(const char* p_module, int p_line) const; + + /** returns the specified variable, if found, otherwise returns NULL */ + TTCN3_Debugger::variable_t* find_variable(const void* p_value) const; + +public: + /** constructor - called once per process (at the beginning) */ + TTCN3_Debugger(); + + /** destructor - called once per process (at the end) */ + ~TTCN3_Debugger(); + + ////////////////////////////////////////////////////// + ////// methods called from TITAN generated code ////// + ////////////////////////////////////////////////////// + + /** creates, stores and returns a new global scope for the specified module */ + TTCN3_Debug_Scope* add_global_scope(const char* p_module); + + /** creates, stores and returns a new global scope for the specified module */ + TTCN3_Debug_Scope* add_component_scope(const char* p_component); + + /** stores the string representation of the current function's return value + * (only if the debugger is switched on) */ + void set_return_value(const CHARSTRING& p_value); + + /** activates a breakpoint if the specified line and the current function's module + * match any of the breakpoints stored in the debugger + * the special parameter values (SBP_ERROR_VERDICT and SBP_FAIL_VERDICT) only + * trigger a breakpoint if their respective behaviors have been set to do so + * (does nothing if the debugger is switched off) */ + void breakpoint_entry(int p_line /*bool p_stepping_helper*/); + + /** variable printing function for base types */ + static CHARSTRING print_base_var(const variable_t& p_var); + + /** variable printing function for value arrays */ + template <typename T_type, unsigned int array_size, int index_offset> + static CHARSTRING print_value_array(const variable_t& p_var) + { + TTCN_Logger::begin_event_log2str(); + ((VALUE_ARRAY<T_type, array_size, index_offset>*)p_var.value)->log(); + return TTCN_Logger::end_event_log2str(); + } + + /** variable printing function for template arrays */ + template <typename T_value_type, typename T_template_type, + unsigned int array_size, int index_offset> + static CHARSTRING print_template_array(const variable_t& p_var) + { + TTCN_Logger::begin_event_log2str(); + ((TEMPLATE_ARRAY<T_value_type, T_template_type, array_size, + index_offset>*)p_var.value)->log(); + return TTCN_Logger::end_event_log2str(); + } + + /** variable printing function for port arrays */ + template <typename T_type, unsigned int array_size, int index_offset> + static CHARSTRING print_port_array(const variable_t& p_var) + { + TTCN_Logger::begin_event_log2str(); + ((PORT_ARRAY<T_type, array_size, index_offset>*)p_var.value)->log(); + return TTCN_Logger::end_event_log2str(); + } + + /** variable printing function for timer arrays */ + template <typename T_type, unsigned int array_size, int index_offset> + static CHARSTRING print_timer_array(const variable_t& p_var) + { + TTCN_Logger::begin_event_log2str(); + ((TIMER_ARRAY<T_type, array_size, index_offset>*)p_var.value)->log(); + return TTCN_Logger::end_event_log2str(); + } + + /** variable printing function for lazy parameters */ + template <typename EXPR_TYPE> + static CHARSTRING print_lazy_param(const variable_t& p_var) + { + TTCN_Logger::begin_event_log2str(); + ((Lazy_Param<EXPR_TYPE>*)p_var.value)->log(); + return TTCN_Logger::end_event_log2str(); + } + + ////////////////////////////////////////////////////// + ////// methods called by other debugger classes ////// + ////////////////////////////////////////////////////// + + /** returns true if the debugger is switched on */ + bool is_on() const { return active; } + + /** prints formatted string to the debugger's output stream */ + void print(const char* fmt, ...) const; + + /** adds the specified function object pointer to the call stack + * (only if the debugger is switched on) */ + void add_function(TTCN3_Debug_Function* p_function); + + /** adds the specified scope object pointer to the current function's scope list + * (only if the debugger is switched on and the call stack is not empty) */ + void add_scope(TTCN3_Debug_Scope* p_scope); + + /** removes the specified function object pointer from the call stack, if it is + * the function at the top of the stack */ + void remove_function(TTCN3_Debug_Function* p_function); + + /** removes the specified scope object pointer from the current function's scope list + * (only if the call stack is not empty) */ + void remove_scope(TTCN3_Debug_Scope* p_scope); + + /** finds or creates, and returns the variable entry specified by the parameters + * + * if the call stack is empty, an entry for a global or component variable is + * created and stored in the main debugger object (if it doesn't already exist); + * if the call stack is not empty (and if the debugger is switched on), the + * variable entry for a local variable is created and stored by the current function*/ + const variable_t* add_variable(const void* p_value, const char* p_name, const char* p_type, + CHARSTRING (*p_print_function)(const variable_t&)); + + /** removes the variable entry for the specified local variable in the current + * function (only if the call stack is not empty) */ + void remove_variable(const variable_t* p_var); + + /** returns the global scope object associated with the specified module */ + const TTCN3_Debug_Scope* get_global_scope(const char* p_module) const; + + /** returns the component scope object associated with the specified component type */ + const TTCN3_Debug_Scope* get_component_scope(const char* p_component) const; + + /** appends the specified function call snapshot to the end of the snapshot string */ + void add_snapshot(const char* p_snapshot); + + /** executes a command received from the user interface */ + void execute_command(debug_command_t p_command, const charstring_list& p_arguments); +}; + +/** the main debugger object */ +extern TTCN3_Debugger ttcn3_debugger; + + +////////////////////////////////////////////////////// +//////////////// TTCN3_Debug_Scope /////////////////// +////////////////////////////////////////////////////// + +/** debugger scope class + * + * instantiated at the beginning of every code block in the TTCN-3 code (except + * for the code blocks of functions), plus one (global scope) instance is created + * for every module and one (component scope) for every component type + * + * the class' main purpose is to track which local variables were created in the + * current code block or to track which of the main debugger object's variables + * belong to which global or component scope */ +class TTCN3_Debug_Scope { + + /** list of pointers to local variable entries from the current function object or + * global or component variable entries from the main debugger object + * (the elements are not owned)*/ + Vector<const TTCN3_Debugger::variable_t*> variables; + +public: + + /** constructor - lets the current function know of this new scope */ + TTCN3_Debug_Scope(); + + /** destructor - tells the current function to delete the variable entries listed + * in this instance */ + ~TTCN3_Debug_Scope(); + + ////////////////////////////////////////////////////// + ////// methods called from TITAN generated code ////// + ////////////////////////////////////////////////////// + + /** passes the parameters to the main debugger or current function object to + * create and store a variable entry from them, and tracks this new variable + * by storing a pointer to it + * (local variables are only created and stored if the debugger is switched on) */ + void add_variable(const void* p_value, const char* p_name, const char* p_type, + CHARSTRING (*p_print_function)(const TTCN3_Debugger::variable_t&)); + + ////////////////////////////////////////////////////// + ////// methods called by other debugger classes ////// + ////////////////////////////////////////////////////// + + /** returns true if there is at least one variable in the scope object */ + bool has_variables() const { return !variables.empty(); } + + /** returns the specified variable, if found, otherwise returns NULL */ + const TTCN3_Debugger::variable_t* find_variable(const char* p_name) const; + + /** prints the names of variables in this scope that match the specified pattern + * @param p_filter the mentioned pattern + * @param p_first true if no variables have been printed yet */ + void list_variables(const char* p_filter, bool& p_first) const; +}; + + +////////////////////////////////////////////////////// +/////////////// TTCN3_Debug_Function ///////////////// +////////////////////////////////////////////////////// + +/** debugger function class + * + * instantiated at the beginning of every function, destroyed when function execution ends + * + * tracks all variables created during the function's execution (local variables), + * including the function's parameters, and stores the function's return value */ +class TTCN3_Debug_Function { + + /** name of the function, not owned */ + const char* function_name; + + /** the TTCN-3 keyword(s) used to define the function ("function", "testcase", + * "altstep", "template" or "external function"), not owned */ + const char* function_type; + + /** name of the module this function is defined in, not owned */ + const char* module_name; + + /** names of the function's parameters (in the order of their declaration), owned */ + charstring_list* parameter_names; + + /** types (directions) of the function's parameters ("in", "inout" or "out"), owned */ + charstring_list* parameter_types; + + /** list of local variables tracked by this object, the array elements are owned */ + Vector<TTCN3_Debugger::variable_t*> variables; + + /** list of pointers to the scope objects of code blocks in the function, + * the elements are not owned + * (currently not used for anything) */ + Vector<TTCN3_Debug_Scope*> scopes; + + /** pointer to the global scope object, not owned + * (may be NULL, if the module's global scope is empty) */ + const TTCN3_Debug_Scope* global_scope; + + /** pointer to the runs-on component's scope object, not owned + * (may be NULL, if the component's scope is empty or if the function has no runs-on clause) */ + const TTCN3_Debug_Scope* component_scope; + + /** the function's return value (unbound if the return value has not been set yet, + * or if the function doesn't return anything) + * + * since this is only set right before the function ends, it is only accessible + * from the destructor */ + CHARSTRING return_value; + +public: + + /** constructor - initializes the instance with the specified parameters, + * retrieves the global scope and component scope from the main debugger object */ + TTCN3_Debug_Function(const char* p_name, const char* p_type, const char* p_module, + const charstring_list& p_parameter_names, const charstring_list& p_parameter_types, const char* p_component_name); + + /** destructor - frees resources and saves the function's ending snapshot + * (including the values of 'out' and 'inout' parameters and the return value) + * in the main debugger object (only if the debugger is switched on) */ + ~TTCN3_Debug_Function(); + + ////////////////////////////////////////////////////// + ////// methods called from TITAN generated code ////// + ////////////////////////////////////////////////////// + + /** creates, stores and returns the variable entry of the local variable + * specified by the parameters (only if the debugger is switched on) */ + const TTCN3_Debugger::variable_t* add_variable(const void* p_value, const char* p_name, + const char* p_type, CHARSTRING (*p_print_function)(const TTCN3_Debugger::variable_t&)); + + /** stores the string representation of the value returned by the function */ + void set_return_value(const CHARSTRING& p_value); + + /** saves the function's initial snapshot (including the values on 'in' and + * 'inout' parameters) in the main debugger object + * (only if the debugger is switched on) */ + void initial_snapshot() const; + + ////////////////////////////////////////////////////// + ////// methods called by other debugger classes ////// + ////////////////////////////////////////////////////// + + /** adds the specified scope object pointer to the function's scope list */ + void add_scope(TTCN3_Debug_Scope* p_scope); + + /** removes the specified scope object pointer from the function's scope list, + * if it is the last scope in the list */ + void remove_scope(TTCN3_Debug_Scope* p_scope); + + /** removes the specified variable from the variable list */ + void remove_variable(const TTCN3_Debugger::variable_t* p_var); + + /** searches for the variable entry with the specified name in the function's + * local variable list, the global scope (if any) and the component scope (if any), + * returns NULL, if the variable was not found */ + const TTCN3_Debugger::variable_t* find_variable(const char* p_name) const; + + /** prints the function's type, name and current values of parameters */ + void print_function() const; + + /** returns the name of the module the function was defined in */ + const char* get_module_name() const { return module_name; } + + /** prints the names of variables specified by the parameters (separated by spaces) + * handles the D_LIST_VARIABLES debugger command + * @param p_scope specifies which scope to print variables from: + * - "local" - the function's local variables (including variables in code blocks) + * - "global" - variables in the module's global scope + * - "comp" or "component" - variables in the function's runs-on component scope + * - "all" - all variables visible in the function (i.e. all of the above) + * @param p_filter a pattern to filter variable names further */ + void list_variables(const char* p_scope, const char* p_filter) const; +}; + +/** This macro stores a function's return value in the current function. + * The returned value might be an expression, so it is copied to a temporary first, + * to avoid being evaluated twice. */ +#define DEBUGGER_STORE_RETURN_VALUE(tmp, ret_val) \ + (tmp = (ret_val), \ + ttcn3_debugger.set_return_value((TTCN_Logger::begin_event_log2str(), \ + tmp.log(), \ + TTCN_Logger::end_event_log2str())), \ + tmp) + +#endif /* DEBUGGER_HH */ + diff --git a/core/Makefile b/core/Makefile index 4d5957bb34b0bf611454ced1bd7206315f9c7aa1..d4fcd8446269a6787a27991ddff8313ad41983c6 100644 --- a/core/Makefile +++ b/core/Makefile @@ -104,7 +104,7 @@ Module_list.cc Objid.cc Octetstring.cc Parallel_main.cc Port.cc RAW.cc \ Runtime.cc Single_main.cc Snapshot.cc Struct_of.cc Template.cc TEXT.cc \ Textbuf.cc Timer.cc Param_Types.cc Universal_charstring.cc \ Verdicttype.cc XER.cc XmlReader.cc TitanLoggerControlImpl.cc TCov.cc JSON.cc \ -Profiler.cc ProfilerTools.cc ProfMerge_main.cc $(RT2_SOURCES) +Profiler.cc ProfilerTools.cc ProfMerge_main.cc Debugger.cc $(RT2_SOURCES) # Keep GENERATED_SOURCES at the beginning. This may speed up parallel builds # by starting early the compilation of the largest files. @@ -186,7 +186,7 @@ Port.hh Event_Handler.hh Struct_of.hh Array.hh Optional.hh Textbuf.hh Encdec.hh Module_list.hh Parameters.h Addfunc.hh RAW.hh BER.hh TEXT.hh ASN_Null.hh \ ASN_Any.hh ASN_External.hh ASN_EmbeddedPDV.hh ASN_CharacterString.hh XER.hh \ XmlReader.hh cversion.h TitanLoggerControl.ttcn TitanLoggerApi.xsd Vector.hh \ -JSON.hh Profiler.hh RefdIndex.hh ProfilerTools.hh +JSON.hh Profiler.hh RefdIndex.hh ProfilerTools.hh Debugger.hh # Copied during "make install" ifdef REGEX_DIR diff --git a/core/Runtime.cc b/core/Runtime.cc index 087514e831f08a865e3f8ae5ea634abcb7a8de08..27ca3b3b28c021d65d08dec2ca960062654a0d1e 100644 --- a/core/Runtime.cc +++ b/core/Runtime.cc @@ -2129,6 +2129,12 @@ void TTCN_Runtime::setverdict_internal(verdicttype new_value, TTCN_Logger::log_setverdict(new_value, old_verdict, local_verdict); else TTCN_Logger::log_setverdict(new_value, old_verdict, local_verdict, reason, reason); } + if (new_value == FAIL) { + ttcn3_debugger.breakpoint_entry(TTCN3_Debugger::SBP_FAIL_VERDICT); + } + else if (new_value == ERROR) { + ttcn3_debugger.breakpoint_entry(TTCN3_Debugger::SBP_ERROR_VERDICT); + } } void TTCN_Runtime::set_begin_controlpart_command(const char *new_command) diff --git a/core/TTCN3.hh b/core/TTCN3.hh index 3d7e815d862bb5a2159e50f09a79fa4d0f636fb9..869d3fa8d8f8948d61f50feae032b68536b8dd8a 100644 --- a/core/TTCN3.hh +++ b/core/TTCN3.hh @@ -85,5 +85,6 @@ #include "Error.hh" #include "XmlReader.hh" #include "Profiler.hh" +#include "Debugger.hh" #endif