diff --git a/README.md b/README.md
index dd872fe7..903707e5 100644
--- a/README.md
+++ b/README.md
@@ -103,13 +103,19 @@ Join us for an exciting series of video tutorials where you'll learn how to inst
- [Introduction to Scout](https://www.youtube.com/watch?v=L4kGwPDuWgA)
- [Installing Scout](https://www.youtube.com/watch?v=lStQxKQ_l2Q&t=1s)
- [How to run Scout](https://www.youtube.com/watch?v=_6F24AwscKc)
-- [Detecting and fixing issues: Divide before multiply](https://www.youtube.com/watch?v=aLtXyYvw27o)
-- [Detecting and fixing issues: Incorrect exponentiation](https://www.youtube.com/watch?v=qjnHwKCD_hM)
-- [Detecting and fixing issues: Overflow check](https://www.youtube.com/watch?v=Mi7AcJRPgvU)
-- [Detecting and fixing issues: Insufficiently random values](https://www.youtube.com/watch?v=LPBMDPXmczQ)
-- [Detecting and fixing issues: DoS - Unexpected revert with vector](https://www.youtube.com/watch?v=H79mMnnWyvA)
-- [Detecting and fixing issues: DoS - Unbounded operation](https://www.youtube.com/watch?v=DFM0yNNDiyw)
-- [Detecting and fixing issues: Set contract storage](https://www.youtube.com/watch?v=z6RNfhQt6EI)
+- [Learning to Scout Soroban: Divide before multiply](https://www.youtube.com/watch?v=aLtXyYvw27o)
+- [Learning to Scout Soroban: Incorrect exponentiation](https://www.youtube.com/watch?v=qjnHwKCD_hM)
+- [Learning to Scout Soroban: Overflow check](https://www.youtube.com/watch?v=Mi7AcJRPgvU)
+- [Learning to Scout Soroban: Insufficiently random values](https://www.youtube.com/watch?v=LPBMDPXmczQ)
+- [Learning to Scout Soroban: DoS - Unexpected revert with vector](https://www.youtube.com/watch?v=H79mMnnWyvA)
+- [Learning to Scout Soroban: DoS - Unbounded operation](https://www.youtube.com/watch?v=DFM0yNNDiyw)
+- [Learning to Scout Soroban: Set contract storage](https://www.youtube.com/watch?v=z6RNfhQt6EI)
+- [Learning to Scout Soroban: Unprotected mapping operation](https://www.youtube.com/watch?v=8yayEpKeles)
+- [Learning to Scout Soroban: Unprotected update current contract wasm](https://www.youtube.com/watch?v=05WnTt4gw5o)
+- [Learning to Scout Soroban: Unrestricted transfer from](https://www.youtube.com/watch?v=jnorbpq3ZXk)
+- [Learning to Scout Soroban: Assert violation](https://www.youtube.com/watch?v=-8iv4qXjx-M)
+- [Learning to Scout Soroban: Iterators over indexing](https://www.youtube.com/watch?v=PN7sD-W0_Qg)
+- [Learning to Scout Soroban: Unsafe expect](https://www.youtube.com/watch?v=sheqaOBOBfo)
:clapper: More videos comming soon!
@@ -139,6 +145,7 @@ Follow our documentation links below and learn more about the vulnerabilities de
- [Scout GitHub Action](https://coinfabrik.github.io/scout-soroban/docs/github-action)
- [Scout VS Code Extension](https://coinfabrik.github.io/scout-soroban/docs/vscode-extension)
- [Scout Soroban Examples](https://coinfabrik.github.io/scout-soroban/docs/soroban-examples)
+- [Toggle detections on and off](https://coinfabrik.github.io/scout-soroban/docs/toggle-detections-on-off)
## Acknowledgements
diff --git a/detectors/avoid-panic-error/src/lib.rs b/detectors/avoid-panic-error/src/lib.rs
index 72895519..f8fcaeb8 100644
--- a/detectors/avoid-panic-error/src/lib.rs
+++ b/detectors/avoid-panic-error/src/lib.rs
@@ -3,8 +3,9 @@
extern crate rustc_ast;
extern crate rustc_span;
-use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_wrappers::span_lint_and_help;
use rustc_ast::{
+ ptr::P,
tokenstream::TokenTree,
visit::{walk_expr, Visitor},
AssocItemKind, AttrArgs, AttrKind, Block, Expr, ExprKind, FnRetTy, Item, ItemKind, MacCall,
@@ -93,16 +94,12 @@ impl EarlyLintPass for AvoidPanicError {
ItemKind::Impl(impl_item) => {
for assoc_item in &impl_item.items {
if let AssocItemKind::Fn(fn_item) = &assoc_item.kind {
- self.check_function(
- cx,
- &fn_item.sig.decl.output,
- fn_item.body.as_ref().unwrap(),
- );
+ self.check_function(cx, &fn_item.sig.decl.output, &fn_item.body);
}
}
}
ItemKind::Fn(fn_item) => {
- self.check_function(cx, &fn_item.sig.decl.output, fn_item.body.as_ref().unwrap());
+ self.check_function(cx, &fn_item.sig.decl.output, &fn_item.body);
}
ItemKind::Mod(_, ModKind::Loaded(items, _, _)) => {
for item in items {
@@ -112,11 +109,7 @@ impl EarlyLintPass for AvoidPanicError {
ItemKind::Trait(trait_item) => {
for item in &trait_item.items {
if let AssocItemKind::Fn(fn_item) = &item.kind {
- self.check_function(
- cx,
- &fn_item.sig.decl.output,
- fn_item.body.as_ref().unwrap(),
- );
+ self.check_function(cx, &fn_item.sig.decl.output, &fn_item.body);
}
}
}
@@ -126,10 +119,12 @@ impl EarlyLintPass for AvoidPanicError {
}
impl AvoidPanicError {
- fn check_function(&self, cx: &EarlyContext, output: &FnRetTy, body: &Block) {
- if is_result_type(output) {
- let mut visitor = PanicVisitor { cx };
- visitor.visit_block(body);
+ fn check_function(&self, cx: &EarlyContext, output: &FnRetTy, body: &Option
>) {
+ if let Some(body) = body {
+ if is_result_type(output) {
+ let mut visitor = PanicVisitor { cx };
+ visitor.visit_block(body);
+ }
}
}
}
diff --git a/detectors/dynamic-storage/Cargo.toml b/detectors/dynamic-storage/Cargo.toml
index c564b4ff..0860b324 100644
--- a/detectors/dynamic-storage/Cargo.toml
+++ b/detectors/dynamic-storage/Cargo.toml
@@ -7,7 +7,7 @@ version = "0.1.0"
crate-type = ["cdylib"]
[dependencies]
-clippy_utils = { workspace = true }
+clippy_wrappers = { workspace = true }
dylint_linting = { workspace = true }
if_chain = { workspace = true }
utils = { workspace = true }
diff --git a/detectors/dynamic-storage/src/lib.rs b/detectors/dynamic-storage/src/lib.rs
index 151a0c52..c33f4645 100644
--- a/detectors/dynamic-storage/src/lib.rs
+++ b/detectors/dynamic-storage/src/lib.rs
@@ -4,7 +4,7 @@ extern crate rustc_hir;
extern crate rustc_middle;
extern crate rustc_span;
-use clippy_utils::diagnostics::span_lint;
+use clippy_wrappers::span_lint;
use if_chain::if_chain;
use rustc_hir::{
intravisit::{walk_expr, FnKind, Visitor},
diff --git a/detectors/integer-overflow-or-underflow/Cargo.toml b/detectors/integer-overflow-or-underflow/Cargo.toml
index 15f1a7fd..f584b02b 100644
--- a/detectors/integer-overflow-or-underflow/Cargo.toml
+++ b/detectors/integer-overflow-or-underflow/Cargo.toml
@@ -7,7 +7,7 @@ version = "0.1.0"
crate-type = ["cdylib"]
[dependencies]
-clippy_utils = { workspace = true }
+clippy_wrappers = { workspace = true }
dylint_linting = { workspace = true }
if_chain = { workspace = true }
utils = { workspace = true }
diff --git a/detectors/integer-overflow-or-underflow/src/lib.rs b/detectors/integer-overflow-or-underflow/src/lib.rs
index 031282ca..130b844a 100644
--- a/detectors/integer-overflow-or-underflow/src/lib.rs
+++ b/detectors/integer-overflow-or-underflow/src/lib.rs
@@ -3,7 +3,7 @@
extern crate rustc_hir;
extern crate rustc_span;
-use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_wrappers::span_lint_and_help;
use rustc_hir::{
intravisit::{walk_expr, FnKind, Visitor},
BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp,
diff --git a/detectors/iterators-over-indexing/src/lib.rs b/detectors/iterators-over-indexing/src/lib.rs
index 2126784d..4f39260e 100644
--- a/detectors/iterators-over-indexing/src/lib.rs
+++ b/detectors/iterators-over-indexing/src/lib.rs
@@ -20,6 +20,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{TyCtxt, TyKind};
use rustc_span::{symbol::Ident, Span};
use rustc_type_ir::Interner;
+use std::collections::HashSet;
use utils::get_node_type;
const LINT_MESSAGE: &str =
@@ -39,7 +40,7 @@ dylint_linting::declare_late_lint! {
}
struct ForLoopVisitor<'a, 'b> {
- span_constant: Vec,
+ span_constant: HashSet,
cx: &'b LateContext<'a>,
}
struct VectorAccessVisitor<'a, 'b> {
@@ -351,7 +352,7 @@ fn handle_expr<'a>(me: &mut ForLoopVisitor<'a, '_>, expr: &'a Expr<'a>) -> Resul
}
if visitor.has_vector_access {
- me.span_constant.push(expr.span);
+ me.span_constant.insert(expr.span);
}
Ok(())
@@ -377,7 +378,7 @@ impl<'tcx> LateLintPass<'tcx> for IteratorOverIndexing {
if let FnKind::Method(_ident, _sig) = kind {
let span_constant = {
let mut visitor = ForLoopVisitor {
- span_constant: vec![],
+ span_constant: HashSet::new(),
cx,
};
walk_expr(&mut visitor, body.value);
diff --git a/detectors/storage-change-events/Cargo.toml b/detectors/storage-change-events/Cargo.toml
index 97569970..80eac245 100644
--- a/detectors/storage-change-events/Cargo.toml
+++ b/detectors/storage-change-events/Cargo.toml
@@ -7,7 +7,7 @@ version = "0.1.0"
crate-type = ["cdylib"]
[dependencies]
-clippy_utils = { workspace = true }
+clippy_wrappers = { workspace = true }
dylint_linting = { workspace = true }
if_chain = { workspace = true }
utils = { workspace = true }
diff --git a/detectors/storage-change-events/src/lib.rs b/detectors/storage-change-events/src/lib.rs
index b119ff21..1c45369f 100644
--- a/detectors/storage-change-events/src/lib.rs
+++ b/detectors/storage-change-events/src/lib.rs
@@ -1,26 +1,21 @@
#![feature(rustc_private)]
extern crate rustc_hir;
-extern crate rustc_middle;
extern crate rustc_span;
-use clippy_utils::diagnostics::span_lint_and_help;
-
+use clippy_wrappers::span_lint_and_help;
use rustc_hir::{
intravisit::{walk_expr, Visitor},
Expr, ExprKind,
};
use rustc_lint::{LateContext, LateLintPass};
-
-use rustc_span::Span;
-
-use std::collections::HashMap;
-use std::collections::HashSet;
-use std::vec;
+use rustc_span::{def_id::DefId, Span};
+use std::{
+ collections::{HashMap, HashSet},
+ vec,
+};
use utils::{is_soroban_function, FunctionCallVisitor};
-use rustc_span::def_id::DefId;
-
const LINT_MESSAGE: &str = "Consider emiting an event when storage is modified";
dylint_linting::impl_late_lint! {
diff --git a/detectors/token-interface-events/Cargo.toml b/detectors/token-interface-events/Cargo.toml
index 644b6b4d..5fc56514 100644
--- a/detectors/token-interface-events/Cargo.toml
+++ b/detectors/token-interface-events/Cargo.toml
@@ -7,7 +7,7 @@ version = "0.1.0"
crate-type = ["cdylib"]
[dependencies]
-clippy_utils = { workspace = true }
+clippy_wrappers = { workspace = true }
dylint_linting = { workspace = true }
if_chain = { workspace = true }
utils = { workspace = true }
diff --git a/detectors/token-interface-events/src/lib.rs b/detectors/token-interface-events/src/lib.rs
index 69e3d264..1963452b 100644
--- a/detectors/token-interface-events/src/lib.rs
+++ b/detectors/token-interface-events/src/lib.rs
@@ -1,26 +1,21 @@
#![feature(rustc_private)]
extern crate rustc_hir;
-extern crate rustc_middle;
extern crate rustc_span;
-use clippy_utils::diagnostics::span_lint_and_help;
-
+use clippy_wrappers::span_lint_and_help;
use rustc_hir::{
intravisit::{walk_expr, Visitor},
Expr, ExprKind,
};
use rustc_lint::{LateContext, LateLintPass};
-
-use rustc_span::Span;
-
-use std::collections::HashMap;
-use std::collections::HashSet;
-use std::vec;
+use rustc_span::{def_id::DefId, Span};
+use std::{
+ collections::{HashMap, HashSet},
+ vec,
+};
use utils::{verify_token_interface_function, FunctionCallVisitor};
-use rustc_span::def_id::DefId;
-
const LINT_MESSAGE: &str = "This function belongs to the Token Interface and should emit an event";
dylint_linting::impl_late_lint! {
diff --git a/detectors/unsafe-expect/Cargo.toml b/detectors/unsafe-expect/Cargo.toml
index f451b997..24bde4b3 100644
--- a/detectors/unsafe-expect/Cargo.toml
+++ b/detectors/unsafe-expect/Cargo.toml
@@ -11,6 +11,7 @@ clippy_utils = { workspace = true }
clippy_wrappers = { workspace = true }
dylint_linting = { workspace = true }
if_chain = { workspace = true }
+utils = { workspace = true }
[package.metadata.rust-analyzer]
rustc_private = true
diff --git a/detectors/unsafe-expect/src/lib.rs b/detectors/unsafe-expect/src/lib.rs
index 42cd146f..4f3cb283 100644
--- a/detectors/unsafe-expect/src/lib.rs
+++ b/detectors/unsafe-expect/src/lib.rs
@@ -4,9 +4,7 @@
extern crate rustc_hir;
extern crate rustc_span;
-use std::{collections::HashSet, hash::Hash};
-
-use clippy_utils::higher;
+use clippy_utils::higher::IfOrIfLet;
use clippy_wrappers::span_lint_and_help;
use if_chain::if_chain;
use rustc_hir::{
@@ -17,6 +15,8 @@ use rustc_hir::{
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_span::{sym, Span, Symbol};
+use std::{collections::HashSet, hash::Hash};
+use utils::fn_returns;
const LINT_MESSAGE: &str = "Unsafe usage of `expect`";
const PANIC_INDUCING_FUNCTIONS: [&str; 2] = ["panic", "bail"];
@@ -149,6 +149,7 @@ struct UnsafeExpectVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
conditional_checker: HashSet,
checked_exprs: HashSet,
+ linted_spans: HashSet,
}
impl UnsafeExpectVisitor<'_, '_> {
@@ -174,21 +175,23 @@ impl UnsafeExpectVisitor<'_, '_> {
None
}
- fn set_conditional_checker(&mut self, conditional_checkers: &HashSet) {
- for checker in conditional_checkers {
- self.conditional_checker.insert(*checker);
- if checker.check_type.is_safe_to_expect() {
- self.checked_exprs.insert(checker.checked_expr_hir_id);
- }
- }
- }
-
- fn reset_conditional_checker(&mut self, conditional_checkers: HashSet) {
+ fn update_conditional_checker(
+ &mut self,
+ conditional_checkers: &HashSet,
+ set: bool,
+ ) {
for checker in conditional_checkers {
- if checker.check_type.is_safe_to_expect() {
- self.checked_exprs.remove(&checker.checked_expr_hir_id);
+ if set {
+ self.conditional_checker.insert(*checker);
+ if checker.check_type.is_safe_to_expect() {
+ self.checked_exprs.insert(checker.checked_expr_hir_id);
+ }
+ } else {
+ if checker.check_type.is_safe_to_expect() {
+ self.checked_exprs.remove(&checker.checked_expr_hir_id);
+ }
+ self.conditional_checker.remove(checker);
}
- self.conditional_checker.remove(&checker);
}
}
@@ -236,6 +239,7 @@ impl UnsafeExpectVisitor<'_, '_> {
.get_expect_info(receiver)
.map_or(true, |id| !self.checked_exprs.contains(&id));
}
+
args.iter().any(|arg| self.contains_unsafe_method_call(arg))
|| self.contains_unsafe_method_call(receiver)
}
@@ -252,7 +256,9 @@ impl UnsafeExpectVisitor<'_, '_> {
fn check_expr_for_unsafe_expect(&mut self, expr: &Expr) {
match &expr.kind {
ExprKind::MethodCall(path_segment, receiver, args, _) => {
- if self.is_method_call_unsafe(path_segment, receiver, args) {
+ if self.is_method_call_unsafe(path_segment, receiver, args)
+ && !self.linted_spans.contains(&expr.span)
+ {
span_lint_and_help(
self.cx,
UNSAFE_EXPECT,
@@ -261,6 +267,7 @@ impl UnsafeExpectVisitor<'_, '_> {
None,
"Please, use a custom error instead of `expect`",
);
+ self.linted_spans.insert(expr.span);
}
}
ExprKind::Call(func, args) => {
@@ -312,17 +319,17 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafeExpectVisitor<'a, 'tcx> {
}
// Find `if` or `if let` expressions
- if let Some(higher::IfOrIfLet {
+ if let Some(IfOrIfLet {
cond,
then: if_expr,
r#else: _,
- }) = higher::IfOrIfLet::hir(expr)
+ }) = IfOrIfLet::hir(expr)
{
// If we are interested in the condition (if it is a CheckType) we traverse the body.
let conditional_checker = ConditionalChecker::from_expression(cond);
- self.set_conditional_checker(&conditional_checker);
+ self.update_conditional_checker(&conditional_checker, true);
walk_expr(self, if_expr);
- self.reset_conditional_checker(conditional_checker);
+ self.update_conditional_checker(&conditional_checker, false);
return;
}
@@ -338,13 +345,15 @@ impl<'tcx> LateLintPass<'tcx> for UnsafeExpect {
&mut self,
cx: &LateContext<'tcx>,
_: FnKind<'tcx>,
- _: &'tcx FnDecl<'tcx>,
+ fn_decl: &'tcx FnDecl<'tcx>,
body: &'tcx Body<'tcx>,
span: Span,
_: LocalDefId,
) {
- // If the function comes from a macro expansion, we don't want to analyze it.
- if span.from_expansion() {
+ // If the function comes from a macro expansion or does not return a Result<(), ()> or Option<()>, we don't want to analyze it.
+ if span.from_expansion()
+ || !fn_returns(fn_decl, sym::Result) && !fn_returns(fn_decl, sym::Option)
+ {
return;
}
@@ -352,6 +361,7 @@ impl<'tcx> LateLintPass<'tcx> for UnsafeExpect {
cx,
checked_exprs: HashSet::new(),
conditional_checker: HashSet::new(),
+ linted_spans: HashSet::new(),
};
walk_expr(&mut visitor, body.value);
diff --git a/detectors/unsafe-unwrap/Cargo.toml b/detectors/unsafe-unwrap/Cargo.toml
index cb8e056c..4812ff59 100644
--- a/detectors/unsafe-unwrap/Cargo.toml
+++ b/detectors/unsafe-unwrap/Cargo.toml
@@ -11,6 +11,7 @@ clippy_utils = { workspace = true }
clippy_wrappers = { workspace = true }
dylint_linting = { workspace = true }
if_chain = { workspace = true }
+utils = { workspace = true }
[package.metadata.rust-analyzer]
rustc_private = true
diff --git a/detectors/unsafe-unwrap/src/lib.rs b/detectors/unsafe-unwrap/src/lib.rs
index 6f2026d4..63b225ec 100644
--- a/detectors/unsafe-unwrap/src/lib.rs
+++ b/detectors/unsafe-unwrap/src/lib.rs
@@ -1,23 +1,22 @@
#![feature(rustc_private)]
#![allow(clippy::enum_variant_names)]
-extern crate rustc_ast;
extern crate rustc_hir;
extern crate rustc_span;
-use std::{collections::HashSet, hash::Hash};
-
-use clippy_utils::higher;
+use clippy_utils::higher::IfOrIfLet;
use clippy_wrappers::span_lint_and_help;
use if_chain::if_chain;
use rustc_hir::{
def::Res,
def_id::LocalDefId,
intravisit::{walk_expr, FnKind, Visitor},
- BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, PathSegment, QPath, UnOp,
+ BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, LetStmt, PathSegment, QPath, UnOp,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_span::{sym, Span, Symbol};
+use std::{collections::HashSet, hash::Hash};
+use utils::{fn_returns, get_node_type_opt, match_type_to_str};
const LINT_MESSAGE: &str = "Unsafe usage of `unwrap`";
const PANIC_INDUCING_FUNCTIONS: [&str; 2] = ["panic", "bail"];
@@ -65,6 +64,13 @@ dylint_linting::declare_late_lint! {
}
}
+// Enum to represent the type being unwrapped
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum UnwrapType {
+ Option,
+ Result,
+}
+
/// Represents the type of check performed on method call expressions to determine their safety or behavior.
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
enum CheckType {
@@ -132,7 +138,7 @@ impl ConditionalChecker {
fn from_expression(condition: &Expr<'_>) -> HashSet {
match condition.kind {
// Single `not` expressions are supported
- ExprKind::Unary(op, condition) => Self::handle_condition(condition, op == UnOp::Not),
+ ExprKind::Unary(UnOp::Not, condition) => Self::handle_condition(condition, true),
// Multiple `or` expressions are supported
ExprKind::Binary(op, left_condition, right_condition) if op.node == BinOpKind::Or => {
let mut result = Self::from_expression(left_condition);
@@ -150,9 +156,33 @@ struct UnsafeUnwrapVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
conditional_checker: HashSet,
checked_exprs: HashSet,
+ returns_result_or_option: bool,
}
impl UnsafeUnwrapVisitor<'_, '_> {
+ fn get_help_message(&self, unwrap_type: UnwrapType) -> &'static str {
+ match (self.returns_result_or_option, unwrap_type) {
+ (true, UnwrapType::Option) => "Consider using `ok_or` to convert Option to Result",
+ (true, UnwrapType::Result) => "Consider using the `?` operator for error propagation",
+ (false, UnwrapType::Option) => {
+ "Consider pattern matching or using `if let` instead of `unwrap`"
+ }
+ (false, UnwrapType::Result) => {
+ "Consider handling the error case explicitly or using `if let` instead of `unwrap`"
+ }
+ }
+ }
+
+ fn determine_unwrap_type(&self, receiver: &Expr<'_>) -> UnwrapType {
+ let type_opt = get_node_type_opt(self.cx, &receiver.hir_id);
+ if let Some(type_) = type_opt {
+ if match_type_to_str(self.cx, type_, "Result") {
+ return UnwrapType::Result;
+ }
+ }
+ UnwrapType::Option
+ }
+
fn is_panic_inducing_call(&self, func: &Expr<'_>) -> bool {
if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind {
return PANIC_INDUCING_FUNCTIONS.iter().any(|&func| {
@@ -175,21 +205,23 @@ impl UnsafeUnwrapVisitor<'_, '_> {
None
}
- fn set_conditional_checker(&mut self, conditional_checkers: &HashSet) {
- for checker in conditional_checkers {
- self.conditional_checker.insert(*checker);
- if checker.check_type.is_safe_to_unwrap() {
- self.checked_exprs.insert(checker.checked_expr_hir_id);
- }
- }
- }
-
- fn reset_conditional_checker(&mut self, conditional_checkers: HashSet) {
+ fn update_conditional_checker(
+ &mut self,
+ conditional_checkers: &HashSet,
+ set: bool,
+ ) {
for checker in conditional_checkers {
- if checker.check_type.is_safe_to_unwrap() {
- self.checked_exprs.remove(&checker.checked_expr_hir_id);
+ if set {
+ self.conditional_checker.insert(*checker);
+ if checker.check_type.is_safe_to_unwrap() {
+ self.checked_exprs.insert(checker.checked_expr_hir_id);
+ }
+ } else {
+ if checker.check_type.is_safe_to_unwrap() {
+ self.checked_exprs.remove(&checker.checked_expr_hir_id);
+ }
+ self.conditional_checker.remove(checker);
}
- self.conditional_checker.remove(&checker);
}
}
@@ -253,18 +285,20 @@ impl UnsafeUnwrapVisitor<'_, '_> {
}
impl<'a, 'tcx> Visitor<'tcx> for UnsafeUnwrapVisitor<'a, 'tcx> {
- fn visit_local(&mut self, local: &'tcx rustc_hir::LetStmt<'tcx>) -> Self::Result {
+ fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) -> Self::Result {
if let Some(init) = local.init {
match init.kind {
ExprKind::MethodCall(path_segment, receiver, args, _) => {
if self.is_method_call_unsafe(path_segment, receiver, args) {
+ let unwrap_type = self.determine_unwrap_type(receiver);
+ let help_message = self.get_help_message(unwrap_type);
span_lint_and_help(
self.cx,
UNSAFE_UNWRAP,
local.span,
LINT_MESSAGE,
None,
- "Please, use a custom error instead of `unwrap`",
+ help_message,
);
}
}
@@ -300,17 +334,17 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafeUnwrapVisitor<'a, 'tcx> {
}
// Find `if` or `if let` expressions
- if let Some(higher::IfOrIfLet {
+ if let Some(IfOrIfLet {
cond,
then: if_expr,
r#else: _,
- }) = higher::IfOrIfLet::hir(expr)
+ }) = IfOrIfLet::hir(expr)
{
// If we are interested in the condition (if it is a CheckType) we traverse the body.
let conditional_checker = ConditionalChecker::from_expression(cond);
- self.set_conditional_checker(&conditional_checker);
+ self.update_conditional_checker(&conditional_checker, true);
walk_expr(self, if_expr);
- self.reset_conditional_checker(conditional_checker);
+ self.update_conditional_checker(&conditional_checker, false);
return;
}
@@ -320,16 +354,18 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafeUnwrapVisitor<'a, 'tcx> {
if path_segment.ident.name == sym::unwrap;
then {
let receiver_hir_id = self.get_unwrap_info(receiver);
- // If the receiver is `None`, then we asume that the `unwrap` is unsafe
+ // If the receiver is `None` or `Err`, then we assume that the `unwrap` is unsafe
let is_checked_safe = receiver_hir_id.map_or(false, |id| self.checked_exprs.contains(&id));
if !is_checked_safe {
+ let unwrap_type = self.determine_unwrap_type(receiver);
+ let help_message = self.get_help_message(unwrap_type);
span_lint_and_help(
self.cx,
UNSAFE_UNWRAP,
expr.span,
LINT_MESSAGE,
None,
- "Please, use a custom error instead of `unwrap`",
+ help_message,
);
}
}
@@ -344,7 +380,7 @@ impl<'tcx> LateLintPass<'tcx> for UnsafeUnwrap {
&mut self,
cx: &LateContext<'tcx>,
_: FnKind<'tcx>,
- _: &'tcx FnDecl<'tcx>,
+ fn_decl: &'tcx FnDecl<'tcx>,
body: &'tcx Body<'tcx>,
span: Span,
_: LocalDefId,
@@ -358,6 +394,8 @@ impl<'tcx> LateLintPass<'tcx> for UnsafeUnwrap {
cx,
checked_exprs: HashSet::new(),
conditional_checker: HashSet::new(),
+ returns_result_or_option: fn_returns(fn_decl, sym::Result)
+ || fn_returns(fn_decl, sym::Option),
};
walk_expr(&mut visitor, body.value);
diff --git a/docs/docs/toggle-detections-on-off.md b/docs/docs/toggle-detections-on-off.md
new file mode 100644
index 00000000..dcb66f80
--- /dev/null
+++ b/docs/docs/toggle-detections-on-off.md
@@ -0,0 +1,64 @@
+---
+sidebar_position: 10
+---
+
+# Toggle detections on and off
+
+In addition to enabling and disabling detectors, Scout allows users to toggle individual detections on or off. This feature is useful for disabling detections that are false positives or not relevant to the analyzed codebase.
+
+## Usage
+
+### 1) Import scout-utils package
+
+To use the toggle detections on/off feature, you’ll need to import the `scout-utils` package into your project, adding the following line to your `Cargo.toml`.
+
+```rust
+scout-utils = "0.1.0"
+```
+
+### 2) Include scout-utils in your Rust file
+
+Include the scout-utils package in the Rust file in which you want to disable detections, adding the following line:
+
+```rust
+use scout-audit::scout_allow;
+```
+
+### 3) Use scout_allow macro to disable a detection
+
+To disable a detection, you’ll need to use the scout_allow macro, with the name of the detection to disable as an attribute. For example:
+
+```rust
+#[scout_allow(unsafe_unwrap)]
+```
+
+Place the macro before the block of code in which you want to disable a detection. For example:
+
+```rust
+#[scout_allow(unsafe_expect)]
+pub fn my_func() {
+let x: Option<&str> = None;
+x.expect("Something went wrong!");
+}
+```
+
+The macro supports including more than one attribute to disable multiple detections at once. For example:
+
+```rust
+#[scout_allow(unsafe_unwrap, integer_overflow_or_underflow)]
+```
+
+## Supported scope
+
+`scout_allow` macro supports disabling detections for the following scopes:
+
+- Functions (entire body)
+- Modules
+- Structs
+- Enums
+- Traits
+- Impl blocks
+
+## Unnecesary scout_allow macro detector
+
+If Scout Audit detects a scout_allow macro for a block of code in which the disallowed detection is not triggered, it will raise a warning.
diff --git a/docs/docs/vscode-extension.md b/docs/docs/vscode-extension.md
index 45048547..1ba4d8c7 100644
--- a/docs/docs/vscode-extension.md
+++ b/docs/docs/vscode-extension.md
@@ -13,4 +13,3 @@ Add Scout to your development workspace with Scout's VS Code extension and run S
:bulb: Tip: To see the errors highlighted in your code, we recommend installing the [Error Lens Extension](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens).
:point_right: Download Scout VS Code from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=CoinFabrik.scout-audit).
-
diff --git a/scripts/run-tests.py b/scripts/run-tests.py
index 1b2e3f3e..300aea2f 100644
--- a/scripts/run-tests.py
+++ b/scripts/run-tests.py
@@ -63,6 +63,7 @@ def run_integration_tests(detector, root):
root,
)
+ #print("stderr: ", stderr.read())
if stdout is None:
print(
f"{RED}Failed to run integration tests in {root} - Metadata returned empty.{ENDC}"
@@ -78,7 +79,7 @@ def run_integration_tests(detector, root):
detector_key = detector.replace("-", "_")
short_message = detector_metadata.get(detector_key, {}).get("short_message")
- returncode, _, stderr = run_subprocess(
+ returncode, stdout, stderr = run_subprocess(
[
"cargo",
"scout-audit",
@@ -91,12 +92,12 @@ def run_integration_tests(detector, root):
)
should_lint = root.endswith("vulnerable-example")
- if should_lint and short_message and short_message not in stderr:
+ if should_lint and short_message and short_message not in stdout:
returncode = 1
print_results(
returncode,
- stderr,
+ stdout,
"integration-test",
root,
time.time() - start_time,