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

[Draft] Python: Modernize File Not Always Closed query #18845

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 58 additions & 57 deletions python/ql/src/Resources/FileNotAlwaysClosed.ql
Original file line number Diff line number Diff line change
Expand Up @@ -13,62 +13,63 @@
*/

import python
import FileOpen

/**
* Whether resource is opened and closed in in a matched pair of methods,
* either `__enter__` and `__exit__` or `__init__` and `__del__`
*/
predicate opened_in_enter_closed_in_exit(ControlFlowNode open) {
file_not_closed_at_scope_exit(open) and
exists(FunctionValue entry, FunctionValue exit |
open.getScope() = entry.getScope() and
exists(ClassValue cls |
cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit
or
cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit
) and
exists(AttrNode attr_open, AttrNode attrclose |
attr_open.getScope() = entry.getScope() and
attrclose.getScope() = exit.getScope() and
expr_is_open(attr_open.(DefinitionNode).getValue(), open) and
attr_open.getName() = attrclose.getName() and
close_method_call(_, attrclose)
)
)
}

predicate file_not_closed_at_scope_exit(ControlFlowNode open) {
exists(EssaVariable v |
BaseFlow::reaches_exit(v) and
var_is_open(v, open) and
not file_is_returned(v, open)
)
or
call_to_open(open) and
not exists(AssignmentDefinition def | def.getValue() = open) and
not exists(Return r | r.getValue() = open.getNode())
}

predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) {
exists(EssaVariable v |
exit.(RaisingNode).viableExceptionalExit(_, _) and
not closes_arg(exit, v.getSourceVariable()) and
not close_method_call(exit, v.getAUse().(NameNode)) and
var_is_open(v, open) and
v.getAUse() = exit.getAChild*()
)
}
// import FileOpen
import FileNotAlwaysClosedQuery

// /**
// * Whether resource is opened and closed in in a matched pair of methods,

Check warning

Code scanning / CodeQL

Comment has repeated word Warning

The comment repeats in.
// * either `__enter__` and `__exit__` or `__init__` and `__del__`
// */
// predicate opened_in_enter_closed_in_exit(ControlFlowNode open) {
// file_not_closed_at_scope_exit(open) and
// exists(FunctionValue entry, FunctionValue exit |
// open.getScope() = entry.getScope() and
// exists(ClassValue cls |
// cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit
// or
// cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit
// ) and
// exists(AttrNode attr_open, AttrNode attrclose |
// attr_open.getScope() = entry.getScope() and
// attrclose.getScope() = exit.getScope() and
// expr_is_open(attr_open.(DefinitionNode).getValue(), open) and
// attr_open.getName() = attrclose.getName() and
// close_method_call(_, attrclose)
// )
// )
// }
// predicate file_not_closed_at_scope_exit(ControlFlowNode open) {
// exists(EssaVariable v |
// BaseFlow::reaches_exit(v) and
// var_is_open(v, open) and
// not file_is_returned(v, open)
// )
// or
// call_to_open(open) and
// not exists(AssignmentDefinition def | def.getValue() = open) and
// not exists(Return r | r.getValue() = open.getNode())
// }
// predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) {
// exists(EssaVariable v |
// exit.(RaisingNode).viableExceptionalExit(_, _) and
// not closes_arg(exit, v.getSourceVariable()) and
// not close_method_call(exit, v.getAUse().(NameNode)) and
// var_is_open(v, open) and
// v.getAUse() = exit.getAChild*()
// )
// }
/* Check to see if a file is opened but not closed or returned */
from ControlFlowNode defn, string message
where
not opened_in_enter_closed_in_exit(defn) and
(
file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed."
or
not file_not_closed_at_scope_exit(defn) and
file_not_closed_at_exception_exit(defn, _) and
message = "File may not be closed if an exception is raised."
)
select defn.getNode(), message
// from ControlFlowNode defn, string message
// where
// not opened_in_enter_closed_in_exit(defn) and
// (
// file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed."
// or
// not file_not_closed_at_scope_exit(defn) and
// file_not_closed_at_exception_exit(defn, _) and
// message = "File may not be closed if an exception is raised."
// )
// select defn.getNode(), message
from FileOpen fo
where fileNotAlwaysClosed(fo)
select fo, "File is opened but is not closed."
39 changes: 39 additions & 0 deletions python/ql/src/Resources/FileNotAlwaysClosedQuery.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** Definitions for reasoning about whether files are closed. */

import python
//import semmle.python.dataflow.DataFlow
import semmle.python.ApiGraphs

abstract class FileOpen extends DataFlow::CfgNode { }

class FileOpenCall extends FileOpen {
FileOpenCall() { this = API::builtin("open").getACall() }
}

// todo: type tracking to find wrapping funcs
abstract class FileClose extends DataFlow::CfgNode { }

class FileCloseCall extends FileClose {
FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) }
}

class WithStatement extends FileClose {
WithStatement() { exists(With w | this.asExpr() = w.getContextExpr()) }
}

predicate fileIsClosed(FileOpen fo) { exists(FileClose fc | DataFlow::localFlow(fo, fc)) }

predicate fileIsReturned(FileOpen fo) {
exists(Return ret | DataFlow::localFlow(fo, DataFlow::exprNode(ret.getValue())))
}

predicate fileIsStoredInField(FileOpen fo) {
exists(DataFlow::AttrWrite aw | DataFlow::localFlow(fo, aw.getValue()))
}

predicate fileNotAlwaysClosed(FileOpen fo) {
not fileIsClosed(fo) and
not fileIsReturned(fo) and
not fileIsStoredInField(fo)
// TODO: exception cases
}
119 changes: 0 additions & 119 deletions python/ql/test/query-tests/Resources/Dataflow.expected

This file was deleted.

14 changes: 0 additions & 14 deletions python/ql/test/query-tests/Resources/Dataflow.ql

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading