Class ClosureExtractor


  • public class ClosureExtractor
    extends CAstRewriterExt
    A CAst rewriter for extracting bits of code into one-shot closures. What to extract is determined by an ExtractionPolicy.

    For instance, a ForInBodyExtractionPolicy extracts the body of every for-in loop in the program, whereas a CorrelatedPairExtractionPolicy extracts pieces of code containing correlated property reads and writes of the same property.

    As an example, consider the following function:

       function extend(dest, src) {
         for(var p in src)
           dest[p] = src[p];
       }
     

    Under both ForInBodyExtractionPolicy and CorrelatedPairExtractionPolicy, this should be transformed into

       function extend(dest, src) {
         for(var p in src)
           (function _forin_body_0(p) {
             dest[p] = src[p];
           })(p);
       }
     

    There are four issues to be considered here.

    • References to this:

      If the code to extract contains references to this, these references have to be rewritten; otherwise they would refer to the global object in the transformed code.

      We do this by giving the extracted function an extra parameter thi$, and rewriting this to thi$ within the extracted code.

      For instance,

         Object.prototype.extend = function(src) {
           for(var p in src)
             this[p] = src[p];
         }
         

      becomes

         Object.prototype.extend = function(src) {
           for(var p in src)
             (function _forin_body_0(p, thi$) {
               thi$[p] = src[p];
             })(p, this);
         }
         
    • Local variable declarations:

      Local variable declarations inside the extracted code have to be hoisted to the enclosing function; otherwise they would become local variables of the extracted function instead.

      This is already taken care of by the translation from Rhino's AST to CAst.

      Optionally, the policy can request that one local variable of the surrounding function be turned into a local variable of the extracted closure. The rewriter checks that this is possible: the code to extract must not contain function calls or new expressions, and it must not contain break, continue, or return statements. The former requirement prevents a called function from observing a different value of the local variable than before. The latter requirement is necessary because the final value of the localised variable needs to be returned and assigned to its counterpart in the surrounding function; since non-local jumps are encoded by special return values (see next item), this would no longer be possible.

    • break, continue, return:

      A break or continue statement within the extracted loop body that refers to the loop itself or an enclosing loop would become invalid in the transformed code. A return statement would no longer return from the enclosing function, but instead from the extracted function.

      We transform all three statements into return statements returning an object literal with a property type indicating whether this is a 'goto' (i.e., break or return) or a 'return'. In the former case, the 'target' property contains an integer identifying the jump target; in the latter case, the 'value' property contains the value to return.

      The return value of the extracted function is then examined to determine whether it completed normally (i.e., returned undefined), or whether it returned an object indicating special control flow.

      For example, consider this code from MooTools:

         for(var style in Element.ShortStyles) {
           if(property != style)
             continue;
           for(var s in Element.ShortStyles[style])
             result.push(this.getStyle(s));
           return result.join(' ');
         }
         

      Under ForInBodyExtractionPolicy, this is transformed into

         for(var style in Element.ShortStyles) {
           var s;
           re$ = (function _forin_body_0(style, thi$) {
             if(property != style)
               return { type: 'goto', target: 1 };
             for(s in Element.ShortStyles[style]) {
               (function _forin_body_2(s) {
                 result.push(thi$.getStyle(s));
               })(s);
             }
             return { type: 'return', value: result.join(' ') };
           })(style, this);
           if(re$) {
             if(re$.type == 'return')
               return re$.value;
             if(re$.type == 'goto') {
               if(re$.target == 1)
                 continue;
             }
           }
         }
         

      Note that at the CAst level, break and continue are represented as goto statements, which simplifies the translation somewhat. The numerical encoding of jump targets does not matter as long as the extracted function and the fixup code agree on which number represents which label.

    • Assignment to loop variable:

      The loop body may assign to the loop variable. If the variable is referenced after the loop, this assignment needs to be propagated back to the enclosing function in the extracted code.

      TODO: This is not handled at the moment.

    Finally, note that exceptions do not need to be handled specially.

    • Nested Class Summary

      • Nested classes/interfaces inherited from class com.ibm.wala.cast.tree.rewrite.CAstRewriter

        com.ibm.wala.cast.tree.rewrite.CAstRewriter.CopyKey<Self extends com.ibm.wala.cast.tree.rewrite.CAstRewriter.CopyKey<Self>>, com.ibm.wala.cast.tree.rewrite.CAstRewriter.Rewrite, com.ibm.wala.cast.tree.rewrite.CAstRewriter.RewriteContext<K extends com.ibm.wala.cast.tree.rewrite.CAstRewriter.CopyKey<K>>
    • Field Summary

      • Fields inherited from class com.ibm.wala.cast.tree.rewrite.CAstRewriter

        Ast, DEBUG, recursive, rootContext
    • Method Summary

      All Methods Instance Methods Concrete Methods 
      Modifier and Type Method Description
      protected com.ibm.wala.cast.tree.CAstNode copyNodes​(com.ibm.wala.cast.tree.CAstNode root, com.ibm.wala.cast.tree.CAstControlFlowMap cfg, NodePos context, java.util.Map<com.ibm.wala.util.collections.Pair<com.ibm.wala.cast.tree.CAstNode,​com.ibm.wala.cast.tree.rewrite.CAstBasicRewriter.NoKey>,​com.ibm.wala.cast.tree.CAstNode> nodeMap)  
      protected void enterEntity​(com.ibm.wala.cast.tree.CAstEntity entity)  
      protected void leaveEntity()  
      • Methods inherited from class com.ibm.wala.cast.tree.rewrite.CAstRewriter

        copyChildrenArray, copyChildrenArrayAndTargets, copySource, copySubtreesIntoNewNode, copySubtreesIntoNewNode, copyTypes, rewrite
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • Constructor Detail

      • ClosureExtractor

        public ClosureExtractor​(com.ibm.wala.cast.tree.CAst Ast,
                                ExtractionPolicyFactory policyFactory)
    • Method Detail

      • enterEntity

        protected void enterEntity​(com.ibm.wala.cast.tree.CAstEntity entity)
        Overrides:
        enterEntity in class CAstRewriterExt
      • copyNodes

        protected com.ibm.wala.cast.tree.CAstNode copyNodes​(com.ibm.wala.cast.tree.CAstNode root,
                                                            com.ibm.wala.cast.tree.CAstControlFlowMap cfg,
                                                            NodePos context,
                                                            java.util.Map<com.ibm.wala.util.collections.Pair<com.ibm.wala.cast.tree.CAstNode,​com.ibm.wala.cast.tree.rewrite.CAstBasicRewriter.NoKey>,​com.ibm.wala.cast.tree.CAstNode> nodeMap)
        Specified by:
        copyNodes in class com.ibm.wala.cast.tree.rewrite.CAstRewriter<NodePos,​com.ibm.wala.cast.tree.rewrite.CAstBasicRewriter.NoKey>