Skip to content

Commit 1174bf9

Browse files
committed
Fix updating digest after its retrieval
1 parent 84e0378 commit 1174bf9

File tree

3 files changed

+89
-23
lines changed

3 files changed

+89
-23
lines changed

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

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
*/
6464
public abstract class DigestObject extends PythonBuiltinObject {
6565
private final String name;
66-
private byte[] finalDigest = null;
6766

6867
DigestObject(Object cls, Shape instanceShape, String name) {
6968
super(cls, instanceShape);
@@ -178,26 +177,82 @@ private PythonBuiltinClassType determineMainDigestType() {
178177
}
179178
}
180179

181-
byte[] digest() {
182-
if (finalDigest == null) {
183-
finalDigest = finalizeDigest();
184-
}
185-
return finalDigest;
186-
}
180+
/**
181+
* CPython supports updating after retrieving the digest but the JDK does not. We have to
182+
* calculate the digest on a clone, but that does not need to be supported. If it is not, then
183+
* we calculate the digest normally, but we must prevent any further updates.
184+
*
185+
* @see #wasReset(), {@link #update(byte[])}
186+
*/
187+
abstract byte[] digest();
187188

188-
abstract DigestObject copy(PythonObjectFactory factory) throws CloneNotSupportedException;
189-
190-
abstract byte[] finalizeDigest();
189+
/**
190+
* @return true if the digest has already been calculated and the underlying implementation does
191+
* not support cloning, in which case this object can no longer be
192+
* {@linkplain #update(byte[]) updated}
193+
*/
194+
abstract boolean wasReset();
191195

196+
/**
197+
* Must not be called if {@link #wasReset()} returns true.
198+
*/
192199
abstract void update(byte[] data);
193200

201+
abstract DigestObject copy(PythonObjectFactory factory) throws CloneNotSupportedException;
202+
194203
abstract int getDigestLength();
195204

196205
final String getAlgorithm() {
197206
return name;
198207
}
199208

200-
private static final class MessageDigestObject extends DigestObject {
209+
/**
210+
* Ensures that {@link #update(byte[])} is not called after {@link #digest()} if cloning is not
211+
* supported. Also caches the digest and ensures that the cache is cleared on update.
212+
*/
213+
private static abstract class DigestObjectBase extends DigestObject {
214+
private byte[] cachedDigest = null;
215+
private boolean wasReset;
216+
217+
DigestObjectBase(Object cls, Shape instanceShape, String name) {
218+
super(cls, instanceShape, name);
219+
}
220+
221+
@Override
222+
final boolean wasReset() {
223+
return wasReset;
224+
}
225+
226+
@Override
227+
final byte[] digest() {
228+
if (cachedDigest == null) {
229+
try {
230+
cachedDigest = calculateDigestOnClone();
231+
} catch (CloneNotSupportedException e) {
232+
wasReset = true;
233+
cachedDigest = calculateDigest();
234+
}
235+
}
236+
return cachedDigest;
237+
}
238+
239+
@Override
240+
final void update(byte[] data) {
241+
if (wasReset) {
242+
throw CompilerDirectives.shouldNotReachHere("update() called after digest() on an implementation the does not support clone()");
243+
}
244+
cachedDigest = null;
245+
doUpdate(data);
246+
}
247+
248+
abstract byte[] calculateDigestOnClone() throws CloneNotSupportedException;
249+
250+
abstract byte[] calculateDigest();
251+
252+
abstract void doUpdate(byte[] data);
253+
}
254+
255+
private static final class MessageDigestObject extends DigestObjectBase {
201256
private final MessageDigest digest;
202257

203258
MessageDigestObject(PythonBuiltinClassType digestType, Shape instanceShape, String name, MessageDigest digest) {
@@ -213,19 +268,19 @@ DigestObject copy(PythonObjectFactory factory) throws CloneNotSupportedException
213268

214269
@Override
215270
@TruffleBoundary
216-
byte[] finalizeDigest() {
217-
try {
218-
// Try to finalize a clone, because CPython supports updating
219-
// after retrieving the digest and the JDK does not.
220-
return ((MessageDigest) digest.clone()).digest();
221-
} catch (CloneNotSupportedException e) {
222-
return digest.digest();
223-
}
271+
byte[] calculateDigestOnClone() throws CloneNotSupportedException {
272+
return ((MessageDigest) digest.clone()).digest();
224273
}
225274

226275
@Override
227276
@TruffleBoundary
228-
void update(byte[] data) {
277+
byte[] calculateDigest() {
278+
return digest.digest();
279+
}
280+
281+
@Override
282+
@TruffleBoundary
283+
void doUpdate(byte[] data) {
229284
digest.update(data);
230285
}
231286

@@ -236,7 +291,7 @@ int getDigestLength() {
236291
}
237292
}
238293

239-
private static final class MacDigestObject extends DigestObject {
294+
private static final class MacDigestObject extends DigestObjectBase {
240295
private final Mac mac;
241296

242297
MacDigestObject(PythonBuiltinClassType digestType, Shape instanceShape, String name, Mac mac) {
@@ -252,13 +307,19 @@ DigestObject copy(PythonObjectFactory factory) throws CloneNotSupportedException
252307

253308
@Override
254309
@TruffleBoundary
255-
byte[] finalizeDigest() {
310+
byte[] calculateDigestOnClone() throws CloneNotSupportedException {
311+
return ((Mac) mac.clone()).doFinal();
312+
}
313+
314+
@Override
315+
@TruffleBoundary
316+
byte[] calculateDigest() {
256317
return mac.doFinal();
257318
}
258319

259320
@Override
260321
@TruffleBoundary
261-
void update(byte[] data) {
322+
void doUpdate(byte[] data) {
262323
mac.update(data);
263324
}
264325

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary;
5353
import com.oracle.graal.python.builtins.objects.bytes.BytesNodes;
5454
import com.oracle.graal.python.builtins.objects.bytes.PBytes;
55+
import com.oracle.graal.python.nodes.ErrorMessages;
5556
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
5657
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryClinicBuiltinNode;
5758
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
@@ -122,6 +123,9 @@ protected ArgumentClinicProvider getArgumentClinic() {
122123
@Specialization(limit = "3")
123124
PNone update(VirtualFrame frame, DigestObject self, Object buffer,
124125
@CachedLibrary("buffer") PythonBufferAccessLibrary bufferLib) {
126+
if (self.wasReset()) {
127+
raise(PythonBuiltinClassType.ValueError, ErrorMessages.UPDATING_FINALIZED_DIGEST_IS_NOT_SUPPORTED);
128+
}
125129
try {
126130
self.update(bufferLib.getInternalOrCopiedByteArray(buffer));
127131
} finally {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,7 @@ public abstract class ErrorMessages {
784784
public static final TruffleString UNSUPPORTED_STR_TYPE = tsLiteral("unsupported string type: %s");
785785
public static final TruffleString UNSUPPORTED_TARGET_SIZE = tsLiteral("Unsupported target size: %d");
786786
public static final TruffleString UNSUPPORTED_USE_OF_SYS_EXECUTABLE = tsLiteral("internal error: unsupported use of sys.executable");
787+
public static final TruffleString UPDATING_FINALIZED_DIGEST_IS_NOT_SUPPORTED = tsLiteral("internal error: updating a finalized digest is not supported");
787788
public static final TruffleString UTIME_CANNOT_USE_DIR_FD_AND_FOLLOW_SYMLINKS = tsLiteral("utime: cannot use dir_fd and follow_symlinks together on this platform");
788789
public static final TruffleString VALUE_TOO_LARGE_TO_FIT_INTO_INDEX = tsLiteral("value too large to fit into index-sized integer");
789790
public static final TruffleString VARS_ARGUMENT_MUST_HAVE_DICT = tsLiteral("vars() argument must have __dict__ attribute");

0 commit comments

Comments
 (0)