Getting started#
Implementing a custom detector or printer is a very efficient way to extend Wake's detection and analysis capabilities.
Both detectors and printers may be implemented as project-specific or global.
Tip
Built-in detectors and printers may serve as a good starting point for implementing custom detectors and printers.
Using a template#
The best way to get started is to use
or
commands, which will create a template detector or printer in ./detectors
or ./printers
respectively.
By supplying the --global
flag, the template will be created in $XDG_DATA_HOME/wake/global-detectors
or $XDG_DATA_HOME/wake/global-printers
instead.
Tip
If working in VS Code with the Tools for Solidity extension installed, the same can be achieved by running the following commands in the command palette:
Tools for Solidity: New Detector
,Tools for Solidity: New Global Detector
,Tools for Solidity: New Printer
,Tools for Solidity: New Global Printer
.
Detector & printer structure#
Both template detectors and printers are implemented as a minimal Python class:
Detector | Printer |
---|---|
Detectors define the detect
method, which returns a list of detections. Printers define the print
method, which prints the results of the analysis.
Tip
Printers may print information from any method (even from __init__
and cli
), not just from print
.
On the contrary, printers do not need to print anything at all. For example, a printer may be used to generate a file with the results of the analysis.
Command-line interface#
Detector and Printer subclasses should implement a command-line interface (CLI) method using the Click library. The name of the Click command determines the name of the detector or printer. Both detectors and printers may accept additional arguments and options, for example:
@printer.command(name="my-printer")
@click.argument(
"modifier",
type=str,
required=True,
help="Name of the modifier to analyze.",
)
@click.option(
"--follow-function-calls/--no-follow-function-calls",
is_flag=True,
default=False,
help="Follow function calls in the modifier.",
)
See the Detector configuration and Printer configuration sections for how to set the values of arguments and options when running detectors and printers.
Default values for detectors
Detectors must always provide default values for all arguments and options.
This is because detectors may be run with wake detect all
, where passing detector-specific arguments and options is not possible.
The same is true for the LSP server, which runs detectors in the background.
Inherited attributes and methods#
Both Detector and Printer classes inherit from the Visitor class, which provides visit_
methods for all types of Solidity abstract syntax tree (AST) nodes.
Wake builds on the AST and provides an intermediate representation (IR) model, which is an extension of the AST with additional information and fixes for incorrect or missing information.
Refer to the wake.ir
API reference for more information.
The visit_
methods accept a single argument, which is the IR node to be visited, for example:
Visit functions are automatically called by the execution engine when running the detector or printer.
Additionally, there are two methods for generating links from an IR node or from a source code location:
def generate_link(self, node: ir.IrAbc) -> str:
...
def generate_link_from_line_col(self, path: Union[str, Path], line: int, col: int) -> str:
...
Example
The methods may be used in the following way:
def visit_function_definition(self, node: ir.FunctionDefinition) -> None:
link = f"[link={self.generate_link(node)}]{node.canonical_name}[/link]"
Refer to the Rich documentation for more information about the syntax of console links.
Visit modes#
All detectors and printers accept unlimited number of paths to Solidity source code files and directories. The paths are passed as command-line arguments, for example:
When there are any paths specified, the visit_
functions are called only for IR nodes in Solidity files in the specified paths.
However, some detectors and printers may need to visit all IR nodes in the project, to perform the analysis correctly.
In such cases, the detector or printer should override the visit_mode
property and return "all"
instead of the default "paths"
.
When the visit_mode
is set to "all"
, the detector or printer is responsible for filtering out the detections or printed information that are not relevant to the specified paths.
For example:
class MyPrinterPrinter(Printer):
...
@property
def visit_mode(self):
return "all"
def visit_contract_definition(self, node: ir.ContractDefinition) -> None:
from wake.utils import is_relative_to
if not any(is_relative_to(node.source_unit.file, p) for p in self.paths):
return
...
Execution order#
The methods of detectors and printers are executed in the following order:
__init__
,- Click command-line entry point (
cli
or any other method decorated with@detector.command()
or@printer.command()
), visit_mode
,visit_
methods in an unspecified order,detect
for detectors orprint
for printers.