Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add repeat_linter for infinite loops #2107

Merged
merged 18 commits into from
Sep 7, 2023
Merged

Conversation

MEO265
Copy link
Contributor

@MEO265 MEO265 commented Sep 5, 2023

PR resolves #2106

@codecov-commenter
Copy link

codecov-commenter commented Sep 5, 2023

Codecov Report

Merging #2107 (ab17c87) into main (fa914d0) will increase coverage by 0.00%.
The diff coverage is 100.00%.

❗ Current head ab17c87 differs from pull request most recent head 5d95612. Consider uploading reports for the commit 5d95612 to get more accurate results

@@           Coverage Diff           @@
##             main    #2107   +/-   ##
=======================================
  Coverage   99.65%   99.65%           
=======================================
  Files         119      120    +1     
  Lines        5228     5240   +12     
=======================================
+ Hits         5210     5222   +12     
  Misses         18       18           
Files Changed Coverage Δ
R/repeat_linter.R 100.00% <100.00%> (ø)

📢 Have feedback on the report? Share it here.

@@ -0,0 +1,46 @@
#' Repeat linter
#'
#' Check that `while` is not used for infinite loops.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wording suggestion here. this could read as "any infinite while loop is detected" which is broader than the actual linter. maybe "simple infinite loops", or just mention while(TRUE) here

#' @export
repeat_linter <- function() {
xpath <- "
//WHILE[following-sibling::*[1][self::OP-LEFT-PAREN] and
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first condition looks redundant to me. is it possible to write a while loop without '('? if you're worried about 'while' being used in other contexts, while is a reserved word so it will show up as a SYMBOL IINM (worth adding a test for this to be sure)

Copy link
Contributor Author

@MEO265 MEO265 Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while is a reserved word so it will show up as a SYMBOL IINM (worth adding a test for this to be sure)

When I try to create a test for it, the parser always flags an error. I would skip the tests if you don't have a suggestion

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works: parse(text = "`while`(TRUE, NULL)"), but actually resolves as equivalent to while (TRUE) NULL.

We can actually get into really funky territory here:

`while` <- identity
while(TRUE)
# [1] TRUE
while (FALSE) { }
# Error in while (FALSE) { : unused argument ({
# })

I'm OK to skip tests.

xpath <- "
//WHILE[following-sibling::*[1][self::OP-LEFT-PAREN] and
following-sibling::*[2][self::expr[NUM_CONST[text()='TRUE'] and count(*)=1]]
and following-sibling::*[3][self::OP-RIGHT-PAREN]]"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is also redundant. if you're worried about more complicated expressions in the condition, your test for NUM_CONST is implicitly handling that -- TRUE would show up at further nesting of in that case.

repeat_linter <- function() {
xpath <- "
//WHILE[following-sibling::*[1][self::OP-LEFT-PAREN] and
following-sibling::*[2][self::expr[NUM_CONST[text()='TRUE'] and count(*)=1]]
Copy link
Collaborator

@MichaelChirico MichaelChirico Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simplify this as following-sibling::expr[1]/NUM_CONST[...]

the node structure of while() loops is pretty simple in terms of what can parse, so no need to be overly defensive IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented it as suggested but still had to test that an expression existed at all

lints <- xml_nodes_to_lints(
xml_find_all(xml, xpath),
source_expression = source_expression,
lint_message = "'while (TRUE)' is not recommended for infinite loops. Use 'repeat' instead.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer the actionable bit first (there's an issue about doing so consistently in the tracker somewhere...). here that would be "Use repeat instead of while(TRUE) for infinite loops"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more suggested test cases:

# while as a function call
`while`(TRUE)
# TRUE is the expression
while (j < 5) TRUE
# TRUE in a more complicated condition
while (TRUE && j < 5) { ... }
# sensitivity to '{'
while (TRUE) NULL
while (j < 5) NULL

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also should test for lint locations, especially with multiline code and multiple lints in one expression.

{
  while (TRUE) {
  }
  while (TRUE) {
  }
}

Copy link
Collaborator

@MichaelChirico MichaelChirico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good and works, thanks! My comments are for polish/completeness.

@MEO265
Copy link
Contributor Author

MEO265 commented Sep 5, 2023

The code looks good and works, thanks! My comments are for polish/completeness.

Understood. I'm always happy about comments that help to improve.

#' @export
repeat_linter <- function() {
xpath <- "
//*[(self::WHILE or self::expr[SYMBOL_FUNCTION_CALL[.='`while`']])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//* is really troublesome for XPath -- they are evaluated greedily and so this linter will be really expensive on files with a lot of expressions. See https://github.com/r-lib/lintr/wiki/Writing-robust,-performant-linters.

I really think we can ignore the `while` case unless there's a specific request for it -- that's a really unusual way to write a while loop. Otherwise, we would usually write this as //WHILE[...] | //SYMBOL_FUNCTION_CALL[text() = 'while']/parent::expr[...].

(TIL you can use . = '...' to check the text on a node btw! Nice find. I still like text() = '...' for expressiveness, . = '...' is a little bit harder to reason about)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will ignore the `while` case.
I misunderstood you I thought you would have wished for this case by mentioning it as a possible test here

@MichaelChirico
Copy link
Collaborator

@MEO265 FYI, I edited the PR description. "Solves" is not the ideal keyword since it won't auto-close the issue -- see https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue.

@MEO265
Copy link
Contributor Author

MEO265 commented Sep 6, 2023

@MEO265 FYI, I edited the PR description. "Solves" is not the ideal keyword since it won't auto-close the issue -- see https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue.

Thanks for the info.

@MEO265
Copy link
Contributor Author

MEO265 commented Sep 7, 2023

Can someone tell me why pipeline "check-link-rot" fails and whether I can influence it at all?

@MichaelChirico
Copy link
Collaborator

Can someone tell me why pipeline "check-link-rot" fails and whether I can influence it at all?

ignore it, it's been happening to other builds as well, that link appears fragile.

MichaelChirico
MichaelChirico previously approved these changes Sep 7, 2023
@MEO265
Copy link
Contributor Author

MEO265 commented Sep 7, 2023

@MichaelChirico I looked at your changes and will try to do the same next time

@MichaelChirico MichaelChirico reopened this Sep 7, 2023
@MichaelChirico MichaelChirico merged commit 99fdd85 into r-lib:main Sep 7, 2023
41 of 42 checks passed
@MEO265 MEO265 deleted the repeat_linter branch September 7, 2023 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Detection of while(TRUE) and while(FALSE)
4 participants