Übersicht

  • Ziele der Arbeit
  • Testbarkeit in a Nutshell
  • Code Analyse-Tool
    • Regelbasierte Analysen
    • Architektur
  • Komponenten von "YaCI"
    • Rule Parser
    • Rule Analyser
    • Result Generator
    • JTransformer Eclipse Integration
  • Zusammenfassung
  • Ausblick
There are two ways to write error-free programs: only the third one works.
Alan J. Perlis

Ziele der Arbeit

 

  • Statische Code Analyse

  • Untersuche Testbarkeit von Code

  • Regelbasierte Analysen

  • Implementierung einer Werkzeugpalette

  • Ausführbare Analyse-Tasks

Testbarkeit in a Nutshell

 

  • Testbarkeit ist

    ... der Grad, bis zu dem eine Software (eine Softwarekomponente) durch Tests und Test-Suites unterstützt werden kann, durch die wiederum Fehler gefunden und vermieden werden können.


  • Fokus auf Objektorientierte Programmiersprachen

  • Auffinden von "Testabilitiy" Design Pattern

  • Leichtere Umsetzung von Unit Tests und Test-Suites

  • Keine Berechnung von Metriken

Testbarkeit in a Nutshell

Beispiel eines Design Pattern

Vermeide komplexe Logik in Konstruktoren


public class LottoTicket {
  private Database mDataBase = null;

  /* bad */
  public LottoTicket() {
    mDatabase = new Database();
  }

  /* good */
  public LottoTicket(Database db) {
    mDatabase = db;
  }
}

Code Analyse-Tool

Code Analyse-Tool


  • Analyse anhand von Regeln

  • Eigene Sprache für Regelformulierung

  • Modulare Werkzeugpalette

  • Untersuchung des AST

  • Visualisierung der Ergebnisse

=> Yet another Code Inspector (YaCI)

Yet another Code Inspector (YaCI)

Regelbasierte Analysen (1/2)

  • Leichte Erweiterung der Analyse Tasks

  • Regeln als "Conditional Sentences"

  • Flexible "Premise" und "Conclusion"

  • Allgemeiner Aufbau:
/* I am a long Description
    maybe over one or more lines */
Short Description @ when <Premise> then <Conclusion>.

Yet another Code Inspector (YaCI)

Regelbasierte Analysen (2/2)

Vermeide statische oder finale Methoden

/* Prevent constructors with complex logic */
Complex Constructor @ when constructor
  contain keyword new more than 2 times
  and contain control_flow more than 2 times
  and has line_count greater 10 then drop error.

Bevorzuge Kompositionen anstatt Vererbung

/* Prefer composition pattern opposite inheritance */
Find Inheritance @ when class has inheritance of depth
  greater 3 then drop warning.

Yet another Code Inspector (YaCI)

Architektur

  • Umsetzung mit SWI-Prolog

  • Modularer Aufbau

    • Grammatik und Rule Parser

    • Rule Analyser

    • Result Generator

    • JTransformer Eclipse Integration

Yet another Code Inspector (YaCI)

Architektur

Description

Komponenten


von YaCI

YaCI - Komponenten

Rule Parser

Description

YaCI - Komponenten

Rule Parser

  • Verarbeitung der Sequenz aus Zeichen
  •  

  • Umwandlung in interne Darstellung
  •  

  • Spezifikation der Grammatik für Analyse-Regeln
  •  

  • Umsetzung mittels "Definite Clause Grammar"

YaCI - Komponenten

Rule Parser (Live-Demo)

?-  string_codes("/* Prevent constructors with complexe
      logic */\nComplex Constructors @ when constructor [...]
      then drop error.", Rule),
    phrase(parse_rule(Result), Rule).

Result = [rule(
  when(constructor(and(contain([keyword(new), greater, 2]),
    and(contain([control_flow, greater|...]),
    has([line_count, greater|...]))))),
  then(drop(error)),
  description(short('Complex Constructors'),
    long('Prevent constructors with complexe logic'))
)]

YaCI - Komponenten

Rule Analyser

Description

YaCI - Komponenten

Rule Analyser

  • Verarbeitung der Conditional Sentences

  • Untersucht den AST (Faktendatenbank)

  • Generiert Ergebnisse der Analyse

  • Ergebnis umfasst betroffene Elemente

YaCI - Komponenten

Rule Analyser - AST Fakten

compilationUnitT(28591, 28594, 28593,
    [28595, 28596], [28597]).
packageT(28594, ’de.uniwue.thesis’).
classT(28597, 28591, ’AstExample’, [], [28598, 28599, 28600]).
methodT(28600, 28597, getNode, [28613], 10001, [], [], 28614).
constructorT(28599, 28597, [], [], [], 28605).
fieldT(28598, 28597, 28603, mNodes, null).
paramT(28613, 28600, 10080, index).
newT(28610, 28608, 28599, null, [], 21199, [], 28612, null) .
returnT(28616, 28614, 28600, 28617).
modifierT(28604, 28598, private).
modifierT(28615, 28600, public).

YaCI - Komponenten

Rule Analyser (Live-Demo)

?-  analyse_rule(when(constructor(and(
      contain([keyword(new), greater, 2]),
      and(contain([control_flow, greater|...]),
        has([line_count, greater|...])
      )
    )))), Result).

Result = match(class(28597), scopes([
  constructor(id(28604), name('ComplexConstructor'),
    hints([new(1, [28645]), control_flows(2, [28624|...]),
      line_count(8)])
  )
])).

YaCI - Komponenten

Result Generator

Description

YaCI - Komponenten

Result Generator

  • Verbindet Analyser und Eclipse Integration

  • Transformation der Analyse-Resultate

  • Aufbereitung zur Einbindung in GUI

YaCI - Komponenten

Result Generator (Live-Demo)

?-  generate_match_term(match(class(28597), scopes([
      constructor(id(28604), name('ComplexConstructor'),
      hints([new(1, [28645]), control_flows(2, [28624,28645]),
        line_count(8)])
    )])), Result).

Result = result(result_group(28597), elements([
  constructor(28604), new_keyword(28645),
  control_flow(28624), control_flow(28645)
])).

YaCI - Komponenten

JTransformer Eclipse Integration

Description

YaCI - Komponenten

JTransformer Eclipse Integration

  • Resultat der Analyse in GUI einbinden

  • JTransformer Eclipse Plugin

  • Verwendung der JTransformer API

  • Resultate in Control Center View

  • Vollständige Integration in JTransformer Plugin

YaCI - Komponenten

JTransformer Eclipse Integration - JTransformer API

analysis_api:analysis_definition(
  ShortDescription, Trigger, SeverityLevel,
  AnalysisGroup, LongDescription
).

analysis_api:analysis_result(Id, Group, Result) :-
  /**
   * run analysis here and create result terms
   * for each matched ast element call make_result_term/3
   */
  mark_result_term(AstElement, 'Description of element', Result).
      

YaCI - Komponenten

JTransformer Eclipse Integration (Ablauf)

  • Verbindet alle YaCI-Komponenten

  • Aufruf von YaCI aus Eclipse heraus

  • Ablauf:

    1. 1. Laden der *.rules Datei
    2. 2. Parsen der Regeln
    3. 3. analysis_definition initialisieren
    4. 4. analyse_result initialisieren
    5. 5. Zur Laufzeit die Ergebnisse an UI anhängen

YaCI - Komponenten

JTransformer Eclipse Integration (Live-Demo)

JTransformer Control Center

Zusammenfassung

Zusammenfassung

  • Grammatik zur Regelformulierung etabliert

  • Parser implementiert

  • AST-Analyse anhand Regeln implementiert

  • Integration in JTransformer Eclipse Plugin

  • Selektion einzelner Analyse-Tasks via UI

  • Analyse kompletter Java-Projekten

Zusammenfassung

Analyse von "Joda Time"

  • Date- und Time-Utility Framework

  • Allgemeines zum AST des Projektes:
    • Fakten: 355386
    • Methoden: 9126
    • Klassen: 529
    • Konstruktoren: 705

  • Analyse anhand von 10 "Testability"-Regeln

Zusammenfassung

Analyse von "Joda Time" (Live-Demo)

YaCI Results of Joda Time

Ausblick

Ausblick

 

  • Erweiterung der Grammatik

  • Verbesserung der AST-Analyse

  • Werkzeugpalette in CI Prozess integrieren

  • Adaption auf weitere Programmiersprachen

Ausblick

Continuous Integration

Testability-Regeln (1/2)

/* Prevent complex, private methods */
Rule 1 @ when method is private and has line_count [...].
/* Prevent final or static methods */
Rule 2 @ when method is final or method is static then drop warning.
/* Do not use keyword new to often */
Rule 3 @ when method contain keyword new more than 3 times
  then drop warning.
/* Prevent constructors with complex logic */
Rule 4 @ when constructor contain keyword new more than 2 times
  and contain control_flow more than 2 times and has line_count
  greater 5 then drop error.
/* Prevent the Singleton pattern */
Rule 5 @ when constructor is private and method has
  return_type of class and is static and contain access of
  static field then drop warning.

Testability-Regeln (2/2)

/* Prefer composition pattern opposite inheritance */
Rule 6 @ when class has inheritance of depth
  greater 2 then drop warning.
/* Wrap external libraries */
Rule 7 @ when method contain call of class_method more
  than 0 times not belongs to package "org.joda.time"
  then drop warning.
/* Prevent service lookups or static method calls */
Rule 8 @ when method contain call of class_method
  more than 0 times then drop info.
/* Prefer interface-based design */
Rule 9 @ when method is public and has parameter
  not of type interface or constructor has parameter
  not of type interface then drop info.
/* Do not declare class as final */
Rule 10 @ when class is final then drop info.
      

Regeln - Übersicht von Komponenten

Regeln - Grammatik

Rule: when <PREMISE> [<CONJUNC.> <PREMISE> ...] then <CONCLUSION>.

Premise: <SCOPE> <CONDITION> <INSTRUCTION> [<CONJUNC.> <CONDITION> <INSTRUCTION>, ...]

Instruction: <CONDITION_PART> | <ACCESSOR> | <RELATION>

Conjunction:   and | or

Scope:   class | method | ...

Condition:   is | has | ...

Conclusion:   drop 'string' | pattern 'string' | score 'number'

Implementierung der Grammatik (1/2)

parse_rules(Rules) -->
  blanks, list_of_rules(Rules).

list_of_rules([SingleRule]) --> rule(SingleRule).
list_of_rules([SingleRule|Rules]) -->
  rule(SingleRule), list_of_rules(Rules).

rule(Rule) -->
  rule_description(Description), !,
  when, blank, blanks, rule_premise(Premise),
  then, blank, blanks, rule_conclusion(Conclusion),
  ".", ( "\n" ; eos ),
  { Rule = rule(when(Premise), Conclusion, Description) }.

Implementierung der Grammatik (2/2)

rule_scope(ScopeResult) -->
  single_rule_scope(Result),
  { ScopeResult = Result }.

rule_scope(Scope) -->
  single_rule_scope(OpeningScope),
  and, blank, blanks, rule_scope(ClosingScope),
  { Scope = and(OpeningScope, ClosingScope) }.

rule_scope(Scope) -->
  single_rule_scope(OpeningScope),
  or, blank, blanks, rule_scope(ClosingScope),
  { Scope = or(OpeningScope, ClosingScope) }.

JTransformer

  • Analyse und Transformation von Quellcode

  • Erzeugen des AST in Form einer Faktendatenbank

  • PEFs (Program Element Facts)

  • Schnittstelle für Analyseaufgaben und zum Befüllen der GUI

  • Statistiken über erzeugte Faktendatenbank

  • Control Center View zum Verwalten der Analysen

JTransformer - analysis_definition

add_single_definition(
    rule(_,then(Conclusion),
    description(Short, Long))
  ) :-
  Short = short(ShortLabel),
  Long = long(LongLabel),
  extractLogLevel(Conclusion, LogLevel),
  assert(
    analysis_api:analysis_definition(
      ShortLabel,
      onSave,
      LogLevel,
      'Testability',
      LongLabel
    )
  ).

JTransformer - analysis_result

create_single_analysis_result(Premise, Description) :-
  Description = description(short(RuleID), _),
  assert(
    analysis_api:analysis_result(
      RuleID,
      ResultGroup,
      Result
    ) :-
    (
      analyse_rule(Premise, AnalysisResult),
      generate_match_term(AnalysisResult, MatchTerm),
      MatchTerm = result(ResultGroup, RuleElements),
      mark_match(RuleElements, Result)
    )
  ).

YaCI Analyser (1/2)

analyse_rule(when(Premise), Result) :-
  find_compilation_unit_id(_, CompilationClassID),
  apply_premise(Premise, CompilationClassID, PremiseResult),
  Result = PremiseResult.

apply_premise(Scope, Context, Result) :-
  apply_scope(Scope, Context, ScopeResult),
  filter_unbound(ScopeResult, BoundScopeResult),
  Result = match(class(Context), scopes(BoundScopeResult)).

apply_scope(Scope, Context, [ScopeResult]) :-
  apply_single_scope(Scope, Context, ScopeResult).

YaCI Analyser (2/2)

apply_single_scope(constructor(Condition),
    Context, Result) :-
  constructorT(ConstructorID, Context, _ParamList,
    _ExceptionList, _ParamTypeList, _BodyID),
  classT(Context, _, ClassName, _, _),
  is_direct_child(ConstructorID, Context),
  apply_condition(Condition, Context,
    ConstructorID, ConditionResult),
  Result = constructor(
    id(ConstructorID), name(ClassName),
    hints(ConditionResult)
  ); fail.

apply_single_scope(method(Condition), Context, Result) :- /* ... */

apply_single_scope(class(Condition), Context, Result) :- /* ... */