About exception propagation with ensure:
stéphane ducasse
ducasse at iam.unibe.ch
Fri Oct 28 13:23:28 UTC 2005
Hi guys
We got a really strange problem in the classBuilder code.
Here is the story
Context
=======
When the method name: className inEnvironment: env subclassOf:
newSuper type: type instanceVariableNames: instVarString
classVariableNames: classVarString poolDictionaries: poolString
category: category unsafe: unsafe
is redefined as follow. It works and fixes some bugs because Behavior
was not flushed correctly.
All the tests are green for example
ClassBuilderFormatTests>> testByteVariableSubclass
in particular
self should:[self makeIVarsSubclassOf: baseClass] raise: Error.
works ie the exception is raised.
name: className inEnvironment: env subclassOf: newSuper type: type
instanceVariableNames: instVarString classVariableNames:
classVarString poolDictionaries: poolString category: category
unsafe: unsafe
"Define a new class in the given environment.
If unsafe is true do not run any validation checks.
This facility is provided to implement important system changes."
| oldClass newClass organization instVars classVars force
needNew oldCategory copyOfOldClass newCategory |
environ _ env.
instVars _ Scanner new scanFieldNames: instVarString.
classVars _ (Scanner new scanFieldNames: classVarString)
collect: [:x | x asSymbol].
"Validate the proposed name"
unsafe ifFalse:[(self validateClassName: className) ifFalse:
[^nil]].
oldClass _ env at: className ifAbsent:[nil].
oldClass isBehavior
ifFalse: [oldClass _ nil] "Already checked in
#validateClassName:"
ifTrue: [
copyOfOldClass _ oldClass copy.
copyOfOldClass superclass addSubclass: copyOfOldClass].
[unsafe ifFalse:[
"Run validation checks so we know that we have a good chance
for recompilation"
(self validateSuperclass: newSuper forSubclass: oldClass)
ifFalse:[^nil].
(self validateInstvars: instVars from: oldClass forSuper:
newSuper) ifFalse:[^nil].
(self validateClassvars: classVars from: oldClass forSuper:
newSuper) ifFalse:[^nil].
(self validateSubclassFormat: type from: oldClass forSuper:
newSuper extra: instVars size) ifFalse:[^nil]].
"self halt: 'after validate'."
"See if we need a new subclass"
needNew _ self needsSubclassOf: newSuper type: type
instanceVariables: instVars from: oldClass.
needNew == nil ifTrue:[^nil]. "some error"
(needNew and:[unsafe not]) ifTrue:[
"Make sure we don't redefine any dangerous classes"
(self tooDangerousClasses includes: oldClass name) ifTrue:[
self error: oldClass name, ' cannot be changed'.
].
"Check if the receiver should not be redefined"
(oldClass ~~ nil and:[oldClass shouldNotBeRedefined]) ifTrue:[
self notify: oldClass name asText allBold,
' should not be redefined. \Proceed to store
over it.' withCRs]].
"self halt."
needNew ifTrue:[
"Create the new class"
newClass _ self
newSubclassOf: newSuper
type: type
instanceVariables: instVars
from: oldClass.
newClass == nil ifTrue:[^nil]. "Some error"
newClass setName: className.
] ifFalse:[
"Reuse the old class"
newClass _ oldClass.
].
"Install the class variables and pool dictionaries... "
force _ (newClass declare: classVarString) | (newClass sharing:
poolString).
"... classify ..."
newCategory _ category asSymbol.
organization _ environ ifNotNil:[environ organization].
oldClass isNil ifFalse: [oldCategory _ (organization
categoryOfElement: oldClass name) asSymbol].
organization classify: newClass name under: newCategory.
newClass environment: environ.
"... recompile ..."
newClass _ self recompile: force from: oldClass to: newClass
mutate: false.
"... export if not yet done ..."
(environ at: newClass name ifAbsent:[nil]) == newClass ifFalse:[
[environ at: newClass name put: newClass]
on: AttemptToWriteReadOnlyGlobal do:[:ex| ex resume: true].
Smalltalk flushClassNameCache.
].
newClass doneCompiling.
"... notify interested clients ..."
oldClass isNil ifTrue: [
SystemChangeNotifier uniqueInstance classAdded: newClass
inCategory: newCategory.
^ newClass].
SystemChangeNotifier uniqueInstance classDefinitionChangedFrom:
copyOfOldClass to: newClass.
newCategory ~= oldCategory
ifTrue: [SystemChangeNotifier uniqueInstance class: newClass
recategorizedFrom: oldCategory to: category].
] ensure:
[copyOfOldClass ifNotNil: [copyOfOldClass superclass
removeSubclass: copyOfOldClass].
Behavior flushObsoleteSubclasses.].
"apparently when the following expression is inside the
ensure block the exception is not
propagated. One tiny examples we could not reproduce this
behavior."
^newClass
Problem:
======
Now if you move the ^newClass into the ensure block
as
ensure: [copyOfOldClass ifNotNil: [copyOfOldClass superclass
removeSubclass: copyOfOldClass].
Behavior flushObsoleteSubclasses.
^newClass ].
The test does not work
self should:[self makeIVarsSubclassOf: baseClass] raise: Error.
works ie the exception is not raised.
So we check that ensure: propagate exception as shown below
testsOne
"self debug: #testsOne"
self should: [ [1 foo] ensure: [^1]] raise: MessageNotUnderstood
testsTwo
"self debug: #testsTwo"
self should: [ [1 error: 'hello'] ensure: [^1]] raise: Error.
self should: [ [1+2 ] ensure: [1 error: 'hello']] raise: Error
So we do not really understand.
Now our fixes in the classBuilder works but we are perplexed and not
satisfied not to understand.
Stef and adrian
More information about the Squeak-dev
mailing list
|