Skip to content

Commit 3c0970c

Browse files
committed
[GR-65704] Update java implementation of zlib module
PullRequest: graalpython/3840
2 parents a6d63c9 + 442877b commit 3c0970c

File tree

17 files changed

+908
-510
lines changed

17 files changed

+908
-510
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_zlib.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1-
# Copyright (c) 2018, 2024, Oracle and/or its affiliates.
1+
# Copyright (c) 2018, 2025, Oracle and/or its affiliates.
22
# Copyright (C) 1996-2017 Python Software Foundation
33
#
44
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
55

6+
try:
7+
__graalpython__.zlib_module_backend()
8+
except:
9+
class GP:
10+
def zlib_module_backend(self):
11+
return 'cpython'
12+
13+
def _disable_native_zlib(self, flag):
14+
return None
15+
__graalpython__ = GP()
16+
617
import os
718
import unittest
819
import zlib
@@ -241,3 +252,22 @@ def test_zlib_decompress_gzip():
241252
with open(GZ_PATH, 'rb') as f:
242253
data = d.decompress(f.read()) + d.flush()
243254
assert data == GZ_DATA
255+
256+
def test_GR65704():
257+
contents = b"The quick brown fox jumped over the lazy dog"
258+
wbits = 27
259+
260+
__graalpython__._disable_native_zlib(True)
261+
262+
compressed = zlib.compress(contents, wbits=wbits)
263+
decompressor = zlib.decompressobj(wbits=wbits)
264+
265+
decompressed = b''
266+
for b in compressed:
267+
out = decompressor.decompress(bytes([b]))
268+
decompressed += out
269+
decompressed += decompressor.flush()
270+
271+
__graalpython__._disable_native_zlib(False)
272+
273+
assert decompressed == contents

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,15 @@ TruffleString posixModuleBackend(
667667
}
668668
}
669669

670+
@Builtin(name = "zlib_module_backend", minNumOfPositionalArgs = 0)
671+
@GenerateNodeFactory
672+
public abstract static class ZlibModuleBackendNode extends PythonBuiltinNode {
673+
@Specialization
674+
TruffleString zlibModuleBackend() {
675+
return getContext().getNFIZlibSupport().isAvailable() ? T_NATIVE : T_JAVA;
676+
}
677+
}
678+
670679
@Builtin(name = "sha3_module_backend", minNumOfPositionalArgs = 0)
671680
@GenerateNodeFactory
672681
public abstract static class Sha3ModuleBackendNode extends PythonBuiltinNode {
@@ -1254,4 +1263,18 @@ static Object create(VirtualFrame frame, Object cls,
12541263
return objectNode.execute(frame, cls, PythonUtils.EMPTY_OBJECT_ARRAY, PKeyword.EMPTY_KEYWORDS);
12551264
}
12561265
}
1266+
1267+
@Builtin(name = "_disable_native_zlib", minNumOfPositionalArgs = 1)
1268+
@GenerateNodeFactory
1269+
abstract static class DisableNativeZlibNode extends PythonUnaryBuiltinNode {
1270+
@Specialization
1271+
Object disableNativeZlib(boolean disable) {
1272+
if (disable) {
1273+
getContext().getNFIZlibSupport().notAvailable();
1274+
} else {
1275+
getContext().getNFIZlibSupport().setAvailable();
1276+
}
1277+
return PNone.NONE;
1278+
}
1279+
}
12571280
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.modules.zlib;
42+
43+
import static com.oracle.graal.python.builtins.modules.zlib.ZLibModuleBuiltins.DEF_BUF_SIZE;
44+
import static com.oracle.graal.python.builtins.modules.zlib.ZLibModuleBuiltins.MAX_WBITS;
45+
import static com.oracle.graal.python.builtins.modules.zlib.ZLibModuleBuiltins.Z_FINISH;
46+
import static com.oracle.graal.python.builtins.modules.zlib.ZLibModuleBuiltins.Z_SYNC_FLUSH;
47+
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ZLibError;
48+
49+
import java.io.ByteArrayOutputStream;
50+
import java.io.IOException;
51+
import java.io.OutputStream;
52+
import java.util.zip.Deflater;
53+
import java.util.zip.GZIPOutputStream;
54+
import java.util.zip.ZipException;
55+
56+
import com.oracle.graal.python.PythonLanguage;
57+
import com.oracle.graal.python.nodes.PRaiseNode;
58+
import com.oracle.graal.python.runtime.object.PFactory;
59+
import com.oracle.truffle.api.CompilerDirectives;
60+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
61+
import com.oracle.truffle.api.nodes.Node;
62+
import com.oracle.truffle.api.object.Shape;
63+
64+
public final class JavaCompress extends JavaZlibCompObject {
65+
66+
private static class GZIPCompressStream extends GZIPOutputStream {
67+
68+
public GZIPCompressStream(OutputStream out) throws IOException {
69+
super(out);
70+
}
71+
72+
public Deflater getDeflater() {
73+
return def;
74+
}
75+
}
76+
77+
private record CompressStream(Deflater deflater, ByteArrayOutputStream out, GZIPCompressStream stream) {
78+
}
79+
80+
@TruffleBoundary
81+
private static CompressStream createStream(int level, int wbits) {
82+
Deflater def;
83+
GZIPCompressStream stream = null;
84+
ByteArrayOutputStream out = null;
85+
if (wbits > (MAX_WBITS + 9) && wbits <= (MAX_WBITS + 16)) {
86+
// wbits 25..31: gzip container with wrapping
87+
try {
88+
out = new ByteArrayOutputStream();
89+
stream = new GZIPCompressStream(out);
90+
def = stream.getDeflater();
91+
def.setLevel(level);
92+
} catch (IOException e) {
93+
throw new RuntimeException(e);
94+
}
95+
} else {
96+
// wbits < 0: generate a RAW stream, i.e., no wrapping
97+
// Otherwise: wrap stream with zlib header and trailer
98+
def = new Deflater(level, wbits < 0);
99+
}
100+
return new CompressStream(def, out, stream);
101+
}
102+
103+
final CompressStream stream;
104+
final int level;
105+
final int strategy;
106+
107+
public JavaCompress(Object cls, Shape instanceShape, int level, int wbits, int strategy, byte[] zdict) {
108+
super(cls, instanceShape, wbits, zdict);
109+
this.stream = JavaCompress.createStream(level, wbits);
110+
this.level = level;
111+
this.strategy = strategy;
112+
}
113+
114+
@TruffleBoundary
115+
protected void setDeflaterInput(byte[] data, int length) {
116+
canCopy = inputData == null;
117+
inputData = data;
118+
inputLen = length;
119+
stream.deflater.setInput(data, 0, length);
120+
}
121+
122+
@TruffleBoundary
123+
protected ZLibCompObject copy() {
124+
assert canCopy();
125+
JavaCompress obj = PFactory.createJavaZLibCompObjectCompress(PythonLanguage.get(null), level, wbits, strategy, zdict);
126+
Deflater deflater = obj.stream.deflater();
127+
128+
obj.setStrategy();
129+
obj.setDictionary();
130+
if (inputData != null) {
131+
// feed the new copy of deflater the same input data
132+
obj.setDeflaterInput(inputData, inputLen);
133+
deflater.deflate(new byte[inputLen]);
134+
}
135+
return obj;
136+
}
137+
138+
@TruffleBoundary
139+
protected void setStrategy() {
140+
stream.deflater.setStrategy(strategy);
141+
}
142+
143+
protected void setDictionary() {
144+
if (getZdict().length > 0) {
145+
stream.deflater.setDictionary(getZdict());
146+
}
147+
}
148+
149+
private static void compress(CompressStream stream, ByteArrayOutputStream out, int mode) {
150+
byte[] result = new byte[DEF_BUF_SIZE];
151+
int bytesWritten = result.length;
152+
while (bytesWritten == result.length) {
153+
bytesWritten = stream.deflater.deflate(result, 0, result.length, mode);
154+
out.write(result, 0, bytesWritten);
155+
}
156+
}
157+
158+
@TruffleBoundary
159+
protected byte[] compress(int mode) {
160+
if (mode == Z_FINISH) {
161+
return compressFinish();
162+
}
163+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
164+
compress(stream, baos, mode);
165+
return baos.toByteArray();
166+
}
167+
168+
@TruffleBoundary
169+
private static void compressFinish(CompressStream stream, ByteArrayOutputStream out) {
170+
stream.deflater.finish();
171+
compress(stream, out, Z_SYNC_FLUSH);
172+
stream.deflater.end();
173+
}
174+
175+
@TruffleBoundary
176+
private byte[] compressFinish() {
177+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
178+
compressFinish(stream, baos);
179+
setUninitialized();
180+
return baos.toByteArray();
181+
}
182+
183+
@TruffleBoundary
184+
protected static byte[] compressFinish(byte[] bytes, int length, int level, int wbits, Node node) {
185+
CompressStream stream = createStream(level, wbits);
186+
stream.deflater.setInput(bytes, 0, length);
187+
if (stream.stream != null) {
188+
try {
189+
stream.stream.finish();
190+
return stream.out.toByteArray();
191+
} catch (ZipException ze) {
192+
throw PRaiseNode.raiseStatic(node, ZLibError, ze);
193+
} catch (IOException e) {
194+
throw CompilerDirectives.shouldNotReachHere(e);
195+
}
196+
}
197+
Deflater compresser = stream.deflater();
198+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
199+
compresser.setInput(bytes, 0, length);
200+
compresser.finish();
201+
byte[] resultArray = new byte[DEF_BUF_SIZE];
202+
while (!compresser.finished()) {
203+
int howmany = compresser.deflate(resultArray);
204+
baos.write(resultArray, 0, howmany);
205+
}
206+
compresser.end();
207+
return baos.toByteArray();
208+
209+
}
210+
}

0 commit comments

Comments
 (0)