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