Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Latest commit

 

History

History
87 lines (65 loc) · 5.49 KB

extensions.md

File metadata and controls

87 lines (65 loc) · 5.49 KB

Abide Extensions

The abide framework was build to be easily extensible, and not only with respect to rule addition. One can also extend two other key features of the framework, however doing so is slightly more involved as rule contribution.

Adding new directives

Directives are simply traits that will be mixed in to the rule context at rule instantiation. By default, a simple Context value will be used, but this behavior can be overriden by defining a companion object to the rule that extends the ContextGenerator trait. These generators will instantiate the correct context for the companion rule and the abide compiler plugin will generalise the context in order to share cache.

Concretely, given a generator that creates Context typed contexts and another that generates C <: Context, all rules will share the context instance with type C since it generalises Context. This enables cache sharing without requiring knowledge of existing rules and context types.

To bind the the type C <: Context to the rule that will be consuming this context, we define a companion object

object SomeRule extends ContextGenerator {
  // C is typically a mixin of Context and some other stuff
  def getContext(universe : SymbolTable) : C = new C
}

class SomeRule(val context : C) extends Rule { ... }

and the abide compiler plugin will automatically share the C typed context between all rules for which it remains valid.

Note that any SomeRule subtype will also require a C typed context even if SomeRule is not actually used in the verification run, and the framework will indeed instantiate a C context to automatically guarantee type safety.

Defining analyzers

In order to perform rule verification, abide relies on Analyzer instances that know how to actually apply a rule to source trees. For example, in the case of TraversalRule classes, the FusingTraversalAnalyzer will fuse the rules together before applying traversal to increase performance. This lets the analyzer optimize for global information that isn't available inside a particular rule.

When declaring a rule, an analyzer type has to be attached to it so the framework will know how to actually apply the rule. This is managed by the val analyzer : AnalyzerGenerator field contained in rules. An AnalyzerGenerator is an object that knows how to instantiate an Analyzer given a Context and a set of rules (the rules that share this generator object).

Since the analyzer attached to a rule is statically determined by the analyzer field, we need a mechanism to inject new (and possibly more powerful) analyzers. For example, say we have the generator

object SomeAnalyzerGenerator extends AnalyzerGenerator {
  def generateAnalyzer(universe : SymbolTable, rules : List[Rule]) : Analyzer =
    new SomeAnalyzer(universe, rules)
}

and someone comes along with a new analyzer that should replace SomeAnalyzer, but doesn't want to change the rule definitions (where the analyzer field is specified) for some reason. It suffices to declare in the new generator that it subsumes SomeAnalyzerGenerator.

object SomeOtherAnalyzerGenerator extends AnalyzerGenerator {
  def generateAnalyzer(universe : SymbolTable, rules : List[Rule]) : Analyzer =
    new SomeOtherAnalyzer(universe, rules)
  val subsumes : Set[AnalyzerGenerator] = Set(SomeAnalyzerGenerator)
}

Any rule whose generator is transitively subsumed by another generator will be assigned to the analyzer generated by the later. This enables users to extend the framework with only analyzer plugins that will be automatically used to perform analysis by the compiler plugin.

In order to specify to the framework which generators are provided by a package, analyzer generator classes must be appended to the abide-plugin.xml descriptor in an <analyzer class="some.analyzer.generator.Class" /> element.

For a concrete example, see NaiveTraversalAnalyzer which is subsumed by FusingTraversalAnalyzer for traversal rules.

As of now, only the analyzer for unit-local, flow-agnostic rules has been written, but cross-unit and flow-sensitive backends should be added in the future (if deemed useful). However, many simple(r) rules do not actually need flow information and warnings can be collected by a single pass through a compilation unit's body. Such rules are called traversal rules in the abide lingo. See writing traversal rules and abide rules for more details.

Defining presenters

In order to output results, abide relies on Presenter instances that know how to actually process the output of abide. For example, the ConsolePresenter will output the results as compiler messages.

When declaring a presenter, a generator type has to be attached to it so the framework will know how to actually create the presenter. A PresenterGenerator is an object that knows how to instantiate an Presenter given all the needed context and for it to run.

object SomePresenterGenerator extends PresenterGenerator {
  def generatePresenter(global: Global) : Presenter =
    new SomePresenter(global)
}
class SomePresenter(protected val global: Global) extends Presenter {

  import global._

  def apply(unit: CompilationUnit, warnings: List[Warning]): Unit = {
    // TODO: implement the presenter
  }
}