[View]  [Edit]  [Lock]  [References]  [Attachments]  [History]  [Home]  [Changes]  [Search]  [Help] 

Solution to bugs on block closures

CaseProposed solutionSolved?
example2Put the value to return in the thrown exception, evaluating the return expressions before throw.Uploaded Image: ok.gif
example1Use an unique object literalUploaded Image: red.gif

Compiler changes

The changes (in Compiler) to apply the patch follows

! Compiler methodsFor: #generation !
generate: fName early: early return: code
	" Private - Generate code to catch early return of code expression in fName. "

	early ifFalse: [ ^stream nextPutAll: code ].
	stream nextPutAll: 'var $s8Ret$={name:"stReturn"};try{'; nextPutAll: code.
	stream nextPutAll: '} catch($$ex) {if($$ex === $s8Ret$){'.
	self emmitReturn: '$$ex.result' early: true.
	stream nextPutAll: '} throw($$ex);}'! !

! Compiler methodsFor: #visiting !
visitReturnNode: aNode

	| theReturn |
	nestedBlocks > 0 ifTrue: [ earlyReturn := true ].
	theReturn := [
		self emmitReturn: [
			aNode nodes do: [:each | self visit: each ]
		]
	].
	earlyReturn ifFalse: [ ^theReturn value ].
	stream nextPutAll: $(.
	self emmitFunctionDoing: [
		stream nextPutAll: '$s8Ret$.result=('.
		self emmitFunctionDoing: theReturn.
		stream nextPutAll: ')();throw($s8Ret$)'
	].
	stream nextPutAll: ')()'.! !


Bindings for the changes

If we use the current compiler to fileIn the patch, the emmited methods do not work. We need to inject a hand-made version of the methods, and recompile the system.


smalltalk.bind(smalltalk.Compiler,"generate:early:return:",0 ,function (fName,early,code){
var $s8Ret$={name:"stReturn"};
try{var self=this;((early).mustBeBoolean()==true ? nil : (function(){return (function(){
  $s8Ret$.result=(function(){return self['@stream'].nextPutAll_(code);})();
  throw($s8Ret$)
})();})());
 (function($rec){$rec.nextPutAll_(unescape("var%20%24s8Ret%24%3D%7Bname%3A%22stReturn%22%7D%3Btry%7B"));return $rec.nextPutAll_(code);})
(self['@stream']); self['@stream'].nextPutAll_(unescape("%7D%20catch%28%24%24ex%29%20%7Bif%28%24%24ex%20%3D%3D%3D%20%24s8Ret%24%29%7B"));
self.emmitReturn_early_("$$ex.result", true);
self['@stream'].nextPutAll_(unescape("%7D%20throw%28%24%24ex%29%3B%7D"));return self;
} catch($$ex) {if($$ex === $s8Ret$){return $$ex.result;} throw($$ex);}} 

,"generation",unescape("generate%3A%20fName%20early%3A%20early%20return%3A%20code%0A%09%22%20Private%20-%20Generate%20code%20to%20catch%20early%20return%20of%20code%20expression%20in%20fName.%20%22%0A%0A%09early%20ifFalse%3A%20%5B%20%5Estream%20nextPutAll%3A%20code%20%5D.%0A%09stream%20nextPutAll%3A%20%27var%20%24s8Ret%24%3D%7Bname%3A%22stReturn%22%7D%3Btry%7B%27%3B%20nextPutAll%3A%20code.%0A%09stream%20nextPutAll%3A%20%27%7D%20catch%28%24%24ex%29%20%7Bif%28%24%24ex%20%3D%3D%3D%20%24s8Ret%24%29%7B%27.%0A%09self%20emmitReturn%3A%20%27%24%24ex.result%27%20early%3A%20true.%0A%09stream%20nextPutAll%3A%20%27%7D%20throw%28%24%24ex%29%3B%7D%27"));


smalltalk.bind(smalltalk.Compiler,"visitReturnNode:",0 ,function (aNode){
var $s8Ret$={name:"stReturn"};try{
var self=this;var theReturn=nil;((self['@nestedBlocks']._gt((0))).mustBeBoolean()==true ? 
(function(){return self['@earlyReturn']=true;})() : nil); 
theReturn=(function(){return self.emmitReturn_((function(){return aNode.nodes().do_((function(each){return self.visit_(each);}));}));}); 
((self['@earlyReturn']).mustBeBoolean()==true ? nil : (function(){
  return (function(){
  $s8Ret$.result=(function(){return theReturn.value();})();
  throw($s8Ret$)
})();})());
 self['@stream'].nextPutAll_(unescape("%28")); self.emmitFunctionDoing_((function(){self['@stream'].nextPutAll_(unescape("%24s8Ret%24.result%3D%28"));
self.emmitFunctionDoing_(theReturn);
return self['@stream'].nextPutAll_(unescape("%29%28%29%3Bthrow%28%24s8Ret%24%29"));}));
self['@stream'].nextPutAll_(unescape("%29%28%29"));return self;
} catch($$ex) {if($$ex === $s8Ret$){return $$ex.result;} throw($$ex);}}

,"visiting",unescape("visitReturnNode%3A%20aNode%0A%0A%09%7C%20theReturn%20%7C%0A%09nestedBlocks%20%3E%200%20ifTrue%3A%20%5B%20earlyReturn%20%3A%3D%20true%20%5D.%0A%09theReturn%20%3A%3D%20%5B%0A%09%09self%20emmitReturn%3A%20%5B%0A%09%09%09aNode%20nodes%20do%3A%20%5B%3Aeach%20%7C%20self%20visit%3A%20each%20%5D%0A%09%09%5D%0A%09%5D.%0A%09earlyReturn%20ifFalse%3A%20%5B%20%5EtheReturn%20value%20%5D.%0A%09stream%20nextPutAll%3A%20%24%28.%0A%09self%20emmitFunctionDoing%3A%20%5B%0A%09%09stream%20nextPutAll%3A%20%27%24s8Ret%24.result%3D%28%27.%0A%09%09self%20emmitFunctionDoing%3A%20theReturn.%0A%09%09stream%20nextPutAll%3A%20%27%29%28%29%3Bthrow%28%24s8Ret%24%29%27%0A%09%5D.%0A%09stream%20nextPutAll%3A%20%27%29%28%29%27."));



Recompiled version

The following code is the code emmited by the Compiler AFTER applying the patch

smalltalk.bind(smalltalk.Compiler,"generate:early:return:",0 ,function (fName,early,code){var $s8Ret$={name:"stReturn"};try{var self=this;((early).mustBeBoolean()==true ? nil : (function(){return (function(){$s8Ret$.result=(function(){return self['@stream'].nextPutAll_(code);})();throw($s8Ret$)})();})()); (function($rec){$rec.nextPutAll_(unescape("var%20%24s8Ret%24%3D%7Bname%3A%22stReturn%22%7D%3Btry%7B"));return $rec.nextPutAll_(code);})(self['@stream']); self['@stream'].nextPutAll_(unescape("%7D%20catch%28%24%24ex%29%20%7Bif%28%24%24ex%20%3D%3D%3D%20%24s8Ret%24%29%7B")); self.emmitReturn_early_("$$ex.result", true); self['@stream'].nextPutAll_(unescape("%7D%20throw%28%24%24ex%29%3B%7D"));return self;} catch($$ex) {if($$ex === $s8Ret$){return $$ex.result;} throw($$ex);}} ,"generation",unescape("generate%3A%20fName%20early%3A%20early%20return%3A%20code%0A%09%22%20Private%20-%20Generate%20code%20to%20catch%20early%20return%20of%20code%20expression%20in%20fName.%20%22%0A%0A%09early%20ifFalse%3A%20%5B%20%5Estream%20nextPutAll%3A%20code%20%5D.%0A%09stream%20nextPutAll%3A%20%27var%20%24s8Ret%24%3D%7Bname%3A%22stReturn%22%7D%3Btry%7B%27%3B%20nextPutAll%3A%20code.%0A%09stream%20nextPutAll%3A%20%27%7D%20catch%28%24%24ex%29%20%7Bif%28%24%24ex%20%3D%3D%3D%20%24s8Ret%24%29%7B%27.%0A%09self%20emmitReturn%3A%20%27%24%24ex.result%27%20early%3A%20true.%0A%09stream%20nextPutAll%3A%20%27%7D%20throw%28%24%24ex%29%3B%7D%27"));
smalltalk.bind(smalltalk.Compiler,"visitReturnNode:",0 ,function (aNode){var $s8Ret$={name:"stReturn"};try{var self=this;var theReturn=nil;((self['@nestedBlocks']._gt((0))).mustBeBoolean()==true ? (function(){return self['@earlyReturn']=true;})() : nil); theReturn=(function(){return self.emmitReturn_((function(){return aNode.nodes().do_((function(each){return self.visit_(each);}));}));}); ((self['@earlyReturn']).mustBeBoolean()==true ? nil : (function(){return (function(){$s8Ret$.result=(function(){return theReturn.value();})();throw($s8Ret$)})();})()); self['@stream'].nextPutAll_(unescape("%28")); self.emmitFunctionDoing_((function(){self['@stream'].nextPutAll_(unescape("%24s8Ret%24.result%3D%28"));self.emmitFunctionDoing_(theReturn);return self['@stream'].nextPutAll_(unescape("%29%28%29%3Bthrow%28%24s8Ret%24%29"));})); self['@stream'].nextPutAll_(unescape("%29%28%29"));return self;} catch($$ex) {if($$ex === $s8Ret$){return $$ex.result;} throw($$ex);}} ,"visiting",unescape("visitReturnNode%3A%20aNode%0A%0A%09%7C%20theReturn%20%7C%0A%09nestedBlocks%20%3E%200%20ifTrue%3A%20%5B%20earlyReturn%20%3A%3D%20true%20%5D.%0A%09theReturn%20%3A%3D%20%5B%0A%09%09self%20emmitReturn%3A%20%5B%0A%09%09%09aNode%20nodes%20do%3A%20%5B%3Aeach%20%7C%20self%20visit%3A%20each%20%5D%0A%09%09%5D%0A%09%5D.%0A%09earlyReturn%20ifFalse%3A%20%5B%20%5EtheReturn%20value%20%5D.%0A%09stream%20nextPutAll%3A%20%24%28.%0A%09self%20emmitFunctionDoing%3A%20%5B%0A%09%09stream%20nextPutAll%3A%20%27%24s8Ret%24.result%3D%28%27.%0A%09%09self%20emmitFunctionDoing%3A%20theReturn.%0A%09%09stream%20nextPutAll%3A%20%27%29%28%29%3Bthrow%28%24s8Ret%24%29%27%0A%09%5D.%0A%09stream%20nextPutAll%3A%20%27%29%28%29%27."));