1#     Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com
2#
3#     Part of "Nuitka", an optimizing Python compiler that is compatible and
4#     integrates with CPython, but also works on its own.
5#
6#     Licensed under the Apache License, Version 2.0 (the "License");
7#     you may not use this file except in compliance with the License.
8#     You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#     Unless required by applicable law or agreed to in writing, software
13#     distributed under the License is distributed on an "AS IS" BASIS,
14#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#     See the License for the specific language governing permissions and
16#     limitations under the License.
17#
18""" Nodes concern with exec and eval builtins.
19
20These are the dynamic codes, and as such rather difficult. We would like
21to eliminate or limit their impact as much as possible, but it's difficult
22to do.
23"""
24
25from nuitka.PythonVersions import python_version
26
27from .ExpressionBases import ExpressionChildrenHavingBase
28from .NodeBases import StatementChildHavingBase, StatementChildrenHavingBase
29from .NodeMakingHelpers import (
30    convertNoneConstantToNone,
31    makeStatementOnlyNodesFromExpressions,
32)
33
34
35class ExpressionBuiltinEval(ExpressionChildrenHavingBase):
36    kind = "EXPRESSION_BUILTIN_EVAL"
37
38    named_children = ("source", "globals_arg", "locals_arg")
39
40    def __init__(self, source_code, globals_arg, locals_arg, source_ref):
41        ExpressionChildrenHavingBase.__init__(
42            self,
43            values={
44                "source": source_code,
45                "globals_arg": globals_arg,
46                "locals_arg": locals_arg,
47            },
48            source_ref=source_ref,
49        )
50
51    def computeExpression(self, trace_collection):
52        # TODO: Attempt for constant values to do it.
53        return self, None, None
54
55
56# Note: Python3 only so far.
57if python_version >= 0x300:
58
59    class ExpressionBuiltinExec(ExpressionBuiltinEval):
60        kind = "EXPRESSION_BUILTIN_EXEC"
61
62        def __init__(self, source_code, globals_arg, locals_arg, source_ref):
63            ExpressionBuiltinEval.__init__(
64                self,
65                source_code=source_code,
66                globals_arg=globals_arg,
67                locals_arg=locals_arg,
68                source_ref=source_ref,
69            )
70
71        def computeExpression(self, trace_collection):
72            # TODO: Attempt for constant values to do it.
73            return self, None, None
74
75        def computeExpressionDrop(self, statement, trace_collection):
76            if self.getParentVariableProvider().isEarlyClosure():
77                result = StatementExec(
78                    source_code=self.subnode_source,
79                    globals_arg=self.subnode_globals_arg,
80                    locals_arg=self.subnode_locals_arg,
81                    source_ref=self.source_ref,
82                )
83
84                del self.parent
85
86                return (
87                    result,
88                    "new_statements",
89                    """\
90Replaced built-in exec call to exec statement in early closure context.""",
91                )
92            else:
93                return statement, None, None
94
95
96# Note: Python2 only
97if python_version < 0x300:
98
99    class ExpressionBuiltinExecfile(ExpressionBuiltinEval):
100        kind = "EXPRESSION_BUILTIN_EXECFILE"
101
102        named_children = ("source", "globals_arg", "locals_arg")
103
104        def __init__(self, source_code, globals_arg, locals_arg, source_ref):
105            ExpressionBuiltinEval.__init__(
106                self,
107                source_code=source_code,
108                globals_arg=globals_arg,
109                locals_arg=locals_arg,
110                source_ref=source_ref,
111            )
112
113        def computeExpressionDrop(self, statement, trace_collection):
114            # In this case, the copy-back must be done and will only be done
115            # correctly by the code for exec statements.
116            provider = self.getParentVariableProvider()
117
118            if provider.isExpressionClassBody():
119                result = StatementExec(
120                    source_code=self.subnode_source,
121                    globals_arg=self.subnode_globals_arg,
122                    locals_arg=self.subnode_locals_arg,
123                    source_ref=self.source_ref,
124                )
125
126                del self.parent
127
128                return (
129                    result,
130                    "new_statements",
131                    """\
132Changed 'execfile' with unused result to 'exec' on class level.""",
133                )
134            else:
135                return statement, None, None
136
137
138class StatementExec(StatementChildrenHavingBase):
139    kind = "STATEMENT_EXEC"
140
141    named_children = ("source", "globals_arg", "locals_arg")
142
143    def __init__(self, source_code, globals_arg, locals_arg, source_ref):
144        StatementChildrenHavingBase.__init__(
145            self,
146            values={
147                "globals_arg": globals_arg,
148                "locals_arg": locals_arg,
149                "source": source_code,
150            },
151            source_ref=source_ref,
152        )
153
154    def setChild(self, name, value):
155        if name in ("globals_arg", "locals_arg"):
156            value = convertNoneConstantToNone(value)
157
158        return StatementChildrenHavingBase.setChild(self, name, value)
159
160    def computeStatement(self, trace_collection):
161        source_code = trace_collection.onExpression(expression=self.subnode_source)
162
163        if source_code.mayRaiseException(BaseException):
164            trace_collection.onExceptionRaiseExit(BaseException)
165
166        if source_code.willRaiseException(BaseException):
167            result = source_code
168
169            return (
170                result,
171                "new_raise",
172                """\
173Exec statement raises implicitly when determining source code argument.""",
174            )
175
176        globals_arg = trace_collection.onExpression(
177            expression=self.subnode_globals_arg, allow_none=True
178        )
179
180        if globals_arg is not None and globals_arg.mayRaiseException(BaseException):
181            trace_collection.onExceptionRaiseExit(BaseException)
182
183        if globals_arg is not None and globals_arg.willRaiseException(BaseException):
184            result = makeStatementOnlyNodesFromExpressions(
185                expressions=(source_code, globals_arg)
186            )
187
188            return (
189                result,
190                "new_raise",
191                """\
192Exec statement raises implicitly when determining globals argument.""",
193            )
194
195        locals_arg = trace_collection.onExpression(
196            expression=self.subnode_locals_arg, allow_none=True
197        )
198
199        if locals_arg is not None and locals_arg.mayRaiseException(BaseException):
200            trace_collection.onExceptionRaiseExit(BaseException)
201
202        if locals_arg is not None and locals_arg.willRaiseException(BaseException):
203            result = makeStatementOnlyNodesFromExpressions(
204                expressions=(source_code, globals_arg, locals_arg)
205            )
206
207            return (
208                result,
209                "new_raise",
210                """\
211Exec statement raises implicitly when determining locals argument.""",
212            )
213
214        trace_collection.onExceptionRaiseExit(BaseException)
215
216        str_value = self.subnode_source.getStrValue()
217
218        if str_value is not None:
219            # TODO: This needs to be re-done.
220            # TODO: Don't forget to consider side effects of source code,
221            # locals_arg, and globals_arg.
222            return self, None, None
223
224            # exec_body = ...
225            # return (exec_body, "new_statements", "In-lined constant exec statement.")
226
227        return self, None, None
228
229
230class StatementLocalsDictSync(StatementChildHavingBase):
231    kind = "STATEMENT_LOCALS_DICT_SYNC"
232
233    named_child = "locals_arg"
234
235    __slots__ = ("locals_scope", "previous_traces", "variable_traces")
236
237    def __init__(self, locals_scope, locals_arg, source_ref):
238        StatementChildHavingBase.__init__(self, value=locals_arg, source_ref=source_ref)
239
240        self.previous_traces = None
241        self.variable_traces = None
242
243        self.locals_scope = locals_scope
244
245    def getDetails(self):
246        return {"locals_scope": self.locals_scope}
247
248    def getPreviousVariablesTraces(self):
249        return self.previous_traces
250
251    def computeStatement(self, trace_collection):
252        result, change_tags, change_desc = self.computeStatementSubExpressions(
253            trace_collection=trace_collection
254        )
255
256        if result is not self:
257            return result, change_tags, change_desc
258
259        provider = self.getParentVariableProvider()
260        if provider.isCompiledPythonModule():
261            return None, "new_statements", "Removed sync back to locals without locals."
262
263        self.previous_traces = trace_collection.onLocalsUsage(self.locals_scope)
264        if not self.previous_traces:
265            return None, "new_statements", "Removed sync back to locals without locals."
266
267        trace_collection.removeAllKnowledge()
268        self.variable_traces = trace_collection.onLocalsUsage(self.locals_scope)
269
270        return self, None, None
271
272    @staticmethod
273    def mayRaiseException(exception_type):
274        return False
275
276
277class ExpressionBuiltinCompile(ExpressionChildrenHavingBase):
278    kind = "EXPRESSION_BUILTIN_COMPILE"
279
280    named_children = ("source", "filename", "mode", "flags", "dont_inherit", "optimize")
281
282    def __init__(
283        self, source_code, filename, mode, flags, dont_inherit, optimize, source_ref
284    ):
285        ExpressionChildrenHavingBase.__init__(
286            self,
287            values={
288                "source": source_code,
289                "filename": filename,
290                "mode": mode,
291                "flags": flags,
292                "dont_inherit": dont_inherit,
293                "optimize": optimize,
294            },
295            source_ref=source_ref,
296        )
297
298    def computeExpression(self, trace_collection):
299        trace_collection.onExceptionRaiseExit(BaseException)
300
301        # TODO: Attempt for constant values to do it.
302        return self, None, None
303