Switch from using mustPanic to mayReturnNormally to construct a call-expression's CFG#239
Conversation
| } | ||
|
|
||
| /** | ||
| * A fatal log function, which calls os.Exit. |
There was a problem hiding this comment.
| * A fatal log function, which calls os.Exit. | |
| * A fatal log function, which calls `os.Exit`. |
| } | ||
|
|
||
| /** | ||
| * The os.Exit function, which ends the process. |
There was a problem hiding this comment.
| * The os.Exit function, which ends the process. | |
| * The `os.Exit` function, which ends the process. |
| /** | ||
| * The Ginkgo Fail function, which always panics. | ||
| */ |
There was a problem hiding this comment.
| /** | |
| * The Ginkgo Fail function, which always panics. | |
| */ | |
| /** The Ginkgo Fail function, which always panics. */ |
(and several other places)
|
I think you forgot to commit your |
max-schaefer
left a comment
There was a problem hiding this comment.
Some suggestions for how to rearrange things and a few questions. Let's discuss how to evaluate this.
| /** Holds if this function has no observable side effects. */ | ||
| predicate mayHaveSideEffects() { none() } | ||
|
|
||
| predicate mayReturnNormally() { any() } |
| not builtinFunction(getName(), _, _, _) or | ||
| builtinFunction(getName(), _, _, false) |
There was a problem hiding this comment.
| not builtinFunction(getName(), _, _, _) or | |
| builtinFunction(getName(), _, _, false) | |
| not builtinFunction(getName(), _, _, true) |
| /** | ||
| * A function that cannot return normally, either because it panics, exits the process, or loops forever. | ||
| */ | ||
| abstract class NoreturnAnnotatedFunction extends Function { |
There was a problem hiding this comment.
Why do we need a class for this?
| /** | ||
| * A fatal log function, which calls os.Exit. | ||
| */ | ||
| class FatalFunction extends NoreturnAnnotatedFunction { |
There was a problem hiding this comment.
This should go into Stdlib.qll, somewhere close to the other models of the log library.
| /** | ||
| * The os.Exit function, which ends the process. | ||
| */ | ||
| class OsExitFunction extends NoreturnAnnotatedFunction { |
There was a problem hiding this comment.
Similarly, should go into Stdlib.qll.
| /** | ||
| * The Ginkgo Fail function, which always panics. | ||
| */ | ||
| class GinkgoFailFunction extends NoreturnAnnotatedFunction { |
There was a problem hiding this comment.
This should go into Testing.qll.
| * A function for which we have source code. | ||
| */ | ||
| class UserDefinedFunction extends DeclaredFunction { | ||
| UserDefinedFunction() { not this instanceof NoreturnAnnotatedFunction and exists(getBody()) } |
There was a problem hiding this comment.
Why the not instanceof?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
OK, but couldn't that be achieved by simply extending UserDefinedFunction and overriding mayReturnNormally?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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") | |||
There was a problem hiding this comment.
Have you run go fmt over this file? We generally do that for consistency.
6823de9 to
bd2ef51
Compare
|
@max-schaefer all comments applied |
max-schaefer
left a comment
There was a problem hiding this comment.
LGTM, modulo a few minor comments and the outcome of the evaluation.
| predicate mayHaveSideEffects() { none() } | ||
|
|
||
| /** | ||
| * Holds if this function may return without panicking, exiting the process or looping forever. |
There was a problem hiding this comment.
| * 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. |
| * 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. |
There was a problem hiding this comment.
| * extending Function rather than having to remember to extend DeclaredFunction. | |
| * extending `Function` rather than having to remember to extend `DeclaredFunction`. |
| * 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 |
There was a problem hiding this comment.
| * 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 |
| class FailFunction extends Function { | ||
| FailFunction() { hasQualifiedName("github.com/onsi/ginkgo", "Fail") } | ||
|
|
||
| override predicate mayReturnNormally() { none() } |
There was a problem hiding this comment.
Do we need this? Doesn't the override of mustPanic() already entail this?
bd2ef51 to
7676101
Compare
|
Eval findings:
|
|
Great! Would you happen to have a link to the report? |
|
Thanks! Perhaps you could take a stab at updating |
|
@max-schaefer mergeable now that #246 is in? |
max-schaefer
left a comment
There was a problem hiding this comment.
Basically LGTM, modulo some very minor nits.
…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.
7676101 to
6e5ee47
Compare
|
@max-schaefer nits picked |
Call-graph API cleanup
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/test5and 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
test8is in fact unreachable due to unconditionally callingtest5early 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.