1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.ir;
  27 
  28 import java.io.PrintWriter;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.Collections;
  32 import java.util.Comparator;
  33 import java.util.LinkedHashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 import jdk.nashorn.internal.codegen.Label;
  37 import jdk.nashorn.internal.ir.annotations.Immutable;
  38 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
  39 
  40 /**
  41  * IR representation for a list of statements.
  42  */
  43 @Immutable
  44 public class Block extends Node implements BreakableNode, Terminal, Flags<Block> {
  45     private static final long serialVersionUID = 1L;
  46 
  47     /** List of statements */
  48     protected final List<Statement> statements;
  49 
  50     /** Symbol table - keys must be returned in the order they were put in. */
  51     protected final Map<String, Symbol> symbols;
  52 
  53     /** Entry label. */
  54     private final Label entryLabel;
  55 
  56     /** Break label. */
  57     private final Label breakLabel;
  58 
  59     /** Does the block/function need a new scope? */
  60     protected final int flags;
  61 
  62     /**
  63      * @see JoinPredecessor
  64      */
  65     private final LocalVariableConversion conversion;
  66 
  67     /** Flag indicating that this block needs scope */
  68     public static final int NEEDS_SCOPE = 1 << 0;
  69 
  70     /**
  71      * Is this block tagged as terminal based on its contents
  72      * (usually the last statement)
  73      */
  74     public static final int IS_TERMINAL = 1 << 2;
  75 
  76     /**
  77      * Is this block the eager global scope - i.e. the original program. This isn't true for the
  78      * outermost level of recompiles
  79      */
  80     public static final int IS_GLOBAL_SCOPE = 1 << 3;
  81 
  82     /**
  83      * Constructor
  84      *
  85      * @param token      The first token of the block
  86      * @param finish     The index of the last character
  87      * @param flags      The flags of the block
  88      * @param statements All statements in the block
  89      */
  90     public Block(final long token, final int finish, final int flags, final Statement... statements) {
  91         super(token, finish);
  92 
  93         this.statements = Arrays.asList(statements);
  94         this.symbols    = new LinkedHashMap<>();
  95         this.entryLabel = new Label("block_entry");
  96         this.breakLabel = new Label("block_break");
  97         final int len = statements.length;
  98         final int terminalFlags = len > 0 && statements[len - 1].hasTerminalFlags() ? IS_TERMINAL : 0;
  99         this.flags = terminalFlags | flags;
 100         this.conversion = null;
 101     }
 102 
 103     /**
 104      * Constructs a new block
 105      *
 106      * @param token The first token of the block
 107      * @param finish The index of the last character
 108      * @param statements All statements in the block
 109      */
 110     public Block(final long token, final int finish, final Statement...statements){
 111         this(token, finish, 0, statements);
 112     }
 113 
 114     /**
 115      * Constructs a new block
 116      *
 117      * @param token The first token of the block
 118      * @param finish The index of the last character
 119      * @param statements All statements in the block
 120      */
 121     public Block(final long token, final int finish, final List<Statement> statements){
 122         this(token, finish, 0, statements);
 123     }
 124 
 125     /**
 126      * Constructor
 127      *
 128      * @param token      The first token of the block
 129      * @param finish     The index of the last character
 130      * @param flags      The flags of the block
 131      * @param statements All statements in the block
 132      */
 133     public Block(final long token, final int finish, final int flags, final List<Statement> statements) {
 134         this(token, finish, flags, statements.toArray(new Statement[statements.size()]));
 135     }
 136 
 137     private Block(final Block block, final int finish, final List<Statement> statements, final int flags, final Map<String, Symbol> symbols, final LocalVariableConversion conversion) {
 138         super(block, finish);
 139         this.statements = statements;
 140         this.flags      = flags;
 141         this.symbols    = new LinkedHashMap<>(symbols); //todo - symbols have no dependencies on any IR node and can as far as we understand it be shallow copied now
 142         this.entryLabel = new Label(block.entryLabel);
 143         this.breakLabel = new Label(block.breakLabel);
 144         this.conversion = conversion;
 145     }
 146 
 147     /**
 148      * Is this block the outermost eager global scope - i.e. the primordial program?
 149      * Used for global anchor point for scope depth computation for recompilation code
 150      * @return true if outermost eager global scope
 151      */
 152     public boolean isGlobalScope() {
 153         return getFlag(IS_GLOBAL_SCOPE);
 154     }
 155 
 156     /**
 157      * Clear the symbols in the block.
 158      * TODO: make this immutable.
 159      */
 160     public void clearSymbols() {
 161         symbols.clear();
 162     }
 163 
 164     @Override
 165     public Node ensureUniqueLabels(final LexicalContext lc) {
 166         return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
 167     }
 168 
 169     /**
 170      * Assist in IR navigation.
 171      *
 172      * @param visitor IR navigating visitor.
 173      * @return new or same node
 174      */
 175     @Override
 176     public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
 177         if (visitor.enterBlock(this)) {
 178             return visitor.leaveBlock(setStatements(lc, Node.accept(visitor, statements)));
 179         }
 180 
 181         return this;
 182     }
 183 
 184     /**
 185      * Get a copy of the list for all the symbols defined in this block
 186      * @return symbol iterator
 187      */
 188     public List<Symbol> getSymbols() {
 189         return Collections.unmodifiableList(new ArrayList<>(symbols.values()));
 190     }
 191 
 192     /**
 193      * Retrieves an existing symbol defined in the current block.
 194      * @param name the name of the symbol
 195      * @return an existing symbol with the specified name defined in the current block, or null if this block doesn't
 196      * define a symbol with this name.T
 197      */
 198     public Symbol getExistingSymbol(final String name) {
 199         return symbols.get(name);
 200     }
 201 
 202     /**
 203      * Test if this block represents a <tt>catch</tt> block in a <tt>try</tt> statement.
 204      * This is used by the Splitter as catch blocks are not be subject to splitting.
 205      *
 206      * @return true if this block represents a catch block in a try statement.
 207      */
 208     public boolean isCatchBlock() {
 209         return statements.size() == 1 && statements.get(0) instanceof CatchNode;
 210     }
 211 
 212     @Override
 213     public void toString(final StringBuilder sb, final boolean printType) {
 214         for (final Node statement : statements) {
 215             statement.toString(sb, printType);
 216             sb.append(';');
 217         }
 218     }
 219 
 220     /**
 221      * Print symbols in block in alphabetical order, sorted on name
 222      * Used for debugging, see the --print-symbols flag
 223      *
 224      * @param stream print writer to output symbols to
 225      *
 226      * @return true if symbols were found
 227      */
 228     public boolean printSymbols(final PrintWriter stream) {
 229         final List<Symbol> values = new ArrayList<>(symbols.values());
 230 
 231         Collections.sort(values, new Comparator<Symbol>() {
 232             @Override
 233             public int compare(final Symbol s0, final Symbol s1) {
 234                 return s0.getName().compareTo(s1.getName());
 235             }
 236         });
 237 
 238         for (final Symbol symbol : values) {
 239             symbol.print(stream);
 240         }
 241 
 242         return !values.isEmpty();
 243     }
 244 
 245     /**
 246      * Tag block as terminal or non terminal
 247      * @param lc          lexical context
 248      * @param isTerminal is block terminal
 249      * @return same block, or new if flag changed
 250      */
 251     public Block setIsTerminal(final LexicalContext lc, final boolean isTerminal) {
 252         return isTerminal ? setFlag(lc, IS_TERMINAL) : clearFlag(lc, IS_TERMINAL);
 253     }
 254 
 255     @Override
 256     public int getFlags() {
 257         return flags;
 258     }
 259 
 260     /**
 261      * Is this a terminal block, i.e. does it end control flow like ending with a throw or return?
 262      *
 263      * @return true if this node statement is terminal
 264      */
 265     @Override
 266     public boolean isTerminal() {
 267         return getFlag(IS_TERMINAL);
 268     }
 269 
 270     /**
 271      * Get the entry label for this block
 272      * @return the entry label
 273      */
 274     public Label getEntryLabel() {
 275         return entryLabel;
 276     }
 277 
 278     @Override
 279     public Label getBreakLabel() {
 280         return breakLabel;
 281     }
 282 
 283     @Override
 284     public Block setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
 285         if(this.conversion == conversion) {
 286             return this;
 287         }
 288         return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
 289     }
 290 
 291     @Override
 292     public LocalVariableConversion getLocalVariableConversion() {
 293         return conversion;
 294     }
 295 
 296     /**
 297      * Get the list of statements in this block
 298      *
 299      * @return a list of statements
 300      */
 301     public List<Statement> getStatements() {
 302         return Collections.unmodifiableList(statements);
 303     }
 304 
 305     /**
 306      * Returns the number of statements in the block.
 307      * @return the number of statements in the block.
 308      */
 309     public int getStatementCount() {
 310         return statements.size();
 311     }
 312 
 313     /**
 314      * Returns the line number of the first statement in the block.
 315      * @return the line number of the first statement in the block, or -1 if the block has no statements.
 316      */
 317     public int getFirstStatementLineNumber() {
 318         if(statements == null || statements.isEmpty()) {
 319             return -1;
 320         }
 321         return statements.get(0).getLineNumber();
 322     }
 323 
 324     /**
 325      * Returns the last statement in the block.
 326      * @return the last statement in the block, or null if the block has no statements.
 327      */
 328     public Statement getLastStatement() {
 329         return statements.isEmpty() ? null : statements.get(statements.size() - 1);
 330     }
 331 
 332     /**
 333      * Reset the statement list for this block
 334      *
 335      * @param lc lexical context
 336      * @param statements new statement list
 337      * @return new block if statements changed, identity of statements == block.statements
 338      */
 339     public Block setStatements(final LexicalContext lc, final List<Statement> statements) {
 340         if (this.statements == statements) {
 341             return this;
 342         }
 343         int lastFinish = 0;
 344         if (!statements.isEmpty()) {
 345             lastFinish = statements.get(statements.size() - 1).getFinish();
 346         }
 347         return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags, symbols, conversion));
 348     }
 349 
 350     /**
 351      * Add or overwrite an existing symbol in the block
 352      *
 353      * @param lc     get lexical context
 354      * @param symbol symbol
 355      */
 356     public void putSymbol(final LexicalContext lc, final Symbol symbol) {
 357         symbols.put(symbol.getName(), symbol);
 358     }
 359 
 360     /**
 361      * Check whether scope is necessary for this Block
 362      *
 363      * @return true if this function needs a scope
 364      */
 365     public boolean needsScope() {
 366         return (flags & NEEDS_SCOPE) == NEEDS_SCOPE;
 367     }
 368 
 369     @Override
 370     public Block setFlags(final LexicalContext lc, final int flags) {
 371         if (this.flags == flags) {
 372             return this;
 373         }
 374         return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
 375     }
 376 
 377     @Override
 378     public Block clearFlag(final LexicalContext lc, final int flag) {
 379         return setFlags(lc, flags & ~flag);
 380     }
 381 
 382     @Override
 383     public Block setFlag(final LexicalContext lc, final int flag) {
 384         return setFlags(lc, flags | flag);
 385     }
 386 
 387     @Override
 388     public boolean getFlag(final int flag) {
 389         return (flags & flag) == flag;
 390     }
 391 
 392     /**
 393      * Set the needs scope flag.
 394      * @param lc lexicalContext
 395      * @return new block if state changed, otherwise this
 396      */
 397     public Block setNeedsScope(final LexicalContext lc) {
 398         if (needsScope()) {
 399             return this;
 400         }
 401 
 402         return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags | NEEDS_SCOPE, symbols, conversion));
 403     }
 404 
 405     /**
 406      * Computationally determine the next slot for this block,
 407      * indexed from 0. Use this as a relative base when computing
 408      * frames
 409      * @return next slot
 410      */
 411     public int nextSlot() {
 412         int next = 0;
 413         for (final Symbol symbol : getSymbols()) {
 414             if (symbol.hasSlot()) {
 415                 next += symbol.slotCount();
 416             }
 417         }
 418         return next;
 419     }
 420 
 421     @Override
 422     public boolean isBreakableWithoutLabel() {
 423         return false;
 424     }
 425 
 426     @Override
 427     public List<Label> getLabels() {
 428         return Collections.unmodifiableList(Arrays.asList(entryLabel, breakLabel));
 429     }
 430 
 431     @Override
 432     public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
 433         return Acceptor.accept(this, visitor);
 434     }
 435 }