Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ clap = { version = "4", features = ["derive"] }
syn-solidity = "0.3.0"
syn = { version = "2.0.26", features = ["full"] }
proc-macro2 = { version = "1.0.65", features = ["span-locations"]}
colored = "2.0.4"
9 changes: 6 additions & 3 deletions src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use clap::Parser;
use std::fs;
use syn_solidity::{File, Item};

use crate::scanners::{memory::Metadata, Registry};
use crate::scanners::{result::Reporter, Registry};

#[derive(Debug, Parser)]
pub struct Command {
Expand All @@ -19,13 +19,16 @@ impl Command {
let ast_root: File =
syn::parse_str(&code).with_context(|| "Failed to parse code into AST".to_string())?;
let ast_items: &[Item] = &ast_root.items;
let metadata = Metadata::new(file_path);
let scanners_registry = Registry::default();
let mut reporter = Reporter::default();

scanners_registry
.get_scanners()
.iter()
.for_each(|s| s.execute(ast_items, &metadata));
.for_each(|s| s.execute(ast_items, &mut reporter));

reporter.log(file_path);

Ok(())
}
}
117 changes: 73 additions & 44 deletions src/scanners/implementations/missing_comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,32 @@ use syn_solidity::{
Item, ItemContract, ItemEnum, ItemError, ItemEvent, ItemFunction, ItemStruct, ItemUdt,
};

use crate::scanners::{memory::Metadata, Scanner};
use crate::scanners::{
result::{Reporter, Severity},
Scanner,
};

/// A scanner responsible for looking at various Solidity items and reporting if documentation (comments) is missing.
#[derive(Default)]
pub struct MissingComments {}

impl MissingComments {
/// Scan every item in the provided contract object and looks for missing documentation.
fn scan_in_contract(&self, contract: &ItemContract, metadata: &Metadata) {
fn scan_in_contract(&self, contract: &ItemContract, reporter: &mut Reporter) {
for item in &contract.body {
match item {
Item::Enum(enumeration) => {
self.check_missing_comments_for_enum(enumeration, metadata)
self.check_missing_comments_for_enum(enumeration, reporter)
}
Item::Error(error) => self.check_missing_comments_for_error(error, metadata),
Item::Event(event) => self.check_missing_comments_for_event(event, metadata),
Item::Error(error) => self.check_missing_comments_for_error(error, reporter),
Item::Event(event) => self.check_missing_comments_for_event(event, reporter),
Item::Function(function) => {
self.check_missing_comments_for_function(function, metadata)
self.check_missing_comments_for_function(function, reporter)
}
Item::Struct(structure) => {
self.check_missing_comments_for_structure(structure, metadata)
self.check_missing_comments_for_structure(structure, reporter)
}
Item::Udt(udt) => self.check_missing_comments_for_udt(udt, metadata),
Item::Udt(udt) => self.check_missing_comments_for_udt(udt, reporter),
/* Contracts can not be declared inside other contracts */
/* Import directives don't have attributes. */
/* Pragma directives don't have attributes. */
Expand All @@ -44,14 +47,20 @@ impl MissingComments {
/// */
/// contract SimpleContract {}
/// ```
fn check_missing_comments_for_contract(&self, contract: &ItemContract, metadata: &Metadata) {
fn check_missing_comments_for_contract(
&self,
contract: &ItemContract,
reporter: &mut Reporter,
) {
if contract.attrs.is_empty() {
let line = contract.span().start().line;
let column = contract.span().start().column;

println!(
"{:}:{:}:{:} - Missing comment on contract definition",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Warning,
"Missing comment on contract definition",
)
}
}
Expand All @@ -68,14 +77,16 @@ impl MissingComments {
/// Agate,
/// }
/// ```
fn check_missing_comments_for_enum(&self, enumeration: &ItemEnum, metadata: &Metadata) {
fn check_missing_comments_for_enum(&self, enumeration: &ItemEnum, reporter: &mut Reporter) {
if enumeration.attrs.is_empty() {
let line = enumeration.span().start().line;
let column = enumeration.span().start().column;

println!(
"{:}:{:}:{:} - Missing comment on enum definition",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Warning,
"Missing comment on enum definition",
)
}
}
Expand All @@ -87,14 +98,16 @@ impl MissingComments {
/// /// @dev Emitted when a new Quartz has been mined!
/// event QuartzMined(QuartzType indexed variety);
/// ```
fn check_missing_comments_for_event(&self, event: &ItemEvent, metadata: &Metadata) {
fn check_missing_comments_for_event(&self, event: &ItemEvent, reporter: &mut Reporter) {
if event.attrs.is_empty() {
let line = event.span().start().line;
let column = event.span().start().column;

println!(
"{:}:{:}:{:} - Missing comment on event definition",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Warning,
"Missing comment on event definition",
)
}
}
Expand All @@ -106,14 +119,16 @@ impl MissingComments {
/// /// @dev Your favorite stone was broken :(
/// error BrokenQuartz();
/// ```
fn check_missing_comments_for_error(&self, error: &ItemError, metadata: &Metadata) {
fn check_missing_comments_for_error(&self, error: &ItemError, reporter: &mut Reporter) {
if error.attrs.is_empty() {
let line = error.span().start().line;
let column = error.span().start().column;

println!(
"{:}:{:}:{:} - Missing comment on error definition",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Warning,
"Missing comment on error definition",
)
}
}
Expand All @@ -127,14 +142,20 @@ impl MissingComments {
/// return true;
/// }
/// ```
fn check_missing_comments_for_function(&self, function: &ItemFunction, metadata: &Metadata) {
fn check_missing_comments_for_function(
&self,
function: &ItemFunction,
reporter: &mut Reporter,
) {
if function.attrs.is_empty() {
let line = function.span().start().line;
let column = function.span().start().column;

println!(
"{:}:{:}:{:} - Missing comment for function definition",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Warning,
"Missing comment for function definition",
)
}
}
Expand All @@ -148,14 +169,20 @@ impl MissingComments {
/// QuartzType variety;
/// }
/// ```
fn check_missing_comments_for_structure(&self, structure: &ItemStruct, metadata: &Metadata) {
fn check_missing_comments_for_structure(
&self,
structure: &ItemStruct,
reporter: &mut Reporter,
) {
if structure.attrs.is_empty() {
let line = structure.span().start().line;
let column = structure.span().start().column;

println!(
"{:}:{:}:{:} - Missing comment for structure definition",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Warning,
"Missing comment for structure definition",
)
}
}
Expand All @@ -167,39 +194,41 @@ impl MissingComments {
/// /// @dev Got this one from OpenZeppelin, I ran out of Quartz references.
/// type ShortString is bytes32;
/// ```
fn check_missing_comments_for_udt(&self, udt: &ItemUdt, metadata: &Metadata) {
fn check_missing_comments_for_udt(&self, udt: &ItemUdt, reporter: &mut Reporter) {
if udt.attrs.is_empty() {
let line = udt.span().start().line;
let column = udt.span().start().column;

println!(
"{:}:{:}:{:} - Missing comment for user-defined type definition",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Warning,
"Missing comment for user-defined type definition",
)
}
}
}

impl Scanner for MissingComments {
/// Scans every root item (recursively if there is a contract) and reports missing documentation.
fn execute(&self, ast: &[Item], metadata: &Metadata) {
fn execute(&self, ast: &[Item], reporter: &mut Reporter) {
for item in ast {
match item {
Item::Contract(contract) => {
self.check_missing_comments_for_contract(contract, metadata);
self.scan_in_contract(contract, metadata)
self.check_missing_comments_for_contract(contract, reporter);
self.scan_in_contract(contract, reporter)
}
Item::Enum(enumeration) => {
self.check_missing_comments_for_enum(enumeration, metadata)
self.check_missing_comments_for_enum(enumeration, reporter)
}
Item::Error(error) => self.check_missing_comments_for_error(error, metadata),
Item::Error(error) => self.check_missing_comments_for_error(error, reporter),
Item::Function(function) => {
self.check_missing_comments_for_function(function, metadata)
self.check_missing_comments_for_function(function, reporter)
}
Item::Struct(structure) => {
self.check_missing_comments_for_structure(structure, metadata)
self.check_missing_comments_for_structure(structure, reporter)
}
Item::Udt(udt) => self.check_missing_comments_for_udt(udt, metadata),
Item::Udt(udt) => self.check_missing_comments_for_udt(udt, reporter),
/* Events can not be declared at the root level. */
/* Import directives don't have attributes. */
/* Pragma directives don't have attributes. */
Expand Down
23 changes: 14 additions & 9 deletions src/scanners/implementations/mutable_functions.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
use crate::scanners::{memory::Metadata, Scanner};
use crate::scanners::{
result::{Reporter, Severity},
Scanner,
};
use syn_solidity::{FunctionAttribute, Item, ItemContract, ItemFunction, Mutability};

#[derive(Default)]
pub struct MutableFunctions {}

impl MutableFunctions {
/// Scans all functions from the contract while discarding the other items.
fn scan_contract(&self, contract: &ItemContract, metadata: &Metadata) {
fn scan_contract(&self, contract: &ItemContract, reporter: &mut Reporter) {
for item in &contract.body {
if let Item::Function(function) = item {
self.scan_function(function, metadata)
self.scan_function(function, reporter)
}
}
}

/// Reports if a function is able to mutate the contract state.
fn scan_function(&self, function: &ItemFunction, metadata: &Metadata) {
fn scan_function(&self, function: &ItemFunction, reporter: &mut Reporter) {
// TODO: There is probably a cleaner way to check this.
if function.kind.as_str() == "modifier" {
return;
Expand All @@ -36,20 +39,22 @@ impl MutableFunctions {
let line = function.span().start().line;
let column = function.span().start().column;

println!(
"{:}:{:}:{:} - Function can mutate contract state",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Info,
"Function can mutate contract state",
)
}
}

impl Scanner for MutableFunctions {
/// Scans every contract and reports functions able to mutate the storage state.
fn execute(&self, ast: &[Item], metadata: &Metadata) {
fn execute(&self, ast: &[Item], reporter: &mut Reporter) {
for item in ast {
// Mutable functions are only located inside contracts.
if let Item::Contract(contract) = item {
self.scan_contract(contract, metadata)
self.scan_contract(contract, reporter)
}
}
}
Expand Down
23 changes: 14 additions & 9 deletions src/scanners/implementations/mutable_variables.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
use crate::scanners::{memory::Metadata, Scanner};
use crate::scanners::{
result::{Reporter, Severity},
Scanner,
};
use syn_solidity::{Item, ItemContract, VariableAttribute, VariableDefinition};

#[derive(Default)]
pub struct MutableVariables {}

impl MutableVariables {
/// Scans all variables from the contract while discarding the other items.
fn scan_contract(&self, contract: &ItemContract, metadata: &Metadata) {
fn scan_contract(&self, contract: &ItemContract, reporter: &mut Reporter) {
for item in &contract.body {
if let Item::Variable(variable) = item {
self.scan_variable(variable, metadata)
self.scan_variable(variable, reporter)
}
}
}

/// Reports if a variable is likely to mutate.
fn scan_variable(&self, variable: &VariableDefinition, metadata: &Metadata) {
fn scan_variable(&self, variable: &VariableDefinition, reporter: &mut Reporter) {
let immutable_variable_attributes = [
&VariableAttribute::Constant(Default::default()),
&VariableAttribute::Immutable(Default::default()),
Expand All @@ -30,19 +33,21 @@ impl MutableVariables {
let line = variable.span().start().line;
let column = variable.span().start().column;

println!(
"{:}:{:}:{:} - Variable state can be changed",
metadata.file_path, line, column
reporter.report(
line,
column,
Severity::Info,
"Variable state can be changed",
)
}
}

impl Scanner for MutableVariables {
/// Scans every contract and reports variables able to mutate.
fn execute(&self, ast: &[Item], metadata: &Metadata) {
fn execute(&self, ast: &[Item], reporter: &mut Reporter) {
for item in ast {
if let Item::Contract(contract) = item {
self.scan_contract(contract, metadata)
self.scan_contract(contract, reporter)
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/scanners/implementations/mutation_grapher.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use syn_solidity::Item;

use crate::scanners::{memory::Metadata, Scanner};
use crate::scanners::{result::Reporter, Scanner};

#[derive(Default)]
pub struct MutationGrapher {}

impl MutationGrapher {}

impl Scanner for MutationGrapher {
fn execute(&self, _ast: &[Item], _metadata: &Metadata) {
fn execute(&self, _ast: &[Item], _reporter: &mut Reporter) {
/*
println!("todo!")
*/
}
}
Loading