The Trunk: Kernel-eem.1317.mcz

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

The Trunk: Kernel-eem.1317.mcz

commits-2
Eliot Miranda uploaded a new version of Kernel to project The Trunk:
http://source.squeak.org/trunk/Kernel-eem.1317.mcz

==================== Summary ====================

Name: Kernel-eem.1317
Author: eem
Time: 11 March 2020, 1:12:02.344414 pm
UUID: 3755843a-380e-4cbc-a498-b1f1a85f2906
Ancestors: Kernel-eem.1316

Add the core of read-only support, which will provide both for read-only literals and for efficient write-through cacheing to object databases such as gemstone.

This implementation differs slightly from Clément Béra's original implementatin for Pharo in that here ModificationForbidden always uses a mirror (either a Context or a Class) to attempt to modify the read-only
object in retryModification.

=============== Diff against Kernel-eem.1316 ===============

Item was changed:
  ----- Method: Behavior>>adoptInstance: (in category 'instance creation') -----
+ adoptInstance: anObject
+ "Change the class of anObject to me.
+ Primitive (found in Cog and new VMs)  follows the same rules as primitiveChangeClassTo:, but returns the class rather than the modified instance"
- adoptInstance: anInstance
- "Change the class of anInstance to the receiver.
- Primitive. Change the class of the argument anInstance into the receiver, provided
- that the format of the receiver matches the format of the argument's class.
- Fail if the argument is an immediate, or when the pointerness of the receiver is different
- from the pointerness of the argument, or when the receiver is a fixed pointer class and
- anInstance's size differs from the size that an instance of the receiver should have,
- or when anInstance's size is not an integer multiple of the receiver's unit size."
 
  <primitive: 160 error: ec>
+ ec == #'no modification' ifTrue:
+ [^self modificationForbiddenAdopting: anObject].
+ self primitiveFailed!
- ^self primitiveFailed!

Item was added:
+ ----- Method: Behavior>>adoptInstance:ignore: (in category 'read-only objects') -----
+ adoptInstance: anInstance ignore: ignore
+ "This method exists only to adapt to the pattern used in ModificationForbidden>>retryModification,
+ which expects all mutators to take an argument."
+ ^self adoptInstance: anInstance!

Item was added:
+ ----- Method: Behavior>>modificationForbiddenAdopting: (in category 'read-only objects') -----
+ modificationForbiddenAdopting: anObject
+ ^(ModificationForbidden new
+ mirror: self
+ object: anObject
+ index: nil
+ newValue: nil
+ retrySelector: #adoptInstance:ignore:) signal!

Item was added:
+ ----- Method: Context>>modificationForbiddenFor:at:put: (in category 'read-only objects') -----
+ modificationForbiddenFor: target at: index put: aCharacter
+ ^(ModificationForbidden new
+ mirror: self
+ object: target
+ index: index
+ newValue: aCharacter
+ retrySelector: #object:basicAt:put:) signal!

Item was added:
+ ----- Method: Context>>modificationForbiddenFor:at:putCharacter: (in category 'read-only objects') -----
+ modificationForbiddenFor: target at: index putCharacter: aCharacter
+ "eem 3/11/2020 13:09 this may be a mistake.  Instead perhaps String clients should
+ send asInteger and use modificationForbiddenFor:at:put:.  Opinions appreciated."
+ ^(ModificationForbidden new
+ mirror: self
+ object: target
+ index: index
+ newValue: (aCharacter isCharacter ifTrue: [aCharacter asInteger] ifFalse: [aCharacter])
+ retrySelector: #object:basicAt:put:) signal!

Item was added:
+ ----- Method: Context>>modificationForbiddenFor:instVarAt:put: (in category 'read-only objects') -----
+ modificationForbiddenFor: target instVarAt: index put: aCharacter
+ ^(ModificationForbidden new
+ mirror: self
+ object: target
+ index: index
+ newValue: aCharacter
+ retrySelector: #object:instVarAt:put:) signal!

Item was changed:
  ----- Method: Context>>object:basicAt:put: (in category 'mirror primitives') -----
  object: anObject basicAt: index put: value
+ "Store the last argument value in the indexable element of the argument anObject
+ indicated by index without sending anObject a message. Fail if the argument index
+ is not an Integer or is out of bounds, or if anObject is not indexable, or if anObject is
+ read-only, or if value is an inappropriate value for anObject's indexable slots.
+ This mimics the action of the VM when it indexes an object. Used to simulate
+ the execution machinery by, for example, the debugger.
- "Store the last argument
- value in the indexable element of the argument anObject indicated by index without sending
- anObject a message. Fail if the argument index is not an Integer or is out of bounds, or if
- anObject is not indexable, or if value is an inappropriate value for anObject's indexable slots.
- This mimics the action of the VM when it indexes an object.
- Used to simulate the execution machinery by, for example, the debugger.
  Primitive.  See Object documentation whatIsAPrimitive."
 
+ <primitive: 61 error: ec>
+ index isInteger ifTrue:
+ [(index >= 1 and: [index <= (self objectSize: anObject)])
+ ifTrue:
+ [ec == #'no modification' ifTrue:
+ [^self modificationForbiddenFor: anObject at: index put: value].
+ self errorImproperStore]
+ ifFalse: [self errorSubscriptBounds: index]].
+ index isNumber ifTrue:
+ [^self object: anObject basicAt: index asInteger put: value].
+ self errorNonIntegerIndex!
- <primitive: 61>
- index isInteger
- ifTrue: [(index >= 1 and: [index <= (self objectSize: anObject)])
- ifTrue: [self errorImproperStore]
- ifFalse: [self errorSubscriptBounds: index]].
- index isNumber
- ifTrue: [^self object: anObject basicAt: index asInteger put: value]
- ifFalse: [self errorNonIntegerIndex]!

Item was changed:
  ----- Method: FutureMaker>>basicAt:put: (in category 'accessing') -----
  basicAt: index put: value
  "Primitive. Assumes receiver is indexable. Store the second argument
  value in the indexable element of the receiver indicated by index. Fail
  if the index is not an Integer or is out of bounds. Or fail if the value is
  not of the right type for this kind of collection. Answer the value that
  was stored. Essential. Do not override in a subclass. See Object
  documentation whatIsAPrimitive."
 
+ <primitive: 61 error: ec>
+ index isInteger ifTrue:
+ [(index >= 1 and: [index <= self basicSize])
+ ifTrue:
+ [ec == #'no modification' ifTrue:
+ [^thisContext modificationForbiddenFor: self at: index put: value].
+ self errorImproperStore]
+ ifFalse: [self errorSubscriptBounds: index]].
+ index isNumber ifTrue:
+ [^self basicAt: index asInteger put: value].
+ self errorNonIntegerIndex!
- <primitive: 61>
- index isInteger
- ifTrue: [(index >= 1 and: [index <= self size])
- ifTrue: [self errorImproperStore]
- ifFalse: [self errorSubscriptBounds: index]].
- index isNumber
- ifTrue: [^self basicAt: index asInteger put: value]
- ifFalse: [self errorNonIntegerIndex]!

Item was changed:
  ----- Method: FutureMaker>>instVarAt:put: (in category 'accessing') -----
+ instVarAt: anInteger put: anObject
- instVarAt: anInteger put: anObject
  "Primitive. Store a value into a fixed variable in the receiver. The
  numbering of the variables corresponds to the named instance variables.
  Fail if the index is not an Integer or is not the index of a fixed variable.
  Answer the value stored as the result. Using this message violates the
  principle that each object has sovereign control over the storing of
  values into its instance variables. Essential. See Object documentation
  whatIsAPrimitive."
 
  <primitive: 174 error: ec>
+ ec == #'no modification' ifTrue:
+ [^thisContext modificationForbiddenFor: self instVarAt: anInteger value: anObject].
  self primitiveFailed!

Item was added:
+ Exception subclass: #ModificationForbidden
+ instanceVariableNames: 'mirror object fieldIndex newValue retrySelector'
+ classVariableNames: ''
+ poolDictionaries: ''
+ category: 'Kernel-Exceptions'!
+
+ !ModificationForbidden commentStamp: 'eem 3/11/2020 11:53' prior: 0!
+ This exception is raised when attempting to mutate a read-only object.
+
+ My instances have 5 fields to be able to reproduce the modification via the retryModification method.
+
+ mirror <Context|Behavior> the Context or Behavior that will perform the modification if modificationRetried
+ object <Object> read-only object that attempted to mutate
+ index <SmallInteger | nil> index of the field in the object mutated, relevant for the corresponding selector
+ value <Object> value that was attempted to be stored into the read-only object
+ selector <Symbol> selector that can be used to reproduce the mutation (typically, #object:basicAt:put:, #object:instVarAt:put:, etc.)!

Item was added:
+ ----- Method: ModificationForbidden>>defaultAction (in category 'priv handling') -----
+ defaultAction
+ UnhandledError signalForException: self!

Item was added:
+ ----- Method: ModificationForbidden>>fieldIndex (in category 'accessing') -----
+ fieldIndex
+ ^ fieldIndex!

Item was added:
+ ----- Method: ModificationForbidden>>indexedMessageText (in category 'printing') -----
+ indexedMessageText
+ ^String streamContents:
+ [ :s |
+ s << ' '.
+ self printObject: object on: s.
+ s << ' is read-only, hence its field '.
+ fieldIndex printOn: s.
+ s << ' cannot be modified with '.
+ self printObject: newValue on: s]!

Item was added:
+ ----- Method: ModificationForbidden>>messageText (in category 'printing') -----
+ messageText
+ "Overwritten to initialize the message text to a standard text if it has not yet been set"
+
+ ^ messageText ifNil: [ messageText := self standardMessageText ]!

Item was added:
+ ----- Method: ModificationForbidden>>mirror (in category 'accessing') -----
+ mirror
+ ^mirror!

Item was added:
+ ----- Method: ModificationForbidden>>mirror:object:index:newValue:retrySelector: (in category 'accessing') -----
+ mirror: aContext object: anObject index: index newValue: value retrySelector: selector
+
+ mirror := aContext.
+ object := anObject.
+ fieldIndex := index.
+ newValue := value.
+ retrySelector := selector!

Item was added:
+ ----- Method: ModificationForbidden>>newValue (in category 'accessing') -----
+ newValue
+ ^ newValue!

Item was added:
+ ----- Method: ModificationForbidden>>nonIndexedMessageText (in category 'printing') -----
+ nonIndexedMessageText
+ ^String streamContents:
+ [ :s |
+ s << ' '.
+ self printObject: object on: s.
+ s << ' is read-only, hence its selector '.
+ s << retrySelector.
+ s << ' cannot be executed with '.
+ self printObject: newValue on: s]!

Item was added:
+ ----- Method: ModificationForbidden>>object (in category 'accessing') -----
+ object
+ ^ object!

Item was added:
+ ----- Method: ModificationForbidden>>printObject:on: (in category 'printing') -----
+ printObject: obj on: s
+ [obj printOn: s]
+ on: Exception
+ do: [ :ex | s << '<cannot print object>' ]!

Item was added:
+ ----- Method: ModificationForbidden>>retryModification (in category 'retrying') -----
+ retryModification
+ fieldIndex
+ ifNotNil: [mirror perform: retrySelector with: object with: fieldIndex with: newValue]
+ ifNil: [mirror perform: retrySelector with: object with: newValue].
+ self resume: newValue!

Item was added:
+ ----- Method: ModificationForbidden>>retrySelector (in category 'accessing') -----
+ retrySelector
+ ^ retrySelector!

Item was added:
+ ----- Method: ModificationForbidden>>standardMessageText (in category 'printing') -----
+ standardMessageText
+ ^fieldIndex
+ ifNil: [self nonIndexedMessageText]
+ ifNotNil: [self indexedMessageText]!

Item was changed:
  ----- Method: Object>>at:put: (in category 'accessing') -----
+ at: index put: anObject
+ "Primitive. Assumes receiver is indexable. Store the argument anObject in
+ the indexable element of the receiver indicated by index. Fail if the  index
+ is not an Integer or is out of bounds, or if the receiver is read-only, or if
+ anObject is not of the right type for this kind of collection. Answer the
+ value (anObject) that was stored. Essential. See Object documentation
+ whatIsAPrimitive."
- at: index put: value
- "Primitive. Assumes receiver is indexable. Store the argument value in
- the indexable element of the receiver indicated by index. Fail if the
- index is not an Integer or is out of bounds. Or fail if the value is not of
- the right type for this kind of collection. Answer the value that was
- stored. Essential. See Object documentation whatIsAPrimitive."
 
+ <primitive: 61 error: ec>
- <primitive: 61>
  index isInteger ifTrue:
  [self class isVariable
+ ifTrue:
+ [(index >= 1 and: [index <= self size])
+ ifTrue:
+ [ec == #'no modification' ifTrue:
+ [^thisContext modificationForbiddenFor: self at: index put: anObject].
+ self errorImproperStore]
- ifTrue: [(index >= 1 and: [index <= self size])
- ifTrue: [self errorImproperStore]
  ifFalse: [self errorSubscriptBounds: index]]
  ifFalse: [self errorNotIndexable]].
+ index isNumber ifTrue:
+ [^self at: index asInteger put: anObject].
+ self errorNonIntegerIndex!
- index isNumber
- ifTrue: [^self at: index asInteger put: value]
- ifFalse: [self errorNonIntegerIndex]!

Item was added:
+ ----- Method: Object>>attemptToAssign:withIndex: (in category 'write barrier') -----
+ attemptToAssign: value withIndex: index
+ "Called by the VM when assigning an instance variable of an immutable object.
+ Upon return, executing will resume *after* the inst var assignment. If the inst var mutation has to be
+ performed, do it manually here in the call back with instVarAt:put:.
+ This method has to return *no* value by jumping to the context's sender"
+
+ thisContext modificationForbiddenFor: self instVarAt: index put: value.
+
+ thisContext sender jump
+ "CAN'T REACH"!

Item was changed:
  ----- Method: Object>>basicAt:put: (in category 'accessing') -----
+ basicAt: index put: anObject
+ "Primitive. Assumes receiver is indexable. Store the argument anObject in
+ the indexable element of the receiver indicated by index. Fail if the  index
+ is not an Integer or is out of bounds, or if the receiver is read-only, or if
+ anObject is not of the right type for this kind of collection. Answer the
+ value (anObject) that was stored. Essential. Do not override in a subclass.
+ See Object documentation whatIsAPrimitive."
- basicAt: index put: value
- "Primitive. Assumes receiver is indexable. Store the second argument
- value in the indexable element of the receiver indicated by index. Fail
- if the index is not an Integer or is out of bounds. Or fail if the value is
- not of the right type for this kind of collection. Answer the value that
- was stored. Essential. Do not override in a subclass. See Object
- documentation whatIsAPrimitive."
 
+ <primitive: 61 error: ec>
+ index isInteger ifTrue:
+ [self class isVariable
+ ifTrue:
+ [(index >= 1 and: [index <= self basicSize])
+ ifTrue:
+ [ec == #'no modification' ifTrue:
+ [^thisContext modificationForbiddenFor: self at: index put: anObject].
+ self errorImproperStore]
+ ifFalse: [self errorSubscriptBounds: index]]
+ ifFalse: [self errorNotIndexable]].
+ index isNumber ifTrue:
+ [^self basicAt: index asInteger put: anObject].
+ self errorNonIntegerIndex!
- <primitive: 61>
- index isInteger
- ifTrue: [(index >= 1 and: [index <= self size])
- ifTrue: [self errorImproperStore]
- ifFalse: [self errorSubscriptBounds: index]].
- index isNumber
- ifTrue: [^self basicAt: index asInteger put: value]
- ifFalse: [self errorNonIntegerIndex]!

Item was added:
+ ----- Method: Object>>beReadOnlyObject (in category 'write barrier') -----
+ beReadOnlyObject
+ "If the VM supports read-only objects it will not write to read-only objects.
+ An attempt to write to an instance variable of a read-only object will
+ cause the VM to send attemptToAssign:withIndex: to the read-only object.
+ An attempt to modify a read-only object in a primitive will cause the
+ primitive to fail with a #'no modification' error code.
+ Set the read-only flag of the receiver to true and answer the previous vaue of the flag."
+ ^self setIsReadOnlyObject: true!

Item was added:
+ ----- Method: Object>>beWritableObject (in category 'write barrier') -----
+ beWritableObject
+ "If the VM supports read-only objects it will not write to read-only objects.
+ An attempt to write to an instance variable of a read-only object will
+ cause the VM to send attemptToAssign:withIndex: to the read-only object.
+ An attempt to modify a read-only object in a primitive will cause the
+ primitive to fail with a #'no modification' error code.
+ Set the read-only flag of the receiver to false and answer the previous vaue of the flag."
+ ^self setIsReadOnlyObject: false!

Item was changed:
  ----- Method: Object>>instVarAt:put: (in category 'system primitives') -----
  instVarAt: index put: anObject
  "Primitive. Store a value into a fixed variable in an object. The numbering of the
  variables corresponds to the named instance variables, followed by the indexed
  instance variables. Fail if the index is not an Integer or is not the index of a fixed
+ or indexed variable, or if the receiver is read-only.
+ Essential. See Object documentation whatIsAPrimitive."
- or indexed variable. Essential. See Object documentation whatIsAPrimitive."
 
  <primitive: 174 error: ec>
+ ec == #'no modification' ifTrue:
+ [^thisContext modificationForbiddenFor: self instVarAt: index put: anObject].
  self primitiveFailed!

Item was added:
+ ----- Method: Object>>isReadOnlyObject (in category 'write barrier') -----
+ isReadOnlyObject
+ "Answer if the receiver is read-only.
+ If the VM supports read-only objects it will not write to read-only objects.
+ An attempt to write to an instance variable of a read-only object will
+ cause the VM to send attemptToAssign:withIndex: to the read-only object.
+ An attempt to modify a read-only object in a primitive will cause the
+ primitive to fail with a #'no modification' error code."
+ <primitive: 163 error: ec>
+ ^self class isImmediateClass!

Item was changed:
  ----- Method: Object>>primitiveChangeClassTo: (in category 'system primitives') -----
  primitiveChangeClassTo: anObject
+ "Primitive. Change the class of the receiver into the class of the argument
+ given that the format of the receiver matches the format of the argument's
+ class. Fail if receiver or argument are immediates (SmallIntegers, Characters
+ or SmallFloat64s), or when the format of the receiver is different from the
+ format of the argument's class, or when the arguments class is fixed and
+ the receiver's size differs from the size that an instance of the argument's
+ class should have.
- "Primitive. Change the class of the receiver into the class of the argument given that the format of the receiver matches the format of the argument's class. Fail if receiver or argument are SmallIntegers, or the receiver is an instance of a compact class and the argument isn't, or when the argument's class is compact and the receiver isn't, or when the format of the receiver is different from the format of the argument's class, or when the arguments class is fixed and the receiver's size differs from the size that an instance of the argument's class should have.
- Note: The primitive will fail in most cases that you think might work. This is mostly because of a) the difference between compact and non-compact classes, and b) because of differences in the format. As an example, '(Array new: 3) primitiveChangeClassTo: Morph basicNew' would fail for three of the reasons mentioned above. Array is compact, Morph is not (failure #1). Array is variable and Morph is fixed (different format - failure #2). Morph is a fixed-field-only object and the array is too short (failure #3).
- The facility is really provided for certain, very specific applications (mostly related to classes changing shape) and not for casual use."
 
+ Note: The primitive will fail in cases that you think might work. This is mostly
+ because of because of differences in the format. As an example,
+ '(Array new: 3) primitiveChangeClassTo: Morph basicNew'
+ would fail because Morph is a fixed-field-only object with about 6 instance
+ variables, and the array is too short (failure #3).
+
+ The facility is really provided for certain, very specific applications (mostly related to classes changing shape) and not for casual use."
+
+ <primitive: 115 error: ec>
+ ec == #'no modification' ifTrue:
+ [^anObject class modificationForbiddenAdopting: self].
- <primitive: 115>
  self primitiveFailed!

Item was added:
+ ----- Method: Object>>setIsReadOnlyObject: (in category 'write barrier') -----
+ setIsReadOnlyObject: aBoolean
+ "If the VM supports read-only objects it will not write to read-only objects.
+ An attempt to write to an instance variable of a read-only object will
+ cause the VM to send attemptToAssign:withIndex: to the read-only object.
+ An attempt to modify a read-only object in a primitive will cause the
+ primitive to fail with a #'no modification' error code.
+ This primitive sets the read-only flag of the receiver to the given
+ value and answers the previous vaue of the flag.
+ Note: Some objects can't be read-only, currently contexts and objects related
+ to process scheduling (Processor, Process instances, Semaphore instances, ...)"
+ <primitive: 164 error: ec>
+ ^self primitiveFailed!



Reply | Threaded
Open this post in threaded view
|

Re: The Trunk: Kernel-eem.1317.mcz

Christoph Thiede

Hi Eliot,


allow me one question regarding these exciting changes (#basicAt:put:): If anInteger is out of range, we retry the operation again and again until the handler has changed the size of the receiver. Why don't we do so for ModificationForbiddens, too? Yes, you can always use #ignore or #resume to use both variants, but the questions is, should it be possible to make an object readable during a debugger is open and then press "Continue" to have the modification retried by default?


Also, is it really necessary to define these error messages in Context only? All related exceptions (#errorImproperStore, #errorSubscriptBounds:, #errorNonIntegerIndex) are raised in Object - which, in my eyes, looks much better and more cleaner. Object >> #modificationForbiddenFor:at:put: then could refer to thisContext sender (such as #subclassResponsibility, #primitiveFailed, or #deprecated:, do, for example).


Best,

Christoph


Von: Squeak-dev <[hidden email]> im Auftrag von [hidden email] <[hidden email]>
Gesendet: Mittwoch, 11. März 2020 21:12:05
An: [hidden email]; [hidden email]
Betreff: [squeak-dev] The Trunk: Kernel-eem.1317.mcz
 
Eliot Miranda uploaded a new version of Kernel to project The Trunk:
http://source.squeak.org/trunk/Kernel-eem.1317.mcz

==================== Summary ====================

Name: Kernel-eem.1317
Author: eem
Time: 11 March 2020, 1:12:02.344414 pm
UUID: 3755843a-380e-4cbc-a498-b1f1a85f2906
Ancestors: Kernel-eem.1316

Add the core of read-only support, which will provide both for read-only literals and for efficient write-through cacheing to object databases such as gemstone.

This implementation differs slightly from Clément Béra's original implementatin for Pharo in that here ModificationForbidden always uses a mirror (either a Context or a Class) to attempt to modify the read-only
object in retryModification.

=============== Diff against Kernel-eem.1316 ===============

Item was changed:
  ----- Method: Behavior>>adoptInstance: (in category 'instance creation') -----
+ adoptInstance: anObject
+        "Change the class of anObject to me.
+        Primitive (found in Cog and new VMs)  follows the same rules as primitiveChangeClassTo:, but returns the class rather than the modified instance"
- adoptInstance: anInstance
-        "Change the class of anInstance to the receiver.
-         Primitive. Change the class of the argument anInstance into the receiver, provided
-         that the format of the receiver matches the format of the argument's class.
-         Fail if the argument is an immediate, or when the pointerness of the receiver is different
-         from the pointerness of the argument, or when the receiver is a fixed pointer class and
-         anInstance's size differs from the size that an instance of the receiver should have,
-         or when anInstance's size is not an integer multiple of the receiver's unit size."
 
         <primitive: 160 error: ec>
+        ec == #'no modification' ifTrue:
+                [^self modificationForbiddenAdopting: anObject].
+        self primitiveFailed!
-        ^self primitiveFailed!

Item was added:
+ ----- Method: Behavior>>adoptInstance:ignore: (in category 'read-only objects') -----
+ adoptInstance: anInstance ignore: ignore
+        "This method exists only to adapt to the pattern used in ModificationForbidden>>retryModification,
+         which expects all mutators to take an argument."
+        ^self adoptInstance: anInstance!

Item was added:
+ ----- Method: Behavior>>modificationForbiddenAdopting: (in category 'read-only objects') -----
+ modificationForbiddenAdopting: anObject
+        ^(ModificationForbidden new
+                mirror: self
+                object: anObject
+                index: nil
+                newValue: nil
+                retrySelector: #adoptInstance:ignore:) signal!

Item was added:
+ ----- Method: Context>>modificationForbiddenFor:at:put: (in category 'read-only objects') -----
+ modificationForbiddenFor: target at: index put: aCharacter
+        ^(ModificationForbidden new
+                mirror: self
+                object: target
+                index: index
+                newValue: aCharacter
+                retrySelector: #object:basicAt:put:) signal!

Item was added:
+ ----- Method: Context>>modificationForbiddenFor:at:putCharacter: (in category 'read-only objects') -----
+ modificationForbiddenFor: target at: index putCharacter: aCharacter
+        "eem 3/11/2020 13:09 this may be a mistake.  Instead perhaps String clients should
+         send asInteger and use modificationForbiddenFor:at:put:.  Opinions appreciated."
+        ^(ModificationForbidden new
+                mirror: self
+                object: target
+                index: index
+                newValue: (aCharacter isCharacter ifTrue: [aCharacter asInteger] ifFalse: [aCharacter])
+                retrySelector: #object:basicAt:put:) signal!

Item was added:
+ ----- Method: Context>>modificationForbiddenFor:instVarAt:put: (in category 'read-only objects') -----
+ modificationForbiddenFor: target instVarAt: index put: aCharacter
+        ^(ModificationForbidden new
+                mirror: self
+                object: target
+                index: index
+                newValue: aCharacter
+                retrySelector: #object:instVarAt:put:) signal!

Item was changed:
  ----- Method: Context>>object:basicAt:put: (in category 'mirror primitives') -----
  object: anObject basicAt: index put: value
+        "Store the last argument value in the indexable element of the argument anObject
+         indicated by index without sending anObject a message. Fail if the argument index
+         is not an Integer or is out of bounds, or if anObject is not indexable, or if anObject is
+         read-only, or if value is an inappropriate value for anObject's indexable slots.
+         This mimics the action of the VM when it indexes an object. Used to simulate
+         the execution machinery by, for example, the debugger.
-        "Store the last argument
-         value in the indexable element of the argument anObject indicated by index without sending
-         anObject a message. Fail if the argument index is not an Integer or is out of bounds, or if
-         anObject is not indexable, or if value is an inappropriate value for anObject's indexable slots.
-         This mimics the action of the VM when it indexes an object.
-         Used to simulate the execution machinery by, for example, the debugger.
          Primitive.  See Object documentation whatIsAPrimitive."
 
+        <primitive: 61 error: ec>
+        index isInteger ifTrue:
+                [(index >= 1 and: [index <= (self objectSize: anObject)])
+                        ifTrue:
+                                [ec == #'no modification' ifTrue:
+                                        [^self modificationForbiddenFor: anObject at: index put: value].
+                                 self errorImproperStore]
+                        ifFalse: [self errorSubscriptBounds: index]].
+        index isNumber ifTrue:
+                [^self object: anObject basicAt: index asInteger put: value].
+        self errorNonIntegerIndex!
-        <primitive: 61>
-        index isInteger
-                ifTrue: [(index >= 1 and: [index <= (self objectSize: anObject)])
-                                        ifTrue: [self errorImproperStore]
-                                        ifFalse: [self errorSubscriptBounds: index]].
-        index isNumber
-                ifTrue: [^self object: anObject basicAt: index asInteger put: value]
-                ifFalse: [self errorNonIntegerIndex]!

Item was changed:
  ----- Method: FutureMaker>>basicAt:put: (in category 'accessing') -----
  basicAt: index put: value
         "Primitive. Assumes receiver is indexable. Store the second argument
         value in the indexable element of the receiver indicated by index. Fail
         if the index is not an Integer or is out of bounds. Or fail if the value is
         not of the right type for this kind of collection. Answer the value that
         was stored. Essential. Do not override in a subclass. See Object
         documentation whatIsAPrimitive."
 
+        <primitive: 61 error: ec>
+        index isInteger ifTrue:
+                [(index >= 1 and: [index <= self basicSize])
+                        ifTrue:
+                                [ec == #'no modification' ifTrue:
+                                        [^thisContext modificationForbiddenFor: self at: index put: value].
+                                 self errorImproperStore]
+                        ifFalse: [self errorSubscriptBounds: index]].
+        index isNumber ifTrue:
+                [^self basicAt: index asInteger put: value].
+        self errorNonIntegerIndex!
-        <primitive: 61>
-        index isInteger
-                ifTrue: [(index >= 1 and: [index <= self size])
-                                        ifTrue: [self errorImproperStore]
-                                        ifFalse: [self errorSubscriptBounds: index]].
-        index isNumber
-                ifTrue: [^self basicAt: index asInteger put: value]
-                ifFalse: [self errorNonIntegerIndex]!

Item was changed:
  ----- Method: FutureMaker>>instVarAt:put: (in category 'accessing') -----
+ instVarAt: anInteger put: anObject
- instVarAt: anInteger put: anObject
         "Primitive. Store a value into a fixed variable in the receiver. The
         numbering of the variables corresponds to the named instance variables.
         Fail if the index is not an Integer or is not the index of a fixed variable.
         Answer the value stored as the result. Using this message violates the
         principle that each object has sovereign control over the storing of
         values into its instance variables. Essential. See Object documentation
         whatIsAPrimitive."
 
         <primitive: 174 error: ec>
+        ec == #'no modification' ifTrue:
+                [^thisContext modificationForbiddenFor: self instVarAt: anInteger value: anObject].
         self primitiveFailed!

Item was added:
+ Exception subclass: #ModificationForbidden
+        instanceVariableNames: 'mirror object fieldIndex newValue retrySelector'
+        classVariableNames: ''
+        poolDictionaries: ''
+        category: 'Kernel-Exceptions'!
+
+ !ModificationForbidden commentStamp: 'eem 3/11/2020 11:53' prior: 0!
+ This exception is raised when attempting to mutate a read-only object.
+
+ My instances have 5 fields to be able to reproduce the modification via the retryModification method.
+
+ mirror        <Context|Behavior>      the Context or Behavior that will perform the modification if modificationRetried
+ object        <Object>                                read-only object that attempted to mutate
+ index <SmallInteger | nil>    index of the field in the object mutated, relevant for the corresponding selector
+ value <Object>                                value that was attempted to be stored into the read-only object
+ selector      <Symbol>                                selector that can be used to reproduce the mutation (typically, #object:basicAt:put:, #object:instVarAt:put:, etc.)!

Item was added:
+ ----- Method: ModificationForbidden>>defaultAction (in category 'priv handling') -----
+ defaultAction
+        UnhandledError signalForException: self!

Item was added:
+ ----- Method: ModificationForbidden>>fieldIndex (in category 'accessing') -----
+ fieldIndex
+        ^ fieldIndex!

Item was added:
+ ----- Method: ModificationForbidden>>indexedMessageText (in category 'printing') -----
+ indexedMessageText
+        ^String streamContents:
+                [ :s |
+                s << ' '.
+                self printObject: object on: s.
+                s << ' is read-only, hence its field '.
+                fieldIndex printOn: s.
+                s << ' cannot be modified with '.
+                self printObject: newValue on: s]!

Item was added:
+ ----- Method: ModificationForbidden>>messageText (in category 'printing') -----
+ messageText
+        "Overwritten to initialize the message text to a standard text if it has not yet been set"
+       
+        ^ messageText ifNil: [ messageText := self standardMessageText ]!

Item was added:
+ ----- Method: ModificationForbidden>>mirror (in category 'accessing') -----
+ mirror
+        ^mirror!

Item was added:
+ ----- Method: ModificationForbidden>>mirror:object:index:newValue:retrySelector: (in category 'accessing') -----
+ mirror: aContext object: anObject index: index newValue: value retrySelector: selector
+
+        mirror := aContext.
+        object := anObject.
+        fieldIndex := index.
+        newValue := value.
+        retrySelector := selector!

Item was added:
+ ----- Method: ModificationForbidden>>newValue (in category 'accessing') -----
+ newValue
+        ^ newValue!

Item was added:
+ ----- Method: ModificationForbidden>>nonIndexedMessageText (in category 'printing') -----
+ nonIndexedMessageText
+        ^String streamContents:
+                [ :s |
+                s << ' '.
+                self printObject: object on: s.
+                s << ' is read-only, hence its selector '.
+                s << retrySelector.
+                s << ' cannot be executed with '.
+                self printObject: newValue on: s]!

Item was added:
+ ----- Method: ModificationForbidden>>object (in category 'accessing') -----
+ object
+        ^ object!

Item was added:
+ ----- Method: ModificationForbidden>>printObject:on: (in category 'printing') -----
+ printObject: obj on: s
+        [obj printOn: s]
+                on: Exception
+                do: [ :ex | s << '<cannot print object>' ]!

Item was added:
+ ----- Method: ModificationForbidden>>retryModification (in category 'retrying') -----
+ retryModification
+        fieldIndex
+                ifNotNil: [mirror perform: retrySelector with: object with: fieldIndex with: newValue]
+                ifNil: [mirror perform: retrySelector with: object with: newValue].
+        self resume: newValue!

Item was added:
+ ----- Method: ModificationForbidden>>retrySelector (in category 'accessing') -----
+ retrySelector
+        ^ retrySelector!

Item was added:
+ ----- Method: ModificationForbidden>>standardMessageText (in category 'printing') -----
+ standardMessageText
+        ^fieldIndex
+                ifNil: [self nonIndexedMessageText]
+                ifNotNil: [self indexedMessageText]!

Item was changed:
  ----- Method: Object>>at:put: (in category 'accessing') -----
+ at: index put: anObject
+        "Primitive. Assumes receiver is indexable. Store the argument anObject in
+         the indexable element of the receiver indicated by index. Fail if the  index
+         is not an Integer or is out of bounds, or if the receiver is read-only, or if
+         anObject is not of the right type for this kind of collection. Answer the
+         value (anObject) that was stored. Essential. See Object documentation
+         whatIsAPrimitive."
- at: index put: value
-        "Primitive. Assumes receiver is indexable. Store the argument value in
-        the indexable element of the receiver indicated by index. Fail if the
-        index is not an Integer or is out of bounds. Or fail if the value is not of
-        the right type for this kind of collection. Answer the value that was
-        stored. Essential. See Object documentation whatIsAPrimitive."
 
+        <primitive: 61 error: ec>
-        <primitive: 61>
         index isInteger ifTrue:
                 [self class isVariable
+                        ifTrue:
+                                [(index >= 1 and: [index <= self size])
+                                        ifTrue:
+                                                [ec == #'no modification' ifTrue:
+                                                        [^thisContext modificationForbiddenFor: self at: index put: anObject].
+                                                 self errorImproperStore]
-                        ifTrue: [(index >= 1 and: [index <= self size])
-                                        ifTrue: [self errorImproperStore]
                                         ifFalse: [self errorSubscriptBounds: index]]
                         ifFalse: [self errorNotIndexable]].
+        index isNumber ifTrue:
+                [^self at: index asInteger put: anObject].
+        self errorNonIntegerIndex!
-        index isNumber
-                ifTrue: [^self at: index asInteger put: value]
-                ifFalse: [self errorNonIntegerIndex]!

Item was added:
+ ----- Method: Object>>attemptToAssign:withIndex: (in category 'write barrier') -----
+ attemptToAssign: value withIndex: index
+        "Called by the VM when assigning an instance variable of an immutable object.
+        Upon return, executing will resume *after* the inst var assignment. If the inst var mutation has to be
+        performed, do it manually here in the call back with instVarAt:put:.
+        This method has to return *no* value by jumping to the context's sender"
+       
+        thisContext modificationForbiddenFor: self instVarAt: index put: value.
+
+        thisContext sender jump
+        "CAN'T REACH"!

Item was changed:
  ----- Method: Object>>basicAt:put: (in category 'accessing') -----
+ basicAt: index put: anObject
+        "Primitive. Assumes receiver is indexable. Store the argument anObject in
+         the indexable element of the receiver indicated by index. Fail if the  index
+         is not an Integer or is out of bounds, or if the receiver is read-only, or if
+         anObject is not of the right type for this kind of collection. Answer the
+         value (anObject) that was stored. Essential. Do not override in a subclass.
+         See Object documentation whatIsAPrimitive."
- basicAt: index put: value
-        "Primitive. Assumes receiver is indexable. Store the second argument
-        value in the indexable element of the receiver indicated by index. Fail
-        if the index is not an Integer or is out of bounds. Or fail if the value is
-        not of the right type for this kind of collection. Answer the value that
-        was stored. Essential. Do not override in a subclass. See Object
-        documentation whatIsAPrimitive."
 
+        <primitive: 61 error: ec>
+        index isInteger ifTrue:
+                [self class isVariable
+                        ifTrue:
+                                [(index >= 1 and: [index <= self basicSize])
+                                        ifTrue:
+                                                [ec == #'no modification' ifTrue:
+                                                        [^thisContext modificationForbiddenFor: self at: index put: anObject].
+                                                 self errorImproperStore]
+                                        ifFalse: [self errorSubscriptBounds: index]]
+                        ifFalse: [self errorNotIndexable]].
+        index isNumber ifTrue:
+                [^self basicAt: index asInteger put: anObject].
+        self errorNonIntegerIndex!
-        <primitive: 61>
-        index isInteger
-                ifTrue: [(index >= 1 and: [index <= self size])
-                                        ifTrue: [self errorImproperStore]
-                                        ifFalse: [self errorSubscriptBounds: index]].
-        index isNumber
-                ifTrue: [^self basicAt: index asInteger put: value]
-                ifFalse: [self errorNonIntegerIndex]!

Item was added:
+ ----- Method: Object>>beReadOnlyObject (in category 'write barrier') -----
+ beReadOnlyObject
+        "If the VM supports read-only objects it will not write to read-only objects.
+         An attempt to write to an instance variable of a read-only object will
+         cause the VM to send attemptToAssign:withIndex: to the read-only object.
+         An attempt to modify a read-only object in a primitive will cause the
+         primitive to fail with a #'no modification' error code.
+         Set the read-only flag of the receiver to true and answer the previous vaue of the flag."
+        ^self setIsReadOnlyObject: true!

Item was added:
+ ----- Method: Object>>beWritableObject (in category 'write barrier') -----
+ beWritableObject
+        "If the VM supports read-only objects it will not write to read-only objects.
+         An attempt to write to an instance variable of a read-only object will
+         cause the VM to send attemptToAssign:withIndex: to the read-only object.
+         An attempt to modify a read-only object in a primitive will cause the
+         primitive to fail with a #'no modification' error code.
+         Set the read-only flag of the receiver to false and answer the previous vaue of the flag."
+        ^self setIsReadOnlyObject: false!

Item was changed:
  ----- Method: Object>>instVarAt:put: (in category 'system primitives') -----
  instVarAt: index put: anObject
         "Primitive. Store a value into a fixed variable in an object. The numbering of the
          variables corresponds to the named instance variables, followed by the indexed
          instance variables. Fail if the index is not an Integer or is not the index of a fixed
+         or indexed variable, or if the receiver is read-only.
+         Essential. See Object documentation whatIsAPrimitive."
-         or indexed variable. Essential. See Object documentation whatIsAPrimitive."
 
         <primitive: 174 error: ec>
+        ec == #'no modification' ifTrue:
+                [^thisContext modificationForbiddenFor: self instVarAt: index put: anObject].
         self primitiveFailed!

Item was added:
+ ----- Method: Object>>isReadOnlyObject (in category 'write barrier') -----
+ isReadOnlyObject
+        "Answer if the receiver is read-only.
+         If the VM supports read-only objects it will not write to read-only objects.
+         An attempt to write to an instance variable of a read-only object will
+         cause the VM to send attemptToAssign:withIndex: to the read-only object.
+         An attempt to modify a read-only object in a primitive will cause the
+         primitive to fail with a #'no modification' error code."
+        <primitive: 163 error: ec>
+        ^self class isImmediateClass!

Item was changed:
  ----- Method: Object>>primitiveChangeClassTo: (in category 'system primitives') -----
  primitiveChangeClassTo: anObject
+        "Primitive. Change the class of the receiver into the class of the argument
+         given that the format of the receiver matches the format of the argument's
+         class. Fail if receiver or argument are immediates (SmallIntegers, Characters
+         or SmallFloat64s), or when the format of the receiver is different from the
+         format of the argument's class, or when the arguments class is fixed and
+         the receiver's size differs from the size that an instance of the argument's
+         class should have.
-        "Primitive. Change the class of the receiver into the class of the argument given that the format of the receiver matches the format of the argument's class. Fail if receiver or argument are SmallIntegers, or the receiver is an instance of a compact class and the argument isn't, or when the argument's class is compact and the receiver isn't, or when the format of the receiver is different from the format of the argument's class, or when the arguments class is fixed and the receiver's size differs from the size that an instance of the argument's class should have.
-        Note: The primitive will fail in most cases that you think might work. This is mostly because of a) the difference between compact and non-compact classes, and b) because of differences in the format. As an example, '(Array new: 3) primitiveChangeClassTo: Morph basicNew' would fail for three of the reasons mentioned above. Array is compact, Morph is not (failure #1). Array is variable and Morph is fixed (different format - failure #2). Morph is a fixed-field-only object and the array is too short (failure #3).
-        The facility is really provided for certain, very specific applications (mostly related to classes changing shape) and not for casual use."
 
+        Note: The primitive will fail in cases that you think might work. This is mostly
+         because of because of differences in the format. As an example,
+                '(Array new: 3) primitiveChangeClassTo: Morph basicNew'
+         would fail because Morph is a fixed-field-only object with about 6 instance
+         variables, and the array is too short (failure #3).
+
+         The facility is really provided for certain, very specific applications (mostly related to classes changing shape) and not for casual use."
+
+        <primitive: 115 error: ec>
+        ec == #'no modification' ifTrue:
+                [^anObject class modificationForbiddenAdopting: self].
-        <primitive: 115>
         self primitiveFailed!

Item was added:
+ ----- Method: Object>>setIsReadOnlyObject: (in category 'write barrier') -----
+ setIsReadOnlyObject: aBoolean
+        "If the VM supports read-only objects it will not write to read-only objects.
+         An attempt to write to an instance variable of a read-only object will
+         cause the VM to send attemptToAssign:withIndex: to the read-only object.
+         An attempt to modify a read-only object in a primitive will cause the
+         primitive to fail with a #'no modification' error code.
+         This primitive sets the read-only flag of the receiver to the given
+         value and answers the previous vaue of the flag.
+         Note: Some objects can't be read-only, currently contexts and objects related
+         to process scheduling (Processor, Process instances, Semaphore instances, ...)"
+        <primitive: 164 error: ec>
+        ^self primitiveFailed!



Carpe Squeak!