Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Switch from using mustPanic to mayReturnNormally to construct a call-expression's CFG#239

Merged
max-schaefer merged 1 commit intogithub:masterfrom
smowton:smowton/feature/find-noreturn-user-functions
Jul 7, 2020
Merged

Switch from using mustPanic to mayReturnNormally to construct a call-expression's CFG#239
max-schaefer merged 1 commit intogithub:masterfrom
smowton:smowton/feature/find-noreturn-user-functions

Conversation

@smowton
Copy link
Copy Markdown
Contributor

@smowton smowton commented Jun 30, 2020

We also use this to note that user-defined functions can only return normally if their CFG normal exit node is reachable, and annotate some well-known functions as noreturn.

For example, this will by fiat declare os.Exit noreturn (never returns normally), and will also notice that a user function func myExit() { os.Exit(1) } is also noreturn, because it doesn't have any control-flow edges that reach the normal return node.

Note that the test diff shows some expected changes to other functions' CFGs, which happens because stmts.go / test5 and a few other test functions cannot return.

There are also a couple of apparently-unrelated changes, but these turn out to be because most of test8 is in fact unreachable due to unconditionally calling test5 early on. A partial, unconnected CFG is generated for the unreachable statements before the absence of a normal exit edge is noticed. This may constitute a bug if we care about the internal consistency of the CFG of unreachable blocks.

@smowton smowton requested a review from a team June 30, 2020 16:48
Comment thread ql/src/semmle/go/Scopes.qll Outdated
}

/**
* A fatal log function, which calls os.Exit.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* A fatal log function, which calls os.Exit.
* A fatal log function, which calls `os.Exit`.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
}

/**
* The os.Exit function, which ends the process.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* The os.Exit function, which ends the process.
* The `os.Exit` function, which ends the process.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
Comment on lines +733 to +735
/**
* The Ginkgo Fail function, which always panics.
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
/**
* The Ginkgo Fail function, which always panics.
*/
/** The Ginkgo Fail function, which always panics. */

(and several other places)

@owen-mc
Copy link
Copy Markdown
Contributor

owen-mc commented Jul 1, 2020

I think you forgot to commit your .expected file

Copy link
Copy Markdown
Contributor

@max-schaefer max-schaefer left a comment

Choose a reason for hiding this comment

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

Some suggestions for how to rearrange things and a few questions. Let's discuss how to evaluate this.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
/** Holds if this function has no observable side effects. */
predicate mayHaveSideEffects() { none() }

predicate mayReturnNormally() { any() }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Needs qldoc.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
Comment on lines +523 to +524
not builtinFunction(getName(), _, _, _) or
builtinFunction(getName(), _, _, false)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
not builtinFunction(getName(), _, _, _) or
builtinFunction(getName(), _, _, false)
not builtinFunction(getName(), _, _, true)

Comment thread ql/src/semmle/go/Scopes.qll Outdated
/**
* A function that cannot return normally, either because it panics, exits the process, or loops forever.
*/
abstract class NoreturnAnnotatedFunction extends Function {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need a class for this?

Comment thread ql/src/semmle/go/Scopes.qll Outdated
/**
* A fatal log function, which calls os.Exit.
*/
class FatalFunction extends NoreturnAnnotatedFunction {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should go into Stdlib.qll, somewhere close to the other models of the log library.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
/**
* The os.Exit function, which ends the process.
*/
class OsExitFunction extends NoreturnAnnotatedFunction {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Similarly, should go into Stdlib.qll.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
/**
* The Ginkgo Fail function, which always panics.
*/
class GinkgoFailFunction extends NoreturnAnnotatedFunction {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should go into Testing.qll.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
* A function for which we have source code.
*/
class UserDefinedFunction extends DeclaredFunction {
UserDefinedFunction() { not this instanceof NoreturnAnnotatedFunction and exists(getBody()) }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why the not instanceof?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Allows one to extend NoreturnAnnotatedFunction in a query to mark a function noreturn which we do have a body for, but where the noreturn-ness is not obvious enough to be detected automatically.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

OK, but couldn't that be achieved by simply extending UserDefinedFunction and overriding mayReturnNormally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That does work, but you have to remember to do that rather than directly extend Function, otherwise you will match both UserDefinedFunction and MyFunctionExtension, and your override of mayReturnNormally has no effect

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But I guess now you'd have to remember to override NoreturnAnnotatedFunction, so maybe that doesn't help. Basically I want to allow people to use class MyFn extends Function { override predicate mayReturnNormally ... } with the expected behaviour, rather than having to know that UserDefinedFunction exists and will be providing a competing definition

@@ -0,0 +1,23 @@
package main

import ("os"; "log")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Have you run go fmt over this file? We generally do that for consistency.

Comment thread ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll
@smowton smowton force-pushed the smowton/feature/find-noreturn-user-functions branch 2 times, most recently from 6823de9 to bd2ef51 Compare July 1, 2020 11:31
@smowton
Copy link
Copy Markdown
Contributor Author

smowton commented Jul 1, 2020

@max-schaefer all comments applied

Copy link
Copy Markdown
Contributor

@max-schaefer max-schaefer left a comment

Choose a reason for hiding this comment

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

LGTM, modulo a few minor comments and the outcome of the evaluation.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
predicate mayHaveSideEffects() { none() }

/**
* Holds if this function may return without panicking, exiting the process or looping forever.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Holds if this function may return without panicking, exiting the process or looping forever.
* Holds if this function may return without panicking, exiting the process, or looping forever.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
* return normally, but it never fails to hold for functions that can.
*
* Note this is declared here and not in DeclaredFunction so that queries can override this by
* extending Function rather than having to remember to extend DeclaredFunction.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* extending Function rather than having to remember to extend DeclaredFunction.
* extending `Function` rather than having to remember to extend `DeclaredFunction`.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
* This predicate is an over-approximation: it may hold for functions that can never
* return normally, but it never fails to hold for functions that can.
*
* Note this is declared here and not in DeclaredFunction so that queries can override this by
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Note this is declared here and not in DeclaredFunction so that queries can override this by
* Note this is declared here and not in `DeclaredFunction` so that library models can override this by

Comment thread ql/src/semmle/go/frameworks/Testing.qll Outdated
class FailFunction extends Function {
FailFunction() { hasQualifiedName("github.com/onsi/ginkgo", "Fail") }

override predicate mayReturnNormally() { none() }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need this? Doesn't the override of mustPanic() already entail this?

@smowton smowton force-pushed the smowton/feature/find-noreturn-user-functions branch from bd2ef51 to 7676101 Compare July 1, 2020 13:52
@smowton
Copy link
Copy Markdown
Contributor Author

smowton commented Jul 2, 2020

Eval findings:

  • Total time change for default.slugs negligible (<0.1%)
  • Best project -8%, worst +5%, both <30s runs
  • Long-runners cockroach -2.5%, aws-sdk +3.3%
  • The new stage including mayReturnNormally is 8% slower than the old one
  • 43 new alerts from unreachable-statement, all correct
  • To reduce noise we may want to stop flagging unreachable code that consists solely of a return or panic statement, since Go doesn't have an explicit unreachable instruction. This would remove about 40 of the new alerts; the remainder really do feature unreachable attempts to dump more debug info or issue an HTTP response.

@max-schaefer
Copy link
Copy Markdown
Contributor

Great! Would you happen to have a link to the report?

@smowton
Copy link
Copy Markdown
Contributor Author

smowton commented Jul 2, 2020

@max-schaefer
Copy link
Copy Markdown
Contributor

Thanks! Perhaps you could take a stab at updating UnreachableStatement to not flag trivial return statements (i.e., all returned values are constants or something like that)? And similar for break and continue, I suppose.

@smowton
Copy link
Copy Markdown
Contributor Author

smowton commented Jul 7, 2020

@max-schaefer mergeable now that #246 is in?

Copy link
Copy Markdown
Contributor

@max-schaefer max-schaefer left a comment

Choose a reason for hiding this comment

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

Basically LGTM, modulo some very minor nits.

Comment thread ql/src/semmle/go/Scopes.qll Outdated
Comment thread ql/src/semmle/go/controlflow/ControlFlowGraph.qll Outdated
Comment thread ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll Outdated
Comment thread ql/src/semmle/go/frameworks/Stdlib.qll Outdated
Comment thread ql/src/semmle/go/frameworks/Testing.qll
…expression's CFG

We also use this to note that user-defined functions can only return normally if their CFG normal exit node is reachable, and annotate some well-known functions as noreturn.

For example, this will by fiat declare os.Exit noreturn (never returns normally), and will also notice that a user function `func myExit() { os.Exit(1) }` is also noreturn, because it doesn't have any control-flow edges that reach the normal return node.
@smowton smowton force-pushed the smowton/feature/find-noreturn-user-functions branch from 7676101 to 6e5ee47 Compare July 7, 2020 10:40
@smowton
Copy link
Copy Markdown
Contributor Author

smowton commented Jul 7, 2020

@max-schaefer nits picked

@max-schaefer max-schaefer merged commit 47a8586 into github:master Jul 7, 2020
ceh pushed a commit to ceh-forks/codeql-go that referenced this pull request Jul 22, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants