Skip to content

Commit

Permalink
Identify MustUse values passed to await, Asio\join (#16)
Browse files Browse the repository at this point in the history
* Update README

* Add test for unused with Asio\join, await

* Detect more unused return values

* Mark unused
  • Loading branch information
ryangreenberg authored Apr 8, 2024
1 parent 1ed5e03 commit 89af83a
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 17 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ That will create a binary at `./target/release/hakana-default`

## Running tests

You can run all tests with: `cargo run --release test tests`
You can run all tests with: `cargo run --bin hakana --release test tests`

You can run an individual test with `cargo run test <path-to-test-dir>`
You can run an individual test with `cargo run --bin hakana test <path-to-test-dir>`
71 changes: 56 additions & 15 deletions src/analyzer/stmt_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,20 @@ fn detect_unused_statement_expressions(
stmt: &aast::Stmt<(), ()>,
context: &mut ScopeContext,
) {
if has_unused_must_use(&boxed, &statements_analyzer, &stmt, context) {
analysis_data.maybe_add_issue(
Issue::new(
IssueKind::UnusedFunctionCall,
"This function is annotated with MustUse but the returned value is not used"
.to_string(),
statements_analyzer.get_hpos(&stmt.0),
&context.function_context.calling_functionlike_id,
),
statements_analyzer.get_config(),
statements_analyzer.get_file_path_actual(),
);
}

let functionlike_id = if let aast::Expr_::Call(boxed_call) = &boxed.2 {
get_functionlike_id_from_call(
boxed_call,
Expand Down Expand Up @@ -331,27 +345,14 @@ fn detect_unused_statement_expressions(
}
}
}

if let Some(functionlike_id) = functionlike_id {
if let FunctionLikeIdentifier::Function(function_id) = functionlike_id {
let codebase = statements_analyzer.get_codebase();
if let Some(functionlike_info) = codebase
if let Some(_functionlike_info) = codebase
.functionlike_infos
.get(&(function_id, StrId::EMPTY))
{
if functionlike_info.must_use {
analysis_data.maybe_add_issue(
Issue::new(
IssueKind::UnusedFunctionCall,
"This function is annotated with MustUse but the returned value is not used".to_string(),
statements_analyzer.get_hpos(&stmt.0),
&context.function_context.calling_functionlike_id,
),
statements_analyzer.get_config(),
statements_analyzer.get_file_path_actual(),
);
} else if let Some(expr_type) = analysis_data.get_rc_expr_type(boxed.pos()).cloned()
{
if let Some(expr_type) = analysis_data.get_rc_expr_type(boxed.pos()).cloned() {
let function_name = statements_analyzer.get_interner().lookup(&function_id);

if (function_name.starts_with("HH\\Lib\\Keyset\\")
Expand Down Expand Up @@ -402,6 +403,46 @@ fn detect_unused_statement_expressions(
}
}

fn has_unused_must_use(
boxed: &aast::Expr<(), ()>,
statements_analyzer: &StatementsAnalyzer,
stmt: &aast::Stmt<(), ()>,
context: &mut ScopeContext,
) -> bool {
match &boxed.2 {
aast::Expr_::Call(boxed_call) => {
let functionlike_id = get_functionlike_id_from_call(
boxed_call,
Some(statements_analyzer.get_interner()),
statements_analyzer.get_file_analyzer().resolved_names,
);
if let Some(FunctionLikeIdentifier::Function(function_id)) = functionlike_id {
// For statements like "Asio\join(some_fn());"
// Asio\join does not count as "using" the value
if function_id == StrId::ASIO_JOIN {
return boxed_call.args.iter().any(|arg| {
has_unused_must_use(&arg.1, statements_analyzer, stmt, context)
});
}

let codebase = statements_analyzer.get_codebase();
if let Some(functionlike_info) = codebase
.functionlike_infos
.get(&(function_id, StrId::EMPTY))
{
return functionlike_info.must_use;
}
}
}
aast::Expr_::Await(await_expr) => {
return has_unused_must_use(await_expr, statements_analyzer, stmt, context)
}
_ => (),
}

return false;
}

fn analyze_awaitall(
boxed: (
&Vec<(oxidized::tast::Lid, aast::Expr<(), ()>)>,
Expand Down
12 changes: 12 additions & 0 deletions tests/unused/UnusedExpression/unusedFunctionCallAsync/input.hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<<Hakana\MustUse>>
async function must_use_async(): Awaitable<int> {
return 0;
}

function foo(): void {
Asio\join(must_use_async());
}

function foo_async(): void {
await must_use_async();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ERROR: UnusedFunctionCall - input.hack:7:5 - This function is annotated with MustUse but the returned value is not used
ERROR: UnusedFunctionCall - input.hack:11:5 - This function is annotated with MustUse but the returned value is not used

0 comments on commit 89af83a

Please sign in to comment.