Skip to content

Commit 8804ae2

Browse files
committed
Intrinsify __signature__
Fixes issue with Django, which calls `inspect(hashlib.md5)`, which failed in the previous implementation of __signature__ when deparsing keyword arguments.
1 parent fa0e33e commit 8804ae2

File tree

17 files changed

+231
-96
lines changed

17 files changed

+231
-96
lines changed

graalpython/com.oracle.graal.python.frozen/freeze_modules.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ def add_graalpython_core():
117117
"_sysconfig",
118118
"_weakref",
119119
"builtins",
120-
"function",
121120
"java",
122121
"pip_hook",
123122
"unicodedata",
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
import inspect
40+
import re
41+
from inspect import Parameter
42+
import hashlib
43+
import posix
44+
45+
46+
TEST_CASES = [
47+
(SystemExit.__init__, '($self, /, *args, **kwargs)',
48+
[('self', Parameter.POSITIONAL_ONLY),
49+
('args', Parameter.VAR_POSITIONAL),
50+
('kwargs', Parameter.VAR_KEYWORD)]),
51+
(list.append, '($self, object, /)',
52+
[('self', Parameter.POSITIONAL_ONLY),
53+
('object', Parameter.POSITIONAL_ONLY)]),
54+
(hashlib.md5, '($module, /, string=b\'\', *, usedforsecurity=True)',
55+
[('string', Parameter.POSITIONAL_OR_KEYWORD),
56+
('usedforsecurity', Parameter.KEYWORD_ONLY)]),
57+
(posix.open, '($module, /, path, flags, mode=511, *, dir_fd=None)',
58+
[('path', Parameter.POSITIONAL_OR_KEYWORD),
59+
('flags', Parameter.POSITIONAL_OR_KEYWORD),
60+
('mode', Parameter.POSITIONAL_OR_KEYWORD),
61+
('dir_fd', Parameter.KEYWORD_ONLY)]),
62+
(abs, '($module, x, /)',
63+
[('x', Parameter.POSITIONAL_ONLY)]),
64+
(pow, '($module, /, base, exp, mod=None)',
65+
[('base', Parameter.POSITIONAL_OR_KEYWORD),
66+
('exp', Parameter.POSITIONAL_OR_KEYWORD),
67+
('mod', Parameter.POSITIONAL_OR_KEYWORD)]),
68+
]
69+
70+
71+
def normalize_signature_text(text):
72+
# For the time being:
73+
# GraalPy does not distinguish $self and $module
74+
# GraalPy does not print default values into the signature text
75+
return re.sub(r'=[^,)]*', '', text.replace("=()", "")).replace("$module", "$self")
76+
77+
78+
def test_inspect_signature():
79+
for (fun, expected_signature, expected_params) in TEST_CASES:
80+
actual = inspect.signature(fun)
81+
actual_params = [(p.name, p.kind) for p in actual.parameters.values()]
82+
assert actual_params == expected_params, f"{expected_params !r}\nactual:{actual_params}\nexpect:{expected_params}"
83+
assert normalize_signature_text(fun.__text_signature__) == normalize_signature_text(expected_signature)
84+
85+
86+
# def _create_test_cases(funs):
87+
# for funId in funs:
88+
# fun = eval(funId)
89+
# signature = inspect.signature(fun)
90+
# params = [f"('{p.name}', Parameter.{p.kind})" for p in signature.parameters.values()]
91+
# params = ',\n '.join(params)
92+
# text_signature = fun.__text_signature__.replace("'", "\\'")
93+
# print(f"({funId}, '{text_signature}',\n [{params}]),")
94+
#
95+
#
96+
# _create_test_cases([
97+
# 'SystemExit.__init__',
98+
# 'hashlib.md5',
99+
# 'posix.open',
100+
# 'abs',
101+
# 'list',
102+
# 'pow'])
103+

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,6 @@ private static TruffleString[] initializeCoreFiles() {
406406
toTruffleStringUncached("_weakref"),
407407
toTruffleStringUncached("unicodedata"),
408408
toTruffleStringUncached("_sre"),
409-
toTruffleStringUncached("function"),
410409
toTruffleStringUncached("_sysconfig"),
411410
toTruffleStringUncached("java"),
412411
toTruffleStringUncached("pip_hook"),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ public void postInitialize(Python3Core core) {
327327
}
328328

329329
// abs(x)
330-
@Builtin(name = J_ABS, minNumOfPositionalArgs = 1)
330+
@Builtin(name = J_ABS, minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = "x")
331331
@GenerateNodeFactory
332332
public abstract static class AbsNode extends PythonUnaryBuiltinNode {
333333
@Specialization
@@ -2159,7 +2159,7 @@ private int getDebuggerSessionCount() {
21592159
}
21602160
}
21612161

2162-
@Builtin(name = J_POW, minNumOfPositionalArgs = 2, parameterNames = {"base", "exp", "mod"})
2162+
@Builtin(name = J_POW, minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 0, parameterNames = {"base", "exp", "mod"})
21632163
@GenerateNodeFactory
21642164
public abstract static class PowNode extends PythonTernaryBuiltinNode {
21652165
@NeverDefault

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixModuleBuiltins.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ Object openpty(VirtualFrame frame,
817817
}
818818
}
819819

820-
@Builtin(name = "open", minNumOfPositionalArgs = 2, parameterNames = {"path", "flags", "mode"}, keywordOnlyNames = {"dir_fd"})
820+
@Builtin(name = "open", numOfPositionalOnlyArgs = 0, minNumOfPositionalArgs = 2, parameterNames = {"path", "flags", "mode"}, keywordOnlyNames = {"dir_fd"})
821821
@ArgumentClinic(name = "path", conversionClass = PathConversionNode.class, args = {"false", "false"})
822822
@ArgumentClinic(name = "flags", conversion = ClinicConversion.Int)
823823
@ArgumentClinic(name = "mode", conversion = ClinicConversion.Int, defaultValue = "0777")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/hashlib/Md5ModuleBuiltins.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -65,7 +65,7 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
6565
return Md5ModuleBuiltinsFactory.getFactories();
6666
}
6767

68-
@Builtin(name = "md5", minNumOfPositionalArgs = 0, parameterNames = {"string"}, keywordOnlyNames = {"usedforsecurity"})
68+
@Builtin(name = "md5", numOfPositionalOnlyArgs = 0, parameterNames = {"string"}, keywordOnlyNames = {"usedforsecurity"})
6969
@GenerateNodeFactory
7070
abstract static class Md5FunctionNode extends PythonBuiltinNode {
7171
@Specialization

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/json/JSONEncoderBuiltins.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
6666
import com.oracle.graal.python.runtime.sequence.PSequence;
6767
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
68+
import com.oracle.graal.python.util.PythonUtils;
6869
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
6970
import com.oracle.truffle.api.dsl.Cached;
7071
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
@@ -113,7 +114,7 @@ protected PTuple call(PJSONEncoder self, Object obj, @SuppressWarnings("unused")
113114

114115
@TruffleBoundary
115116
private TruffleString jsonEncode(PJSONEncoder encoder, Object obj) {
116-
TruffleStringBuilderUTF32 builder = TruffleStringBuilder.createUTF32();
117+
TruffleStringBuilderUTF32 builder = PythonUtils.createStringBuilder();
117118
appendListObj(encoder, builder, obj);
118119
return TruffleStringBuilder.ToStringNode.getUncached().execute(builder);
119120
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/SystemExitBuiltins.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -80,7 +80,7 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
8080
return new Object[]{code};
8181
};
8282

83-
@Builtin(name = J___INIT__, minNumOfPositionalArgs = 1, takesVarArgs = true)
83+
@Builtin(name = J___INIT__, minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true)
8484
@GenerateNodeFactory
8585
public abstract static class InitNode extends PythonBuiltinNode {
8686
@Specialization

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/AbstractFunctionBuiltins.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2023, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
33
* Copyright (c) 2014, Regents of the University of California
44
*
55
* All rights reserved.
@@ -45,7 +45,6 @@
4545
import static com.oracle.graal.python.runtime.exception.PythonErrorType.AttributeError;
4646
import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError;
4747
import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING;
48-
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;
4948

5049
import java.util.List;
5150

@@ -61,6 +60,7 @@
6160
import com.oracle.graal.python.lib.PyObjectGetItem;
6261
import com.oracle.graal.python.nodes.ErrorMessages;
6362
import com.oracle.graal.python.nodes.PRaiseNode;
63+
import com.oracle.graal.python.nodes.StringLiterals;
6464
import com.oracle.graal.python.nodes.argument.CreateArgumentsNode;
6565
import com.oracle.graal.python.nodes.attributes.ReadAttributeFromObjectNode;
6666
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
@@ -72,6 +72,7 @@
7272
import com.oracle.graal.python.nodes.object.SetDictNode;
7373
import com.oracle.graal.python.runtime.exception.PException;
7474
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
75+
import com.oracle.graal.python.util.PythonUtils;
7576
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
7677
import com.oracle.truffle.api.dsl.Bind;
7778
import com.oracle.truffle.api.dsl.Cached;
@@ -85,6 +86,7 @@
8586
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
8687
import com.oracle.truffle.api.strings.TruffleString;
8788
import com.oracle.truffle.api.strings.TruffleStringBuilder;
89+
import com.oracle.truffle.api.strings.TruffleStringBuilderUTF32;
8890

8991
@CoreFunctions(extendClasses = {PythonBuiltinClassType.PFunction, PythonBuiltinClassType.PBuiltinFunction, PythonBuiltinClassType.WrapperDescriptor})
9092
public final class AbstractFunctionBuiltins extends PythonBuiltins {
@@ -279,9 +281,6 @@ static Object builtinCode(PBuiltinFunction self, Object mapping,
279281
@GenerateNodeFactory
280282
public abstract static class TextSignatureNode extends PythonBinaryBuiltinNode {
281283

282-
private static final TruffleString ARGS = tsLiteral("*args");
283-
private static final TruffleString KWARGS = tsLiteral("**kwargs");
284-
285284
@Specialization(guards = {"!isBuiltinFunction(self)", "isNoValue(none)"})
286285
static Object getFunction(PFunction self, @SuppressWarnings("unused") PNone none,
287286
@Bind("this") Node inliningTarget,
@@ -315,7 +314,7 @@ public static TruffleString signatureToText(Signature signature, boolean skipSel
315314

316315
TruffleString[] parameterNames = signature.getParameterIds();
317316

318-
TruffleStringBuilder sb = TruffleStringBuilder.create(TS_ENCODING);
317+
TruffleStringBuilderUTF32 sb = PythonUtils.createStringBuilder();
319318
sb.appendStringUncached(T_LPAREN);
320319
boolean first = true;
321320
for (int i = 0; i < parameterNames.length; i++) {
@@ -339,7 +338,7 @@ public static TruffleString signatureToText(Signature signature, boolean skipSel
339338
}
340339
if (takesVarArgs) {
341340
first = appendCommaIfNeeded(sb, first);
342-
sb.appendStringUncached(ARGS);
341+
sb.appendStringUncached(StringLiterals.T_STAR_ARGS);
343342
}
344343
if (keywordNames.length > 0) {
345344
if (!takesVarArgs) {
@@ -355,7 +354,7 @@ public static TruffleString signatureToText(Signature signature, boolean skipSel
355354
}
356355
if (takesVarKeywordArgs) {
357356
appendCommaIfNeeded(sb, first);
358-
sb.appendStringUncached(KWARGS);
357+
sb.appendStringUncached(StringLiterals.T_STAR_KWARGS);
359358
}
360359
sb.appendStringUncached(T_RPAREN);
361360
return sb.toStringUncached();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/BuiltinFunctionBuiltins.java

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2023, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
33
* Copyright (c) 2014, Regents of the University of California
44
*
55
* All rights reserved.
@@ -29,9 +29,14 @@
2929
import static com.oracle.graal.python.nodes.BuiltinNames.T_GETATTR;
3030
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___NAME__;
3131
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___QUALNAME__;
32+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.J___SIGNATURE__;
3233
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___OBJCLASS__;
3334
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___REDUCE__;
35+
import static com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode.T_DOLLAR_DECL_TYPE;
36+
import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING;
37+
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;
3438

39+
import java.util.ArrayList;
3540
import java.util.List;
3641

3742
import com.oracle.graal.python.builtins.Builtin;
@@ -44,12 +49,17 @@
4449
import com.oracle.graal.python.lib.PyObjectGetAttr;
4550
import com.oracle.graal.python.nodes.ErrorMessages;
4651
import com.oracle.graal.python.nodes.PRaiseNode;
52+
import com.oracle.graal.python.nodes.StringLiterals;
53+
import com.oracle.graal.python.nodes.bytecode.ImportNode;
54+
import com.oracle.graal.python.nodes.call.CallNode;
4755
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
4856
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
4957
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
5058
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
5159
import com.oracle.graal.python.runtime.exception.PythonErrorType;
5260
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
61+
import com.oracle.graal.python.util.PythonUtils;
62+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
5363
import com.oracle.truffle.api.dsl.Bind;
5464
import com.oracle.truffle.api.dsl.Cached;
5565
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
@@ -128,4 +138,76 @@ Object doBuiltinFunc(VirtualFrame frame, PBuiltinFunction func,
128138
return factory.createTuple(new Object[]{getattr, args});
129139
}
130140
}
141+
142+
@Builtin(name = J___SIGNATURE__, minNumOfPositionalArgs = 1, isGetter = true)
143+
@GenerateNodeFactory
144+
public abstract static class SignatureNode extends PythonUnaryBuiltinNode {
145+
146+
@Specialization
147+
public Object doIt(PBuiltinFunction fun) {
148+
return createInspectSignagure(fun.getSignature(), false);
149+
}
150+
151+
private enum ParameterKinds {
152+
POSITIONAL_ONLY,
153+
POSITIONAL_OR_KEYWORD,
154+
VAR_POSITIONAL,
155+
KEYWORD_ONLY,
156+
VAR_KEYWORD;
157+
158+
static final ParameterKinds[] VALUES = values();
159+
160+
Object get(Object[] kinds, Object inspectParameter) {
161+
if (kinds[ordinal()] == null) {
162+
kinds[ordinal()] = PyObjectGetAttr.executeUncached(inspectParameter, PythonUtils.toTruffleStringUncached(name()));
163+
}
164+
return kinds[ordinal()];
165+
}
166+
}
167+
168+
@TruffleBoundary
169+
public static Object createInspectSignagure(Signature signature, boolean skipSelf) {
170+
PythonModule inspect = ImportNode.importModule(tsLiteral("inspect"));
171+
Object inspectSignature = PyObjectGetAttr.executeUncached(inspect, tsLiteral("Signature"));
172+
Object inspectParameter = PyObjectGetAttr.executeUncached(inspect, tsLiteral("Parameter"));
173+
Object[] parameterKinds = new Object[ParameterKinds.VALUES.length];
174+
175+
TruffleString[] keywordNames = signature.getKeywordNames();
176+
boolean takesVarArgs = signature.takesVarArgs();
177+
boolean takesVarKeywordArgs = signature.takesVarKeywordArgs();
178+
TruffleString[] parameterNames = signature.getParameterIds();
179+
180+
Object kind = ParameterKinds.POSITIONAL_ONLY.get(parameterKinds, inspectParameter);
181+
ArrayList<Object> parameters = new ArrayList<>();
182+
CallNode callNode = CallNode.getUncached();
183+
for (int i = 0; i < parameterNames.length; i++) {
184+
if (i == 0 && T_DOLLAR_DECL_TYPE.equalsUncached(parameterNames[i], TS_ENCODING)) {
185+
continue;
186+
}
187+
if (skipSelf) {
188+
skipSelf = false;
189+
continue;
190+
}
191+
if (signature.getPositionalOnlyArgIndex() == i) {
192+
kind = ParameterKinds.POSITIONAL_OR_KEYWORD.get(parameterKinds, inspectParameter);
193+
}
194+
TruffleString name = parameterNames[i];
195+
if (name.codePointAtIndexUncached(0, TS_ENCODING) == '$') {
196+
name = name.substringUncached(1, name.codePointLengthUncached(TS_ENCODING) - 1, TS_ENCODING, true);
197+
}
198+
parameters.add(callNode.execute(inspectParameter, name, kind));
199+
}
200+
if (takesVarArgs) {
201+
parameters.add(callNode.execute(inspectParameter, StringLiterals.T_ARGS, ParameterKinds.VAR_POSITIONAL.get(parameterKinds, inspectParameter)));
202+
}
203+
for (TruffleString keywordName : keywordNames) {
204+
parameters.add(callNode.execute(inspectParameter, keywordName, ParameterKinds.KEYWORD_ONLY.get(parameterKinds, inspectParameter)));
205+
}
206+
if (takesVarKeywordArgs) {
207+
parameters.add(callNode.execute(inspectParameter, StringLiterals.T_KWARGS, ParameterKinds.VAR_KEYWORD.get(parameterKinds, inspectParameter)));
208+
}
209+
210+
return callNode.execute(inspectSignature, PythonObjectFactory.getUncached().createTuple(parameters.toArray()));
211+
}
212+
}
131213
}

0 commit comments

Comments
 (0)