Destructive copy in #replaceFrom:to:with:startingAt:

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Destructive copy in #replaceFrom:to:with:startingAt:

jgfoster
After many hours of debugging I’ve finally discovered that Pharo’s implementation of #replaceFrom:to:with:startingAt: does not make any check for overlapping regions to avoid destructive operations. 

| bytes |
bytes := #(1 2 3 4 5 6 7 8) copy.
bytes replaceFrom: 4 to: 7 with: bytes startingAt: 1.
bytes
 "#(1 2 3 1 2 3 1 8) in Pharo"
 "#(1 2 3 1 2 3 4 8) in GemStone”


While it isn’t the implementation I would choose, at least now I know!

James Foster

Reply | Threaded
Open this post in threaded view
|

Re: Destructive copy in #replaceFrom:to:with:startingAt:

Richard O'Keefe
The ANSI Smalltalk standard is characteristically vague here.
Both the memmove and memcpy readings are consistent with it.
The standard is characteristically buggy here too: the two
lines I've flagged with ** disagree.  I take the second one
to be correct.

5.7.12.5 Message:

  replaceFrom: start to: stop with: replacementElements startingAt: replacementStart

Synopsis

  Replace the elements of the receiver between positions start and stop inclusive
  with the elements of replacementElements, in their original order, starting at
  position replacementStart.  Answer the receiver.

Definition: "for" <sequencedCollection>

  The element at position replacementStart in replacementElements is stored
  in the receiver at position start; the element at replacementStart + 1 is
  stored at position start + 1; etc.
  Any previously stored elements at these positions in the receiver are replaced.
**If the size of replacementElements is not equal to (replacementStart+stop-start),
  the result of sending this message is unspecified.

Errors,

  If start < 1 or start > the receiver’s size.
  If stop < 1 or stop > the receiver’s size.
  If replacementStart < 1 or replacementStart > replacementElements size.
**If replacementElements size - replacementStart + 1 < stop - start + 1.

For what it's worth, GNU Smalltalk answers #(1 2 3 1 2 3 4 8), as does my
Smalltalk library.  So does VisualWorks.  It's a primitive in VW, so I
can't see what the code does, but the comment is tolerably clear:
"No range checks are performed, but  overlapping replacements are handled correctly."
VisualAge Smalltalk gives the memmove answer too.  The Dolphin sources say
"Overlapping moves are correctly handled."  ST/X makes a special check for the
receiver and source being the same object and conditionally calls memmove to
do the transfer.  Strongtalk doesn't check for receiver == source but does
check for repStart < start, so also belongs to the memmove group.

So Squeak/Pharo stand out as different here.
susie-0.3 and syx-0.1.7 and panda all do the memcpy() thing too,
but they don't count as finished.

On Fri, 9 Aug 2019 at 05:31, James Foster <[hidden email]> wrote:
After many hours of debugging I’ve finally discovered that Pharo’s implementation of #replaceFrom:to:with:startingAt: does not make any check for overlapping regions to avoid destructive operations. 

| bytes |
bytes := #(1 2 3 4 5 6 7 8) copy.
bytes replaceFrom: 4 to: 7 with: bytes startingAt: 1.
bytes
 "#(1 2 3 1 2 3 1 8) in Pharo"
 "#(1 2 3 1 2 3 4 8) in GemStone”


While it isn’t the implementation I would choose, at least now I know!

James Foster