Skip to content

Commit fac8559

Browse files
committed
Python: Serialize large integers as strings in RPC to avoid Jackson limits
Jackson 2.15+ rejects JSON numbers exceeding 1000 digits via StreamReadConstraints.getMaxNumberLength(). Python integers have no size limit, so a literal like paramiko's 4096-bit DH prime (1234 decimal digits) was serialized as a raw JSON number, causing deserialization to fail on the Java side. Convert integers exceeding Java's long range to strings before serialization. The original source representation is preserved in the literal's valueSource field, so printing is unaffected.
1 parent 78d3500 commit fac8559

2 files changed

Lines changed: 83 additions & 1 deletion

File tree

rewrite-python/rewrite/src/rewrite/rpc/send_queue.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,14 @@ def _get_primitive_value(self, obj: Any) -> Any:
316316
return None
317317
if isinstance(obj, bool):
318318
return obj
319-
if isinstance(obj, (int, str)):
319+
if isinstance(obj, int):
320+
# Integers exceeding Java's long range cannot be serialized as
321+
# JSON numbers (Jackson's StreamReadConstraints rejects them).
322+
# Convert to string — the original source is preserved in valueSource.
323+
if obj > 9223372036854775807 or obj < -9223372036854775808:
324+
return str(obj)
325+
return obj
326+
if isinstance(obj, str):
320327
return obj
321328
if isinstance(obj, float):
322329
# Special float values (inf, nan) are not valid JSON
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Copyright 2025 the original author or authors.
2+
# <p>
3+
# Licensed under the Moderne Source Available License (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
# <p>
7+
# https://docs.moderne.io/licensing/moderne-source-available-license
8+
# <p>
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for parsing files with large literals (e.g. paramiko/kex_group16.py)."""
16+
17+
import ast
18+
import json
19+
import time
20+
21+
from rewrite.python._parser_visitor import ParserVisitor
22+
from rewrite.rpc.send_queue import RpcSendQueue
23+
24+
25+
SOURCE = """\
26+
from hashlib import sha512
27+
28+
29+
class KexGroup16SHA512:
30+
name = "diffie-hellman-group16-sha512"
31+
# http://tools.ietf.org/html/rfc3526#section-5
32+
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF # noqa
33+
G = 2
34+
35+
name = "diffie-hellman-group16-sha512"
36+
hash_algo = sha512
37+
"""
38+
39+
40+
class TestLargeLiteralSerialization:
41+
42+
def test_serialize_large_hex_literal(self):
43+
"""Time each step: parse, RPC serialize, JSON encode."""
44+
45+
# Step 1: Parse
46+
t0 = time.perf_counter()
47+
tree = ast.parse(SOURCE, "kex_group16.py")
48+
cu = ParserVisitor(SOURCE, "kex_group16.py").visit(tree)
49+
t1 = time.perf_counter()
50+
print(f"\nParse: {t1 - t0:.4f}s")
51+
52+
# Step 2: RPC serialize (generate RpcObjectData list)
53+
q = RpcSendQueue(source_file_type="org.openrewrite.python.tree.Py$CompilationUnit")
54+
t2 = time.perf_counter()
55+
rpc_data = q.generate(cu)
56+
t3 = time.perf_counter()
57+
print(f"RPC serialize: {t3 - t2:.4f}s ({len(rpc_data)} messages)")
58+
59+
# Step 3: JSON encode
60+
t4 = time.perf_counter()
61+
json_bytes = json.dumps(rpc_data)
62+
t5 = time.perf_counter()
63+
print(f"JSON encode: {t5 - t4:.4f}s ({len(json_bytes)} bytes)")
64+
65+
# Total
66+
print(f"Total: {t5 - t0:.4f}s")
67+
68+
# Verify the large int was serialized as a string, not a JSON number.
69+
# There should be no JSON numbers exceeding 19 digits in the output.
70+
data = json.loads(json_bytes)
71+
for msg in data:
72+
v = msg.get('value')
73+
if isinstance(v, int):
74+
assert -9223372036854775808 <= v <= 9223372036854775807, \
75+
f"Found int value exceeding Java long range: {v}"

0 commit comments

Comments
 (0)