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.IOException;
  29 import java.io.ObjectInputStream;
  30 import java.io.PrintWriter;
  31 import java.io.Serializable;
  32 import java.util.HashSet;
  33 import java.util.Set;
  34 import java.util.StringTokenizer;
  35 import jdk.nashorn.internal.codegen.types.Type;
  36 import jdk.nashorn.internal.runtime.Context;
  37 import jdk.nashorn.internal.runtime.Debug;
  38 import jdk.nashorn.internal.runtime.options.Options;
  39 
  40 /**
  41  * Symbol is a symbolic address for a value ("variable" if you wish). Identifiers in JavaScript source, as well as
  42  * certain synthetic variables created by the compiler are represented by Symbol objects. Symbols can address either
  43  * local variable slots in bytecode ("slotted symbol"), or properties in scope objects ("scoped symbol"). A symbol can
  44  * also end up being defined but then not used during symbol assignment calculations; such symbol will be neither
  45  * scoped, nor slotted; it represents a dead variable (it might be written to, but is never read). Finally, a symbol can
  46  * be both slotted and in scope. This special case can only occur with bytecode method parameters. They all come in as
  47  * slotted, but if they are used by a nested function (or eval) then they will be copied into the scope object, and used
  48  * from there onwards. Two further special cases are parameters stored in {@code NativeArguments} objects and parameters
  49  * stored in {@code Object[]} parameter to variable-arity functions. Those use the {@code #getFieldIndex()} property to
  50  * refer to their location.
  51  */
  52 
  53 public final class Symbol implements Comparable<Symbol>, Cloneable, Serializable {
  54     private static final long serialVersionUID = 1L;
  55 
  56     /** Is this Global */
  57     public static final int IS_GLOBAL   = 1;
  58     /** Is this a variable */
  59     public static final int IS_VAR      = 2;
  60     /** Is this a parameter */
  61     public static final int IS_PARAM    = 3;
  62     /** Mask for kind flags */
  63     public static final int KINDMASK = (1 << 2) - 1; // Kinds are represented by lower two bits
  64 
  65     /** Is this symbol in scope */
  66     public static final int IS_SCOPE                = 1 <<  2;
  67     /** Is this a this symbol */
  68     public static final int IS_THIS                 = 1 <<  3;
  69     /** Is this a let */
  70     public static final int IS_LET                  = 1 <<  4;
  71     /** Is this a const */
  72     public static final int IS_CONST                = 1 <<  5;
  73     /** Is this an internal symbol, never represented explicitly in source code */
  74     public static final int IS_INTERNAL             = 1 <<  6;
  75     /** Is this a function self-reference symbol */
  76     public static final int IS_FUNCTION_SELF        = 1 <<  7;
  77     /** Is this a function declaration? */
  78     public static final int IS_FUNCTION_DECLARATION = 1 <<  8;
  79     /** Is this a program level symbol? */
  80     public static final int IS_PROGRAM_LEVEL        = 1 <<  9;
  81     /** Are this symbols' values stored in local variable slots? */
  82     public static final int HAS_SLOT                = 1 << 10;
  83     /** Is this symbol known to store an int value ? */
  84     public static final int HAS_INT_VALUE           = 1 << 11;
  85     /** Is this symbol known to store a double value ? */
  86     public static final int HAS_DOUBLE_VALUE        = 1 << 12;
  87     /** Is this symbol known to store an object value ? */
  88     public static final int HAS_OBJECT_VALUE        = 1 << 13;
  89     /** Is this symbol seen a declaration? Used for block scoped LET and CONST symbols only. */
  90     public static final int HAS_BEEN_DECLARED       = 1 << 14;
  91 
  92     /** Null or name identifying symbol. */
  93     private final String name;
  94 
  95     /** Symbol flags. */
  96     private int flags;
  97 
  98     /** First bytecode method local variable slot for storing the value(s) of this variable. -1 indicates the variable
  99      * is not stored in local variable slots or it is not yet known. */
 100     private transient int firstSlot = -1;
 101 
 102     /** Field number in scope or property; array index in varargs when not using arguments object. */
 103     private transient int fieldIndex = -1;
 104 
 105     /** Number of times this symbol is used in code */
 106     private int useCount;
 107 
 108     /** Debugging option - dump info and stack trace when symbols with given names are manipulated */
 109     private static final Set<String> TRACE_SYMBOLS;
 110     private static final Set<String> TRACE_SYMBOLS_STACKTRACE;
 111 
 112     static {
 113         final String stacktrace = Options.getStringProperty("nashorn.compiler.symbol.stacktrace", null);
 114         final String trace;
 115         if (stacktrace != null) {
 116             trace = stacktrace; //stacktrace always implies trace as well
 117             TRACE_SYMBOLS_STACKTRACE = new HashSet<>();
 118             for (final StringTokenizer st = new StringTokenizer(stacktrace, ","); st.hasMoreTokens(); ) {
 119                 TRACE_SYMBOLS_STACKTRACE.add(st.nextToken());
 120             }
 121         } else {
 122             trace = Options.getStringProperty("nashorn.compiler.symbol.trace", null);
 123             TRACE_SYMBOLS_STACKTRACE = null;
 124         }
 125 
 126         if (trace != null) {
 127             TRACE_SYMBOLS = new HashSet<>();
 128             for (final StringTokenizer st = new StringTokenizer(trace, ","); st.hasMoreTokens(); ) {
 129                 TRACE_SYMBOLS.add(st.nextToken());
 130             }
 131         } else {
 132             TRACE_SYMBOLS = null;
 133         }
 134     }
 135 
 136     /**
 137      * Constructor
 138      *
 139      * @param name  name of symbol
 140      * @param flags symbol flags
 141      */
 142     public Symbol(final String name, final int flags) {
 143         this.name       = name;
 144         this.flags      = flags;
 145         if(shouldTrace()) {
 146             trace("CREATE SYMBOL " + name);
 147         }
 148     }
 149 
 150     @Override
 151     public Symbol clone() {
 152         try {
 153             return (Symbol)super.clone();
 154         } catch (final CloneNotSupportedException e) {
 155             throw new AssertionError(e);
 156         }
 157     }
 158 
 159     private static String align(final String string, final int max) {
 160         final StringBuilder sb = new StringBuilder();
 161         sb.append(string.substring(0, Math.min(string.length(), max)));
 162 
 163         while (sb.length() < max) {
 164             sb.append(' ');
 165         }
 166         return sb.toString();
 167     }
 168 
 169     /**
 170      * Debugging .
 171      *
 172      * @param stream Stream to print to.
 173      */
 174 
 175     void print(final PrintWriter stream) {
 176         final StringBuilder sb = new StringBuilder();
 177 
 178         sb.append(align(name, 20)).
 179             append(": ").
 180             append(", ").
 181             append(align(firstSlot == -1 ? "none" : "" + firstSlot, 10));
 182 
 183         switch (flags & KINDMASK) {
 184         case IS_GLOBAL:
 185             sb.append(" global");
 186             break;
 187         case IS_VAR:
 188             if (isConst()) {
 189                 sb.append(" const");
 190             } else if (isLet()) {
 191                 sb.append(" let");
 192             } else {
 193                 sb.append(" var");
 194             }
 195             break;
 196         case IS_PARAM:
 197             sb.append(" param");
 198             break;
 199         default:
 200             break;
 201         }
 202 
 203         if (isScope()) {
 204             sb.append(" scope");
 205         }
 206 
 207         if (isInternal()) {
 208             sb.append(" internal");
 209         }
 210 
 211         if (isThis()) {
 212             sb.append(" this");
 213         }
 214 
 215         if (isProgramLevel()) {
 216             sb.append(" program");
 217         }
 218 
 219         sb.append('\n');
 220 
 221         stream.print(sb.toString());
 222     }
 223 
 224     /**
 225      * Compare the the symbol kind with another.
 226      *
 227      * @param other Other symbol's flags.
 228      * @return True if symbol has less kind.
 229      */
 230     public boolean less(final int other) {
 231         return (flags & KINDMASK) < (other & KINDMASK);
 232     }
 233 
 234     /**
 235      * Allocate a slot for this symbol.
 236      *
 237      * @param needsSlot True if symbol needs a slot.
 238      * @return the symbol
 239      */
 240     public Symbol setNeedsSlot(final boolean needsSlot) {
 241         if(needsSlot) {
 242             assert !isScope();
 243             flags |= HAS_SLOT;
 244         } else {
 245             flags &= ~HAS_SLOT;
 246         }
 247         return this;
 248     }
 249 
 250     /**
 251      * Return the number of slots required for the symbol.
 252      *
 253      * @return Number of slots.
 254      */
 255     public int slotCount() {
 256         return ((flags & HAS_INT_VALUE)    == 0 ? 0 : 1) +
 257                ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2) +
 258                ((flags & HAS_OBJECT_VALUE) == 0 ? 0 : 1);
 259     }
 260 
 261     private boolean isSlotted() {
 262         return firstSlot != -1 && ((flags & HAS_SLOT) != 0);
 263     }
 264 
 265     @Override
 266     public String toString() {
 267         final StringBuilder sb = new StringBuilder();
 268 
 269         sb.append(name).
 270             append(' ');
 271 
 272         if (hasSlot()) {
 273             sb.append(' ').
 274                 append('(').
 275                 append("slot=").
 276                 append(firstSlot).append(' ');
 277             if((flags & HAS_INT_VALUE) != 0) { sb.append('I'); }
 278             if((flags & HAS_DOUBLE_VALUE) != 0) { sb.append('D'); }
 279             if((flags & HAS_OBJECT_VALUE) != 0) { sb.append('O'); }
 280             sb.append(')');
 281         }
 282 
 283         if (isScope()) {
 284             if(isGlobal()) {
 285                 sb.append(" G");
 286             } else {
 287                 sb.append(" S");
 288             }
 289         }
 290 
 291         return sb.toString();
 292     }
 293 
 294     @Override
 295     public int compareTo(final Symbol other) {
 296         return name.compareTo(other.name);
 297     }
 298 
 299     /**
 300      * Does this symbol have an allocated bytecode slot? Note that having an allocated bytecode slot doesn't necessarily
 301      * mean the symbol's value will be stored in it. Namely, a function parameter can have a bytecode slot, but if it is
 302      * in scope, then the bytecode slot will not be used. See {@link #isBytecodeLocal()}.
 303      *
 304      * @return true if this symbol has a local bytecode slot
 305      */
 306     public boolean hasSlot() {
 307         return (flags & HAS_SLOT) != 0;
 308     }
 309 
 310     /**
 311      * Is this symbol a local variable stored in bytecode local variable slots? This is true for a slotted variable that
 312      * is not in scope. (E.g. a parameter that is in scope is slotted, but it will not be a local variable).
 313      * @return true if this symbol is using bytecode local slots for its storage.
 314      */
 315     public boolean isBytecodeLocal() {
 316         return hasSlot() && !isScope();
 317     }
 318 
 319     /**
 320      * Returns true if this symbol is dead (it is a local variable that is statically proven to never be read in any type).
 321      * @return true if this symbol is dead
 322      */
 323     public boolean isDead() {
 324         return (flags & (HAS_SLOT | IS_SCOPE)) == 0;
 325     }
 326 
 327     /**
 328      * Check if this is a symbol in scope. Scope symbols cannot, for obvious reasons
 329      * be stored in byte code slots on the local frame
 330      *
 331      * @return true if this is scoped
 332      */
 333     public boolean isScope() {
 334         assert (flags & KINDMASK) != IS_GLOBAL || (flags & IS_SCOPE) == IS_SCOPE : "global without scope flag";
 335         return (flags & IS_SCOPE) != 0;
 336     }
 337 
 338     /**
 339      * Check if this symbol is a function declaration
 340      * @return true if a function declaration
 341      */
 342     public boolean isFunctionDeclaration() {
 343         return (flags & IS_FUNCTION_DECLARATION) != 0;
 344     }
 345 
 346     /**
 347      * Flag this symbol as scope as described in {@link Symbol#isScope()}
 348      * @return the symbol
 349      */
 350     public Symbol setIsScope() {
 351         if (!isScope()) {
 352             if(shouldTrace()) {
 353                 trace("SET IS SCOPE");
 354             }
 355             flags |= IS_SCOPE;
 356             if(!isParam()) {
 357                 flags &= ~HAS_SLOT;
 358             }
 359         }
 360         return this;
 361     }
 362 
 363     /**
 364      * Mark this symbol as a function declaration.
 365      */
 366     public void setIsFunctionDeclaration() {
 367         if (!isFunctionDeclaration()) {
 368             if(shouldTrace()) {
 369                 trace("SET IS FUNCTION DECLARATION");
 370             }
 371             flags |= IS_FUNCTION_DECLARATION;
 372         }
 373     }
 374 
 375     /**
 376      * Check if this symbol is a variable
 377      * @return true if variable
 378      */
 379     public boolean isVar() {
 380         return (flags & KINDMASK) == IS_VAR;
 381     }
 382 
 383     /**
 384      * Check if this symbol is a global (undeclared) variable
 385      * @return true if global
 386      */
 387     public boolean isGlobal() {
 388         return (flags & KINDMASK) == IS_GLOBAL;
 389     }
 390 
 391     /**
 392      * Check if this symbol is a function parameter
 393      * @return true if parameter
 394      */
 395     public boolean isParam() {
 396         return (flags & KINDMASK) == IS_PARAM;
 397     }
 398 
 399     /**
 400      * Check if this is a program (script) level definition
 401      * @return true if program level
 402      */
 403     public boolean isProgramLevel() {
 404         return (flags & IS_PROGRAM_LEVEL) != 0;
 405     }
 406 
 407     /**
 408      * Check if this symbol is a constant
 409      * @return true if a constant
 410      */
 411     public boolean isConst() {
 412         return (flags & IS_CONST) != 0;
 413     }
 414 
 415     /**
 416      * Check if this is an internal symbol, without an explicit JavaScript source
 417      * code equivalent
 418      * @return true if internal
 419      */
 420     public boolean isInternal() {
 421         return (flags & IS_INTERNAL) != 0;
 422     }
 423 
 424     /**
 425      * Check if this symbol represents {@code this}
 426      * @return true if this
 427      */
 428     public boolean isThis() {
 429         return (flags & IS_THIS) != 0;
 430     }
 431 
 432     /**
 433      * Check if this symbol is a let
 434      * @return true if let
 435      */
 436     public boolean isLet() {
 437         return (flags & IS_LET) != 0;
 438     }
 439 
 440     /**
 441      * Flag this symbol as a function's self-referencing symbol.
 442      * @return true if this symbol as a function's self-referencing symbol.
 443      */
 444     public boolean isFunctionSelf() {
 445         return (flags & IS_FUNCTION_SELF) != 0;
 446     }
 447 
 448     /**
 449      * Is this a block scoped symbol
 450      * @return true if block scoped
 451      */
 452     public boolean isBlockScoped() {
 453         return isLet() || isConst();
 454     }
 455 
 456     /**
 457      * Has this symbol been declared
 458      * @return true if declared
 459      */
 460     public boolean hasBeenDeclared() {
 461         return (flags & HAS_BEEN_DECLARED) != 0;
 462     }
 463 
 464     /**
 465      * Mark this symbol as declared
 466      */
 467     public void setHasBeenDeclared() {
 468         if (!hasBeenDeclared()) {
 469             flags |= HAS_BEEN_DECLARED;
 470         }
 471     }
 472 
 473     /**
 474      * Get the index of the field used to store this symbol, should it be an AccessorProperty
 475      * and get allocated in a JO-prefixed ScriptObject subclass.
 476      *
 477      * @return field index
 478      */
 479     public int getFieldIndex() {
 480         assert fieldIndex != -1 : "fieldIndex must be initialized " + fieldIndex;
 481         return fieldIndex;
 482     }
 483 
 484     /**
 485      * Set the index of the field used to store this symbol, should it be an AccessorProperty
 486      * and get allocated in a JO-prefixed ScriptObject subclass.
 487      *
 488      * @param fieldIndex field index - a positive integer
 489      * @return the symbol
 490      */
 491     public Symbol setFieldIndex(final int fieldIndex) {
 492         if (this.fieldIndex != fieldIndex) {
 493             this.fieldIndex = fieldIndex;
 494         }
 495         return this;
 496     }
 497 
 498     /**
 499      * Get the symbol flags
 500      * @return flags
 501      */
 502     public int getFlags() {
 503         return flags;
 504     }
 505 
 506     /**
 507      * Set the symbol flags
 508      * @param flags flags
 509      * @return the symbol
 510      */
 511     public Symbol setFlags(final int flags) {
 512         if (this.flags != flags) {
 513             this.flags = flags;
 514         }
 515         return this;
 516     }
 517 
 518     /**
 519      * Set a single symbol flag
 520      * @param flag flag to set
 521      * @return the symbol
 522      */
 523     public Symbol setFlag(final int flag) {
 524         if ((this.flags & flag) == 0) {
 525             this.flags |= flag;
 526         }
 527         return this;
 528     }
 529 
 530     /**
 531      * Clears a single symbol flag
 532      * @param flag flag to set
 533      * @return the symbol
 534      */
 535     public Symbol clearFlag(final int flag) {
 536         if ((this.flags & flag) != 0) {
 537             this.flags &= ~flag;
 538         }
 539         return this;
 540     }
 541 
 542     /**
 543      * Get the name of this symbol
 544      * @return symbol name
 545      */
 546     public String getName() {
 547         return name;
 548     }
 549 
 550     /**
 551      * Get the index of the first bytecode slot for this symbol
 552      * @return byte code slot
 553      */
 554     public int getFirstSlot() {
 555         assert isSlotted();
 556         return firstSlot;
 557     }
 558 
 559     /**
 560      * Get the index of the bytecode slot for this symbol for storing a value of the specified type.
 561      * @param type the requested type
 562      * @return byte code slot
 563      */
 564     public int getSlot(final Type type) {
 565         assert isSlotted();
 566         int typeSlot = firstSlot;
 567         if(type.isBoolean() || type.isInteger()) {
 568             assert (flags & HAS_INT_VALUE) != 0;
 569             return typeSlot;
 570         }
 571         typeSlot += ((flags & HAS_INT_VALUE) == 0 ? 0 : 1);
 572         if(type.isNumber()) {
 573             assert (flags & HAS_DOUBLE_VALUE) != 0;
 574             return typeSlot;
 575         }
 576         assert type.isObject();
 577         assert (flags & HAS_OBJECT_VALUE) != 0 : name;
 578         return typeSlot + ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2);
 579     }
 580 
 581     /**
 582      * Returns true if this symbol has a local variable slot for storing a value of specific type.
 583      * @param type the type
 584      * @return true if this symbol has a local variable slot for storing a value of specific type.
 585      */
 586     public boolean hasSlotFor(final Type type) {
 587         if(type.isBoolean() || type.isInteger()) {
 588             return (flags & HAS_INT_VALUE) != 0;
 589         } else if(type.isNumber()) {
 590             return (flags & HAS_DOUBLE_VALUE) != 0;
 591         }
 592         assert type.isObject();
 593         return (flags & HAS_OBJECT_VALUE) != 0;
 594     }
 595 
 596     /**
 597      * Marks this symbol as having a local variable slot for storing a value of specific type.
 598      * @param type the type
 599      */
 600     public void setHasSlotFor(final Type type) {
 601         if(type.isBoolean() || type.isInteger()) {
 602             setFlag(HAS_INT_VALUE);
 603         } else if(type.isNumber()) {
 604             setFlag(HAS_DOUBLE_VALUE);
 605         } else {
 606             assert type.isObject();
 607             setFlag(HAS_OBJECT_VALUE);
 608         }
 609     }
 610 
 611     /**
 612      * Increase the symbol's use count by one.
 613      */
 614     public void increaseUseCount() {
 615         if (isScope()) { // Avoid dirtying a cache line; we only need the use count for scoped symbols
 616             useCount++;
 617         }
 618     }
 619 
 620     /**
 621      * Get the symbol's use count
 622      * @return the number of times the symbol is used in code.
 623      */
 624     public int getUseCount() {
 625         return useCount;
 626     }
 627 
 628     /**
 629      * Set the bytecode slot for this symbol
 630      * @param  firstSlot valid bytecode slot
 631      * @return the symbol
 632      */
 633     public Symbol setFirstSlot(final int firstSlot) {
 634         assert firstSlot >= 0 && firstSlot <= 65535;
 635         if (firstSlot != this.firstSlot) {
 636             if(shouldTrace()) {
 637                 trace("SET SLOT " + firstSlot);
 638             }
 639             this.firstSlot = firstSlot;
 640         }
 641         return this;
 642     }
 643 
 644     /**
 645      * From a lexical context, set this symbol as needing scope, which
 646      * will set flags for the defining block that will be written when
 647      * block is popped from the lexical context stack, used by codegen
 648      * when flags need to be tagged, but block is in the
 649      * middle of evaluation and cannot be modified.
 650      *
 651      * @param  lc     lexical context
 652      * @param  symbol symbol
 653      * @return the symbol
 654      */
 655     public static Symbol setSymbolIsScope(final LexicalContext lc, final Symbol symbol) {
 656         symbol.setIsScope();
 657         if (!symbol.isGlobal()) {
 658             lc.setBlockNeedsScope(lc.getDefiningBlock(symbol));
 659         }
 660         return symbol;
 661     }
 662 
 663     private boolean shouldTrace() {
 664         return TRACE_SYMBOLS != null && (TRACE_SYMBOLS.isEmpty() || TRACE_SYMBOLS.contains(name));
 665     }
 666 
 667     private void trace(final String desc) {
 668         Context.err(Debug.id(this) + " SYMBOL: '" + name + "' " + desc);
 669         if (TRACE_SYMBOLS_STACKTRACE != null && (TRACE_SYMBOLS_STACKTRACE.isEmpty() || TRACE_SYMBOLS_STACKTRACE.contains(name))) {
 670             new Throwable().printStackTrace(Context.getCurrentErr());
 671         }
 672     }
 673 
 674     private void readObject(final ObjectInputStream in) throws ClassNotFoundException, IOException {
 675         in.defaultReadObject();
 676         firstSlot = -1;
 677         fieldIndex = -1;
 678     }
 679 }