Difference between revisions of "CA Library Implementation"

(+ class diagram)
(Rule checking: Checking Standard Rules)
Line 105: Line 105:
  
 
In the <e>rescue</e> clause all possible exceptions are caught and recorded. In case of such an exception it then proceeds to the next class.
 
In the <e>rescue</e> clause all possible exceptions are caught and recorded. In case of such an exception it then proceeds to the next class.
 +
 +
=== Checking ''Standard'' Rules ===
 +
 +
The relatively large class <e>{CA_ALL_RULES_CHECKER}</e> is responsible for checking ''standard rules''. It does this in a straightforward way. It is a subclass of <e>{AST_ITERATOR}</e>, a realization of a visitor on the AST.
 +
 +
Rules can register their actions with <e>{CA_ALL_RULES_CHECKER}</e> by calling a procedure like <e>add_bin_lt_pre_action (a_action: attached PROCEDURE [ANY, TUPLE [BIN_LT_AS]])</e> or <e>add_if_post_action (a_action: attached PROCEDURE [ANY, TUPLE [IF_AS]])</e>. These "pre" and "post" actions exist for many other types of AST nodes as well. All the registered actions are stored in <e>ACTION_SEQUENCE</e> variables:
 +
<e>
 +
if_pre_actions, if_post_actions: ACTION_SEQUENCE [TUPLE [IF_AS]]
 +
 +
add_if_post_action (a_action: attached PROCEDURE [ANY, TUPLE [IF_AS]])
 +
  do
 +
    if_post_actions.extend (a_action)
 +
  end
 +
 +
-- And similar for all other relevant AST nodes...
 +
</e>
 +
 +
The corresponding visitor procedures are redefined. This is done is the following way:
 +
<e>
 +
process_if_as (a_if: IF_AS)
 +
  do
 +
    if_pre_actions.call ([a_if])
 +
    Precursor (a_if)
 +
    if_post_actions.call ([a_if])
 +
  end
 +
 +
-- And similar for all other relevant AST nodes...
 +
</e>
 +
 +
Since the actual iteration over the AST is done in the ancestor we need only very little code to analyze a class:
 +
<e>
 +
feature {CA_RULE_CHECKING_TASK} -- Execution Commands
 +
 +
  run_on_class (a_class_to_check: CLASS_C)
 +
      -- Check all rules that have added their agents.
 +
    local
 +
      l_ast: CLASS_AS
 +
    do
 +
      last_run_successful := False
 +
      l_ast := a_class_to_check.ast
 +
      class_pre_actions.call ([l_ast])
 +
      process_class_as (l_ast)
 +
      class_post_actions.call ([l_ast])
 +
      last_run_successful := True
 +
    end
 +
</e>
 +
 +
This code analyzes a class for all active ''standard'' rules. <e>class_pre_actions</e> and <e>class_post_actions</e> are action sequences that are identical to those for the AST nodes. <e>process_class_as</e>, which is implemented in <e>{AST_ITERATOR}</e> will recursively visit all relevant AST nodes and execute their action sequences.

Revision as of 09:55, 7 March 2014

<< 6. Adding New Rules | 8. UI Implementation > >


The code for Code Analysis is located at three different places in the EVE source:

  1. The framework—the by far largest part, with the rule checking, the rules, the control flow graph functionality, and more—is represented as a library;
  2. The graphical user interface can be found in the interface cluster of EVE;
  3. The command-line interface for code analysis is a single class in the tty cluster of EVE.

The whole code analysis framework is located in the library code_analysis.

Class Relations

The following diagram shows an overview of the relations between the classes of the code analysis framework. All classes are located in the code_analysis library except for CLASS_C (EiffelStudio), ROTA_TIMED_TASK_I (ecosystem cluster), EWB_CODE_ANALYSIS (command-line interface), and ES_CODE_ANALYSIS_BENCH_HELPER (GUI).

The most interesting classes of the code analysis framework.

Interface

In this section it is explained from a client view how to use the code analyzer. The code analyzer is represented by the class CA_CODE_ANALYZER, so a client must have or access an instance of this class. Before the analyzer can be launched all the classes that shall be analyzed must be added using one of the following features. If you use more than one of these commands then the added classes from all commands will be conjoined.

{CA_CODE_ANALYZER}.add_whole_system 
Adds all the classes that are part of the current system. Classes of referenced libraries will not be added. So, for example, if your system consists of the classes MY_MAIN, MY_BOX, and MY_ITEM then these three classes will be added to the list of classes to be analyzed.
.add_class (a_class: attached CONF_CLASS) 
Adds a single class.
.add_classes (a_classes: attached ITERABLE [attached CONF_CLASS]) 
Adds a list of classes.
.add_cluster (a_cluster: attached CLUSTER_I) 
Adds all classes of a cluster (and all the classes of the sub-clusters recursively).
.add_group (a_group: attached CONF_GROUP) 
Adds all classes of a configuration group. An example of a configuration group is a library.

Here are other features which can be called before starting to analyze:

{CA_CODE_ANALYZER}.clear_classes_to_analyze 
Removes all classes that have been added to the list of classes to analyze.
.add_completed_action (a_action: attached PROCEDURE [ANY, TUPLE [ITERABLE [TUPLE [detachable EXCEPTION, CLASS_C]]]]) 
Adds `a_action' to the list of procedures that will be called when analysis has completed. The procedures have one argument, a list of exceptions (with the corresponding class). In the case an exception is thrown during analysis the exception is caught by the code analyzer and is added to this list. In the graphical user interface such exceptions would show up as errors at the top of the list of rule violations.
.add_output_action (a_action: attached PROCEDURE [ANY, TUPLE [READABLE_STRING_GENERAL]]) 
Adds `a_action' to the procedures that are called for outputting the status. The final results (rule violations) are not given to these procedures. These output actions are used by the command-line mode and by the status bar in the GUI.
.is_rule_checkable (a_rule: attached CA_RULE): BOOLEAN 
Tells whether `a_rule' will be checked based on the current preferences and based on the current checking scope (whole system or custom set of classes).

Then, to start analyzing simply call {CA_CODE_ANALYZER}.analyze.

Rule checking

In the GUI we want to be able to continue to work while the code analyzer is running. Analyzing larger sets of classes (such as whole libraries) can take from several seconds to several minutes. For this reason the code analyzer uses an asynchronous task, {CA_RULE_CHECKING_TASK}. In {CA_CODE_ANALYZER}.analyze this task (l_task) is invoked as follows:

In {CA_CODE_ANALYZER}.analyze:

create l_task.make (l_rules_checker, l_rules_to_check, classes_to_analyze, agent analysis_completed)
l_task.set_output_actions (output_actions)
rota.run_task (l_task)

{CA_RULE_CHECKING_TASK} essentially runs the whole analysis. Like all other conformants to {ROTA_TASK_I} this class executes a series of steps between which the user interface gets some time to process its events. In {CA_RULE_CHECKING_TASK} each step analyses one class. This means that a class is checked by all the rules for violations. The following code does that:

From {CA_RULE_CHECKING_TASK}:

step
    -- <Precursor>
  do
    if has_next_step then
        -- Gather type information
      type_recorder.clear
      type_recorder.analyze_class (classes.item)
      context.set_node_types (type_recorder.node_types)
      context.set_checking_class (classes.item)
 
      across rules as l_rules loop
          -- If rule is non-standard then it will not be checked by l_rules_checker.
          -- We will have the rule check the current class here:
        if
          l_rules.item.is_enabled.value
          and then attached {CA_CFG_RULE} l_rules.item as l_cfg_rule
        then
          l_cfg_rule.check_class (classes.item)
        end
      end
 
        -- Status output.
      if output_actions /= Void then
        output_actions.call ([ca_messages.analyzing_class (classes.item.name)])
      end
 
      rules_checker.run_on_class (classes.item)
 
      classes.forth
      has_next_step := not classes.after
      if not has_next_step then
        completed_action.call ([exceptions])
      end
    end
  rescue
      -- Instant error output.
    if output_actions /= Void then
      output_actions.call ([ca_messages.error_on_class (classes.item.name)])
    end
    exceptions.extend ([exception_manager.last_exception, classes.item])
      -- Jump to the next class.
    classes.forth
    has_next_step := not classes.after
    if not has_next_step then
      completed_action.call ([exceptions])
    end
    retry
  end

type_recorder is of type {CA_AST_TYPE_RECORDER}. It uses a functionality of the Eiffel compiler to determine the type of some AST nodes in the current class. The AST itself (as provided by the Eiffel compiler) does not contain any type information. context has type {CA_ANALYSIS_CONTEXT} and contains any side-information such as the previously mentioned types and the current class. The rules were given this context before so that they can access it when needed.

The across loop only checks control flow graph rules. All the standard rules are checked by the line rules_checker.run_on_class (classes.item). rules_checker has type {CA_ALL_RULES_CHECKER}. This is the class where each rule must register the AST nodes the rule visits. run_on_class iterates over the AST and calls all the actions that were registered by the standard rules. So this is the way all rules are used to check the current class. step is executed repeatedly until there are no classes left to analyze.

In the rescue clause all possible exceptions are caught and recorded. In case of such an exception it then proceeds to the next class.

Checking Standard Rules

The relatively large class {CA_ALL_RULES_CHECKER} is responsible for checking standard rules. It does this in a straightforward way. It is a subclass of {AST_ITERATOR}, a realization of a visitor on the AST.

Rules can register their actions with {CA_ALL_RULES_CHECKER} by calling a procedure like add_bin_lt_pre_action (a_action: attached PROCEDURE [ANY, TUPLE [BIN_LT_AS]]) or add_if_post_action (a_action: attached PROCEDURE [ANY, TUPLE [IF_AS]]). These "pre" and "post" actions exist for many other types of AST nodes as well. All the registered actions are stored in ACTION_SEQUENCE variables:

if_pre_actions, if_post_actions: ACTION_SEQUENCE [TUPLE [IF_AS]]
 
add_if_post_action (a_action: attached PROCEDURE [ANY, TUPLE [IF_AS]])
  do
    if_post_actions.extend (a_action)
  end
 
-- And similar for all other relevant AST nodes...

The corresponding visitor procedures are redefined. This is done is the following way:

process_if_as (a_if: IF_AS)
  do
    if_pre_actions.call ([a_if])
    Precursor (a_if)
    if_post_actions.call ([a_if])
  end
 
-- And similar for all other relevant AST nodes...

Since the actual iteration over the AST is done in the ancestor we need only very little code to analyze a class:

feature {CA_RULE_CHECKING_TASK} -- Execution Commands
 
  run_on_class (a_class_to_check: CLASS_C)
      -- Check all rules that have added their agents.
    local
      l_ast: CLASS_AS
    do
      last_run_successful := False
      l_ast := a_class_to_check.ast
      class_pre_actions.call ([l_ast])
      process_class_as (l_ast)
      class_post_actions.call ([l_ast])
      last_run_successful := True
    end

This code analyzes a class for all active standard rules. class_pre_actions and class_post_actions are action sequences that are identical to those for the AST nodes. process_class_as, which is implemented in {AST_ITERATOR} will recursively visit all relevant AST nodes and execute their action sequences.