Skip to content

Commit

Permalink
[inferpython][3.10] change ContextManager management
Browse files Browse the repository at this point in the history
Summary: the Python compiler does not emit any more WITH_CLEANUP_START and WITH_CLEANUP_FINISH. We take that into account here.

Reviewed By: skcho

Differential Revision:
D65031228

Privacy Context Container: L1208441

fbshipit-source-id: 3f5ab92f8f8b70986a3ede324a3beaaf8306079b
  • Loading branch information
davidpichardie authored and facebook-github-bot committed Oct 29, 2024
1 parent f6d9c72 commit daf8c8f
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 67 deletions.
59 changes: 7 additions & 52 deletions infer/src/python/PyIR.ml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ module Ident : sig

val enter : t

val exit : t

val name : t

val print : t
Expand All @@ -54,6 +56,8 @@ end = struct

let enter = "__enter__"

let exit = "__exit__"

let name = "__name__"

let print = "print"
Expand Down Expand Up @@ -555,8 +559,6 @@ module Error = struct
| FixpointComputationNotReached of {src: int; dest: int}
| NextOffsetMissing
| MissingNodeInformation of int
| WithCleanupStart of Exp.opstack_symbol
| WithCleanupFinish of Exp.opstack_symbol
| RaiseExceptionInvalid of int
| SubroutineEmptyStack of int

Expand Down Expand Up @@ -596,12 +598,6 @@ module Error = struct
F.fprintf fmt "Jump to next instruction detected, but next instruction is missing"
| MissingNodeInformation offset ->
F.fprintf fmt "No information about offset %d" offset
| WithCleanupStart exp ->
F.fprintf fmt "WITH_CLEANUP_START/TODO: unsupported scenario with %a" Exp.pp_opstack_symbol
exp
| WithCleanupFinish exp ->
F.fprintf fmt "WITH_CLEANUP_FINISH/TODO: unsupported scenario with %a" Exp.pp_opstack_symbol
exp
| RaiseExceptionInvalid n ->
F.fprintf fmt "RAISE_VARARGS: Invalid mode %d" n
| SubroutineEmptyStack offset ->
Expand Down Expand Up @@ -1172,6 +1168,9 @@ let call_function_with_unnamed_args st ?arg_names exp args =
match exp with
| Exp.BuiltinCaller call ->
Ok (Stmt.BuiltinCall {lhs; call; args; arg_names})
| Exp.ContextManagerExit self_if_needed ->
let name = Ident.Special.exit in
Ok (Stmt.CallMethod {lhs; name; self_if_needed; args= []; arg_names})
| exp ->
let+ exp = State.cast_exp st exp in
Stmt.Call {lhs; exp; args; arg_names}
Expand Down Expand Up @@ -1413,44 +1412,6 @@ let for_iter st delta next_offset_opt =
; else_= TerminatorBuilder.mk_node_call other_label other_stack } ) )


(* See https://github.com/python/cpython/blob/3.8/Python/ceval.c#L3300 *)
let with_cleanup_start st =
let open IResult.Let_syntax in
let* tos, st = State.pop_and_cast st in
let* () =
match (tos : Exp.t) with
| Const None ->
Ok ()
| _ ->
internal_error st (Error.WithCleanupStart (Exp tos))
in
let* context_manager_exit, st = State.pop st in
let* context_manager =
match context_manager_exit with
| Exp.ContextManagerExit exp ->
Ok exp
| exp ->
internal_error st (Error.WithCleanupStart exp)
in
let id, st = call_method st Ident.Special.enter context_manager [Exp.none; Exp.none; Exp.none] in
let st = State.push st Exp.none in
let st = State.push st Exp.none in
let st = State.push st (Temp id) in
Ok (st, None)


(** See https://github.com/python/cpython/blob/3.8/Python/ceval.c#L3373 *)
let with_cleanup_finish st =
let open IResult.Let_syntax in
let* _exit_res, st = State.pop st in
let* tos, st = State.pop st in
match (tos : Exp.opstack_symbol) with
| Exp (Const None) ->
Ok (st, None)
| _ ->
internal_error st (Error.WithCleanupFinish tos)


let raise_varargs st argc =
let open IResult.Let_syntax in
match argc with
Expand Down Expand Up @@ -1989,10 +1950,6 @@ let parse_bytecode st ({FFI.Code.co_consts; co_names; co_varnames} as code)
Ok st
in
Ok (st, None)
| "WITH_CLEANUP_START" ->
with_cleanup_start st
| "WITH_CLEANUP_FINISH" ->
with_cleanup_finish st
| "RAISE_VARARGS" ->
raise_varargs st arg
| "POP_EXCEPT" ->
Expand Down Expand Up @@ -2241,8 +2198,6 @@ let get_successors_offset {FFI.Instruction.opname; arg} =
| "SETUP_WITH"
| "SETUP_FINALLY"
| "POP_FINALLY"
| "WITH_CLEANUP_START"
| "WITH_CLEANUP_FINISH"
| "POP_EXCEPT"
| "BUILD_TUPLE_UNPACK_WITH_CALL"
| "BUILD_TUPLE_UNPACK"
Expand Down
150 changes: 145 additions & 5 deletions infer/src/python/unit/PyIRTest.ml
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,60 @@ def f(x, y, l, bar, toto):
|}
in
PyIR.test source ;
[%expect {|
IR error: UNEXPECTED_EXPRESSION: CM(n8).__exit__ |}]
[%expect
{|
module dummy:

function toplevel():
b0:
n0 <- $MakeFunction["f", "dummy.f", None, None, None, None]
TOPLEVEL[f] <- n0
return None


function dummy.f(x, y, l, bar, toto):
b0:
n0 <- LOCAL[l]
n1 <- $GetIter(n0, None)
jmp b1

b1:
n2 <- $NextIter(n1, None)
n3 <- $HasNextIter(n1, None)
if n3 then jmp b2 else jmp b13

b12:
jmp b1

b13:
return None

b2:
LOCAL[x] <- n2
n4 <- LOCAL[bar]
n5 <- $Call(n4, None)
n6 <- $CallMethod[__enter__](n5, None)
n7 <- LOCAL[toto]
n8 <- $Call(n7, None)
n9 <- $CallMethod[__enter__](n8, None)
LOCAL[obj] <- n9
n10 <- LOCAL[y]
if n10 then jmp b3 else jmp b4

b3:
n15 <- $CallMethod[__exit__](n8, None)
n16 <- $CallMethod[__exit__](n5, None)
jmp b1

b4:
n11 <- GLOBAL[print]
n12 <- $Call(n11, "nop", None)
n13 <- $CallMethod[__exit__](n8, None)
jmp b8

b8:
n14 <- $CallMethod[__exit__](n5, None)
jmp b12 |}]


let%expect_test _ =
Expand Down Expand Up @@ -820,8 +872,39 @@ def f(foo, bar):
|}
in
PyIR.test source ;
[%expect {|
IR error: UNEXPECTED_EXPRESSION: CM(n4).__exit__ |}]
[%expect
{|
module dummy:

function toplevel():
b0:
n0 <- $MakeFunction["f", "dummy.f", None, None, None, None]
TOPLEVEL[f] <- n0
return None


function dummy.f(foo, bar):
b0:
n0 <- LOCAL[foo]
n1 <- $Call(n0, None)
n2 <- $CallMethod[__enter__](n1, None)
LOCAL[foo0] <- n2
n3 <- LOCAL[bar]
n4 <- $Call(n3, None)
n5 <- $CallMethod[__enter__](n4, None)
LOCAL[bar0] <- n5
n6 <- GLOBAL[print]
n7 <- LOCAL[bar0]
n8 <- $Call(n6, n7, None)
n9 <- $CallMethod[__exit__](n4, None)
jmp b4

b4:
n10 <- GLOBAL[print]
n11 <- LOCAL[foo0]
n12 <- $Call(n10, n11, None)
n13 <- $CallMethod[__exit__](n1, None)
return 42 |}]


let%expect_test _ =
Expand Down Expand Up @@ -1659,4 +1742,61 @@ async def foo():
44 DUP_TOP 0
[n3; CM(n8).__exit__; None; None; None]
46 CALL_FUNCTION 3
IR error: UNEXPECTED_EXPRESSION: CM(n8).__exit__ |}]
[n3; n13]
48 GET_AWAITABLE 0
[n3; n14]
50 LOAD_CONST 0 (None)
[n3; n14; None]
52 YIELD_FROM 0
[n3; n14]
54 POP_TOP 0
[n3]
56 POP_TOP 0
[]
58 LOAD_CONST 0 (None)
[None]
60 RETURN_VALUE 0
[]
Successors:


module dummy:

function toplevel():
b0:
n0 <- $MakeFunction["foo", "dummy.foo", None, None, None, None]
TOPLEVEL[foo] <- n0
return None


function dummy.foo(i, f):
b0:
$GenStartCoroutine()
n0 <- GLOBAL[range]
n1 <- GLOBAL[num]
n2 <- $Call(n0, n1, None)
n3 <- $GetIter(n2, None)
jmp b1

b1:
n4 <- $NextIter(n3, None)
n5 <- $HasNextIter(n3, None)
if n5 then jmp b2 else jmp b6

b2:
LOCAL[i] <- n4
n6 <- GLOBAL[read]
n7 <- $Call(n6, None)
n8 <- $GetAwaitable(n7, None)
n9 <- $YieldFrom(n8, None, None)
n10 <- $CallMethod[__enter__](n8, None)
n11 <- $GetAwaitable(n10, None)
n12 <- $YieldFrom(n11, None, None)
LOCAL[f] <- n11
n13 <- $CallMethod[__exit__](n8, None)
n14 <- $GetAwaitable(n13, None)
n15 <- $YieldFrom(n14, None, None)
return None

b6:
return None |}]
16 changes: 14 additions & 2 deletions infer/src/python/unit/PyIRTestBasic.ml
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,20 @@ with open("foo.txt", "wt") as fp:
fp.write("yolo")
|} in
PyIR.test source ;
[%expect {|
IR error: UNEXPECTED_EXPRESSION: CM(n1).__exit__ |}]
[%expect
{|
module dummy:

function toplevel():
b0:
n0 <- TOPLEVEL[open]
n1 <- $Call(n0, "foo.txt", "wt", None)
n2 <- $CallMethod[__enter__](n1, None)
TOPLEVEL[fp] <- n2
n3 <- TOPLEVEL[fp]
n4 <- $CallMethod[write](n3, "yolo", None)
n5 <- $CallMethod[__exit__](n1, None)
return None |}]


let%expect_test _ =
Expand Down
Loading

0 comments on commit daf8c8f

Please sign in to comment.