Skip to content

Commit e600869

Browse files
committed
[GR-26329] Fix: properly convert RHS when writing slice into byte array.
PullRequest: graalpython/1299
2 parents bc4ebcc + 44a5c7d commit e600869

File tree

4 files changed

+91
-20
lines changed

4 files changed

+91
-20
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ def test_setslice():
239239
b[5:10] = [4, 5, 6, 7, 8]
240240
assert b == bytearray(b"hello\x04\x05\x06\x07\x08hello")
241241

242+
# assign list with integers backed by generic storage
243+
b = bytearray(b"hellohellohello")
244+
rhs = ['hello', 5, 6, 7, 8]
245+
rhs[0] = 4
246+
b[5:10] = rhs
247+
assert b == bytearray(b"hello\x04\x05\x06\x07\x08hello")
248+
242249
# assign range
243250
b = bytearray(b"hellohellohello")
244251
b[5:10] = range(5)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/ByteArrayBuiltins.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
5757
import com.oracle.graal.python.builtins.objects.function.PArguments;
5858
import com.oracle.graal.python.builtins.objects.function.PKeyword;
59+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.ByteArrayGeneralizationNode;
5960
import com.oracle.graal.python.builtins.objects.iterator.IteratorNodes;
6061
import com.oracle.graal.python.builtins.objects.memoryview.PMemoryView;
6162
import com.oracle.graal.python.builtins.objects.object.PythonObjectLibrary;
@@ -231,11 +232,15 @@ Object error(Object self, Object idx, Object value) {
231232
}
232233

233234
protected SequenceStorageNodes.SetItemNode createSetItem() {
235+
// Note the error message should never be reached, because the storage should always be
236+
// writeable and so SetItemScalarNode should always have a specialization for it and
237+
// inside that specialization the conversion of RHS may fail and produce Python level
238+
// ValueError
234239
return SequenceStorageNodes.SetItemNode.create(NormalizeIndexNode.forBytearray(), "an integer is required");
235240
}
236241

237242
protected SequenceStorageNodes.SetItemNode createSetSlice() {
238-
return SequenceStorageNodes.SetItemNode.create(NormalizeIndexNode.forBytearray(), "can assign only bytes, buffers, or iterables of ints in range(0, 256)");
243+
return SequenceStorageNodes.SetItemNode.create(NormalizeIndexNode.forBytearray(), ByteArrayGeneralizationNode.CACHED_SUPPLIER);
239244
}
240245

241246
protected static boolean isMemoryView(Object value) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/SequenceStorageNodes.java

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,12 +1140,12 @@ protected SequenceStorage doSliceSequence(SequenceStorage storage, PSlice slice,
11401140
int len = lenNode.execute(storage);
11411141
SliceInfo info = adjustIndices.execute(len, unadjusted);
11421142
try {
1143-
setItemSliceNode.execute(storage, info, sequence);
1143+
setItemSliceNode.execute(storage, info, sequence, true);
11441144
return storage;
11451145
} catch (SequenceStoreException e) {
11461146
generalizeProfile.enter();
11471147
SequenceStorage generalized = generalizeStore(storage, e.getIndicationValue());
1148-
setItemSliceNode.execute(generalized, info, sequence);
1148+
setItemSliceNode.execute(generalized, info, sequence, false);
11491149
return generalized;
11501150
}
11511151
}
@@ -1167,12 +1167,12 @@ protected SequenceStorage doSliceGeneric(SequenceStorage storage, PSlice slice,
11671167
// must not use iterable again. It could have sice-effects.
11681168
PList values = constructListNode.execute(iterable);
11691169
try {
1170-
setItemSliceNode.execute(storage, info, values);
1170+
setItemSliceNode.execute(storage, info, values, true);
11711171
return storage;
11721172
} catch (SequenceStoreException e) {
11731173
generalizeProfile.enter();
11741174
SequenceStorage generalized = generalizeStore(storage, e.getIndicationValue());
1175-
setItemSliceNode.execute(generalized, info, values);
1175+
setItemSliceNode.execute(generalized, info, values, false);
11761176
return generalized;
11771177
}
11781178
}
@@ -1321,21 +1321,25 @@ public static SetItemScalarNode create() {
13211321
@ImportStatic({ListStorageType.class, SequenceStorageBaseNode.class})
13221322
public abstract static class SetItemSliceNode extends Node {
13231323

1324-
public abstract void execute(SequenceStorage s, SliceInfo info, Object iterable);
1324+
public abstract void execute(SequenceStorage s, SliceInfo info, Object iterable, boolean canGeneralize);
1325+
1326+
public final void execute(SequenceStorage s, SliceInfo info, Object iterable) {
1327+
execute(s, info, iterable, true);
1328+
}
13251329

13261330
@Specialization(guards = "hasStorage(seq)")
1327-
static void doStorage(SequenceStorage s, SliceInfo info, PSequence seq,
1331+
static void doStorage(SequenceStorage s, SliceInfo info, PSequence seq, boolean canGeneralize,
13281332
@Shared("setStorageSliceNode") @Cached SetStorageSliceNode setStorageSliceNode,
13291333
@Cached GetSequenceStorageNode getSequenceStorageNode) {
1330-
setStorageSliceNode.execute(s, info, getSequenceStorageNode.execute(seq));
1334+
setStorageSliceNode.execute(s, info, getSequenceStorageNode.execute(seq), canGeneralize);
13311335
}
13321336

13331337
@Specialization
1334-
static void doGeneric(SequenceStorage s, SliceInfo info, Object iterable,
1338+
static void doGeneric(SequenceStorage s, SliceInfo info, Object iterable, boolean canGeneralize,
13351339
@Shared("setStorageSliceNode") @Cached SetStorageSliceNode setStorageSliceNode,
13361340
@Cached ListNodes.ConstructListNode constructListNode) {
13371341
PList list = constructListNode.execute(iterable);
1338-
setStorageSliceNode.execute(s, info, list.getSequenceStorage());
1342+
setStorageSliceNode.execute(s, info, list.getSequenceStorage(), canGeneralize);
13391343
}
13401344

13411345
public static SetItemSliceNode create() {
@@ -1416,10 +1420,10 @@ protected static boolean isBasicSequenceStorage(Object o) {
14161420
@ImportStatic(SequenceStorageBaseNode.class)
14171421
abstract static class SetStorageSliceNode extends Node {
14181422

1419-
public abstract void execute(SequenceStorage s, SliceInfo info, SequenceStorage iterable);
1423+
public abstract void execute(SequenceStorage s, SliceInfo info, SequenceStorage iterable, boolean canGeneralize);
14201424

14211425
@Specialization(limit = "MAX_ARRAY_STORAGES", guards = {"self.getClass() == cachedClass", "self.getClass() == sequence.getClass()", "replacesWholeSequence(cachedClass, self, info)"})
1422-
static void doWholeSequence(BasicSequenceStorage self, @SuppressWarnings("unused") SliceInfo info, BasicSequenceStorage sequence,
1426+
static void doWholeSequence(BasicSequenceStorage self, @SuppressWarnings("unused") SliceInfo info, BasicSequenceStorage sequence, @SuppressWarnings("unused") boolean canGeneralize,
14231427
@Cached("self.getClass()") Class<? extends BasicSequenceStorage> cachedClass) {
14241428
BasicSequenceStorage selfProfiled = cachedClass.cast(self);
14251429
BasicSequenceStorage otherProfiled = cachedClass.cast(sequence);
@@ -1428,8 +1432,8 @@ static void doWholeSequence(BasicSequenceStorage self, @SuppressWarnings("unused
14281432
selfProfiled.minimizeCapacity();
14291433
}
14301434

1431-
@Specialization(guards = {"isDataTypeCompatibleNode.execute(self, values)", "sinfo.step == 1"})
1432-
static void singleStep(SequenceStorage self, SliceInfo sinfo, SequenceStorage values,
1435+
@Specialization(guards = {"!canGeneralize || isDataTypeCompatibleNode.execute(self, values)", "sinfo.step == 1"})
1436+
static void singleStep(SequenceStorage self, SliceInfo sinfo, SequenceStorage values, @SuppressWarnings("unused") boolean canGeneralize,
14331437
@Cached @SuppressWarnings("unused") IsDataTypeCompatibleNode isDataTypeCompatibleNode,
14341438
@Cached LenNode lenNode,
14351439
@Cached SetLenNode setLenNode,
@@ -1456,8 +1460,8 @@ static void singleStep(SequenceStorage self, SliceInfo sinfo, SequenceStorage va
14561460
memoryError, negGrowth, posGrowth, raiseNode);
14571461
}
14581462

1459-
@Specialization(guards = {"isDataTypeCompatibleNode.execute(self, values)", "sinfo.step != 1"})
1460-
static void multiStep(SequenceStorage self, SliceInfo sinfo, SequenceStorage values,
1463+
@Specialization(guards = {"!canGeneralize || isDataTypeCompatibleNode.execute(self, values)", "sinfo.step != 1"})
1464+
static void multiStep(SequenceStorage self, SliceInfo sinfo, SequenceStorage values, @SuppressWarnings("unused") boolean canGeneralize,
14611465
@Cached @SuppressWarnings("unused") IsDataTypeCompatibleNode isDataTypeCompatibleNode,
14621466
@Cached ConditionProfile wrongLength,
14631467
@Cached ConditionProfile deleteSlice,
@@ -1490,8 +1494,8 @@ static void multiStep(SequenceStorage self, SliceInfo sinfo, SequenceStorage val
14901494
}
14911495
}
14921496

1493-
@Specialization(guards = "!isAssignCompatibleNode.execute(self, sequence)")
1494-
static void doError(@SuppressWarnings("unused") SequenceStorage self, @SuppressWarnings("unused") SliceInfo info, SequenceStorage sequence,
1497+
@Specialization(guards = {"canGeneralize", "!isAssignCompatibleNode.execute(self, sequence)"})
1498+
static void doError(@SuppressWarnings("unused") SequenceStorage self, @SuppressWarnings("unused") SliceInfo info, SequenceStorage sequence, @SuppressWarnings("unused") boolean canGeneralize,
14951499
@Cached @SuppressWarnings("unused") IsAssignCompatibleNode isAssignCompatibleNode) {
14961500
throw new SequenceStoreException(sequence.getIndicativeValue());
14971501
}
@@ -2765,6 +2769,13 @@ static int doGeneric(VirtualFrame frame, SequenceStorage left, Object item,
27652769
}
27662770
}
27672771

2772+
/**
2773+
* Generalization node must convert given storage to a storage that is able to be written any
2774+
* number of any valid elements. I.e., there must be a specialization handling that storage type
2775+
* in {@link SetItemScalarNode}. Note: it is possible that the RHS of the write may be invalid
2776+
* element, e.g., large integer when the storage is bytes array storage, but in such case the
2777+
* {@link SetItemScalarNode} will correctly raise Python level {@code ValueError}.
2778+
*/
27682779
public abstract static class GeneralizationNode extends Node {
27692780
public abstract SequenceStorage execute(SequenceStorage toGeneralize, Object indicationValue);
27702781

@@ -2836,6 +2847,49 @@ public static NoGeneralizationCustomMessageNode create(String msg) {
28362847
}
28372848
}
28382849

2850+
/**
2851+
* Byte array is specific that it supports being written using slice from other iterables as
2852+
* long as the individual elements written can be converted to bytes. Arrays from the array
2853+
* module do not support this, they can be written only individual elements or slice from other
2854+
* array of the same type.
2855+
*
2856+
* This node works with the assumption that all storages of byte arrays support writing of any
2857+
* number of bytes. There is no actual generalization of the storage, but instead we tell the
2858+
* caller that it should try again with the same storage (in the second try, it should try to
2859+
* write whatever is in RHS no matter of the type of the storage of RHS).
2860+
*
2861+
* This is only limitation of this node. Shall we ever want to support byte arrays that can be
2862+
* backed by different types of storage, we'd only need to change this node to accommodate for
2863+
* that and return the most generic storage of those.
2864+
*/
2865+
public static class ByteArrayGeneralizationNode extends GeneralizationNode {
2866+
public static ByteArrayGeneralizationNode UNCACHED = new ByteArrayGeneralizationNode();
2867+
2868+
public static final GenNodeSupplier SUPPLIER = new GenNodeSupplier() {
2869+
@Override
2870+
public GeneralizationNode getUncached() {
2871+
return UNCACHED;
2872+
}
2873+
2874+
@Override
2875+
public GeneralizationNode create() {
2876+
return new ByteArrayGeneralizationNode();
2877+
}
2878+
};
2879+
2880+
public static final Supplier<GeneralizationNode> CACHED_SUPPLIER = new Supplier<GeneralizationNode>() {
2881+
@Override
2882+
public GeneralizationNode get() {
2883+
return new ByteArrayGeneralizationNode();
2884+
}
2885+
};
2886+
2887+
@Override
2888+
public SequenceStorage execute(SequenceStorage toGeneralize, @SuppressWarnings("unused") Object indicationValue) {
2889+
return toGeneralize;
2890+
}
2891+
}
2892+
28392893
/**
28402894
* Implements list generalization rules; previously in 'SequenceStroage.generalizeFor'.
28412895
*/

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/util/CastToByteNode.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,15 @@ protected byte doBoolean(boolean value) {
147147
return value ? (byte) 1 : (byte) 0;
148148
}
149149

150-
@Specialization(guards = "coerce")
150+
@Specialization
151151
protected byte doBytes(VirtualFrame frame, PBytesLike value,
152152
@Cached("create()") SequenceStorageNodes.GetItemNode getItemNode) {
153-
return doIntOvf(getItemNode.executeInt(frame, value.getSequenceStorage(), 0));
153+
// Workaround GR-26346
154+
if (coerce) {
155+
return doIntOvf(getItemNode.executeInt(frame, value.getSequenceStorage(), 0));
156+
} else {
157+
return doGeneric(value);
158+
}
154159
}
155160

156161
@Specialization(guards = "plib.isForeignObject(value)", limit = "1")

0 commit comments

Comments
 (0)