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.codegen; 27 28 import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR; 30 import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; 31 import static jdk.nashorn.internal.codegen.CompilerConstants.EXCEPTION_PREFIX; 32 import static jdk.nashorn.internal.codegen.CompilerConstants.ITERATOR_PREFIX; 33 import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; 34 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; 35 import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX; 36 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; 37 import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; 38 import static jdk.nashorn.internal.ir.Symbol.HAS_OBJECT_VALUE; 39 import static jdk.nashorn.internal.ir.Symbol.IS_CONST; 40 import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF; 41 import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL; 42 import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; 43 import static jdk.nashorn.internal.ir.Symbol.IS_LET; 44 import static jdk.nashorn.internal.ir.Symbol.IS_PARAM; 45 import static jdk.nashorn.internal.ir.Symbol.IS_PROGRAM_LEVEL; 46 import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE; 47 import static jdk.nashorn.internal.ir.Symbol.IS_THIS; 48 import static jdk.nashorn.internal.ir.Symbol.IS_VAR; 49 import static jdk.nashorn.internal.ir.Symbol.KINDMASK; 50 51 import java.util.ArrayDeque; 52 import java.util.ArrayList; 53 import java.util.Deque; 54 import java.util.HashMap; 55 import java.util.HashSet; 56 import java.util.Iterator; 57 import java.util.List; 58 import java.util.ListIterator; 59 import java.util.Map; 60 import java.util.Set; 61 import jdk.nashorn.internal.ir.AccessNode; 62 import jdk.nashorn.internal.ir.BinaryNode; 63 import jdk.nashorn.internal.ir.Block; 64 import jdk.nashorn.internal.ir.CatchNode; 65 import jdk.nashorn.internal.ir.Expression; 66 import jdk.nashorn.internal.ir.ForNode; 67 import jdk.nashorn.internal.ir.FunctionNode; 68 import jdk.nashorn.internal.ir.IdentNode; 69 import jdk.nashorn.internal.ir.IndexNode; 70 import jdk.nashorn.internal.ir.LexicalContextNode; 71 import jdk.nashorn.internal.ir.LiteralNode; 72 import jdk.nashorn.internal.ir.Node; 73 import jdk.nashorn.internal.ir.RuntimeNode; 74 import jdk.nashorn.internal.ir.RuntimeNode.Request; 75 import jdk.nashorn.internal.ir.Splittable; 76 import jdk.nashorn.internal.ir.Statement; 77 import jdk.nashorn.internal.ir.SwitchNode; 78 import jdk.nashorn.internal.ir.Symbol; 79 import jdk.nashorn.internal.ir.TryNode; 80 import jdk.nashorn.internal.ir.UnaryNode; 81 import jdk.nashorn.internal.ir.VarNode; 82 import jdk.nashorn.internal.ir.WithNode; 83 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor; 84 import jdk.nashorn.internal.parser.TokenType; 85 import jdk.nashorn.internal.runtime.Context; 86 import jdk.nashorn.internal.runtime.ECMAErrors; 87 import jdk.nashorn.internal.runtime.ErrorManager; 88 import jdk.nashorn.internal.runtime.JSErrorType; 89 import jdk.nashorn.internal.runtime.ParserException; 90 import jdk.nashorn.internal.runtime.Source; 91 import jdk.nashorn.internal.runtime.logging.DebugLogger; 92 import jdk.nashorn.internal.runtime.logging.Loggable; 93 import jdk.nashorn.internal.runtime.logging.Logger; 94 95 /** 96 * This visitor assigns symbols to identifiers denoting variables. It does few more minor calculations that are only 97 * possible after symbols have been assigned; such is the transformation of "delete" and "typeof" operators into runtime 98 * nodes and counting of number of properties assigned to "this" in constructor functions. This visitor is also notable 99 * for what it doesn't do, most significantly it does no type calculations as in JavaScript variables can change types 100 * during runtime and as such symbols don't have types. Calculation of expression types is performed by a separate 101 * visitor. 102 */ 103 @Logger(name="symbols") 104 final class AssignSymbols extends SimpleNodeVisitor implements Loggable { 105 private final DebugLogger log; 106 private final boolean debug; 107 108 private static boolean isParamOrVar(final IdentNode identNode) { 109 final Symbol symbol = identNode.getSymbol(); 110 return symbol.isParam() || symbol.isVar(); 111 } 112 113 private static String name(final Node node) { 114 final String cn = node.getClass().getName(); 115 final int lastDot = cn.lastIndexOf('.'); 116 if (lastDot == -1) { 117 return cn; 118 } 119 return cn.substring(lastDot + 1); 120 } 121 122 /** 123 * Checks if various symbols that were provisionally marked as needing a slot ended up unused, and marks them as not 124 * needing a slot after all. 125 * @param functionNode the function node 126 * @return the passed in node, for easy chaining 127 */ 128 private static FunctionNode removeUnusedSlots(final FunctionNode functionNode) { 129 if (!functionNode.needsCallee()) { 130 functionNode.compilerConstant(CALLEE).setNeedsSlot(false); 131 } 132 if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) { 133 functionNode.compilerConstant(SCOPE).setNeedsSlot(false); 134 } 135 // Named function expressions that end up not referencing themselves won't need a local slot for the self symbol. 136 if(functionNode.isNamedFunctionExpression() && !functionNode.usesSelfSymbol()) { 137 final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName()); 138 if(selfSymbol != null && selfSymbol.isFunctionSelf()) { 139 selfSymbol.setNeedsSlot(false); 140 selfSymbol.clearFlag(Symbol.IS_VAR); 141 } 142 } 143 return functionNode; 144 } 145 146 private final Deque<Set<String>> thisProperties = new ArrayDeque<>(); 147 private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol 148 private final Compiler compiler; 149 private final boolean isOnDemand; 150 151 public AssignSymbols(final Compiler compiler) { 152 this.compiler = compiler; 153 this.log = initLogger(compiler.getContext()); 154 this.debug = log.isEnabled(); 155 this.isOnDemand = compiler.isOnDemandCompilation(); 156 } 157 158 @Override 159 public DebugLogger getLogger() { 160 return log; 161 } 162 163 @Override 164 public DebugLogger initLogger(final Context context) { 165 return context.getLogger(this.getClass()); 166 } 167 168 /** 169 * Define symbols for all variable declarations at the top of the function scope. This way we can get around 170 * problems like 171 * 172 * while (true) { 173 * break; 174 * if (true) { 175 * var s; 176 * } 177 * } 178 * 179 * to an arbitrary nesting depth. 180 * 181 * see NASHORN-73 182 * 183 * @param functionNode the FunctionNode we are entering 184 * @param body the body of the FunctionNode we are entering 185 */ 186 private void acceptDeclarations(final FunctionNode functionNode, final Block body) { 187 // This visitor will assign symbol to all declared variables. 188 body.accept(new SimpleNodeVisitor() { 189 @Override 190 protected boolean enterDefault(final Node node) { 191 // Don't bother visiting expressions; var is a statement, it can't be inside an expression. 192 // This will also prevent visiting nested functions (as FunctionNode is an expression). 193 return !(node instanceof Expression); 194 } 195 196 @Override 197 public Node leaveVarNode(final VarNode varNode) { 198 final IdentNode ident = varNode.getName(); 199 final boolean blockScoped = varNode.isBlockScoped(); 200 if (blockScoped && lc.inUnprotectedSwitchContext()) { 201 throwUnprotectedSwitchError(varNode); 202 } 203 final Block block = blockScoped ? lc.getCurrentBlock() : body; 204 final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags()); 205 if (varNode.isFunctionDeclaration()) { 206 symbol.setIsFunctionDeclaration(); 207 } 208 return varNode.setName(ident.setSymbol(symbol)); 209 } 210 }); 211 } 212 213 private IdentNode compilerConstantIdentifier(final CompilerConstants cc) { 214 return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); 215 } 216 217 /** 218 * Creates an ident node for an implicit identifier within the function (one not declared in the script source 219 * code). These identifiers are defined with function's token and finish. 220 * @param name the name of the identifier 221 * @return an ident node representing the implicit identifier. 222 */ 223 private IdentNode createImplicitIdentifier(final String name) { 224 final FunctionNode fn = lc.getCurrentFunction(); 225 return new IdentNode(fn.getToken(), fn.getFinish(), name); 226 } 227 228 private Symbol createSymbol(final String name, final int flags) { 229 if ((flags & Symbol.KINDMASK) == IS_GLOBAL) { 230 //reuse global symbols so they can be hashed 231 Symbol global = globalSymbols.get(name); 232 if (global == null) { 233 global = new Symbol(name, flags); 234 globalSymbols.put(name, global); 235 } 236 return global; 237 } 238 return new Symbol(name, flags); 239 } 240 241 /** 242 * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically 243 * used to create assignment of {@code :callee} to the function name symbol in self-referential function 244 * expressions as well as for assignment of {@code :arguments} to {@code arguments}. 245 * 246 * @param name the ident node identifying the variable to initialize 247 * @param initConstant the compiler constant it is initialized to 248 * @param fn the function node the assignment is for 249 * @return a var node with the appropriate assignment 250 */ 251 private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) { 252 final IdentNode init = compilerConstantIdentifier(initConstant); 253 assert init.getSymbol() != null && init.getSymbol().isBytecodeLocal(); 254 255 final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init); 256 257 final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); 258 assert nameSymbol != null; 259 260 return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this); 261 } 262 263 private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) { 264 final List<VarNode> syntheticInitializers = new ArrayList<>(2); 265 266 // Must visit the new var nodes in the context of the body. We could also just set the new statements into the 267 // block and then revisit the entire block, but that seems to be too much double work. 268 final Block body = functionNode.getBody(); 269 lc.push(body); 270 try { 271 if (functionNode.usesSelfSymbol()) { 272 // "var fn = :callee" 273 syntheticInitializers.add(createSyntheticInitializer(functionNode.getIdent(), CALLEE, functionNode)); 274 } 275 276 if (functionNode.needsArguments()) { 277 // "var arguments = :arguments" 278 syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()), 279 ARGUMENTS, functionNode)); 280 } 281 282 if (syntheticInitializers.isEmpty()) { 283 return functionNode; 284 } 285 286 for(final ListIterator<VarNode> it = syntheticInitializers.listIterator(); it.hasNext();) { 287 it.set((VarNode)it.next().accept(this)); 288 } 289 } finally { 290 lc.pop(body); 291 } 292 293 final List<Statement> stmts = body.getStatements(); 294 final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size()); 295 newStatements.addAll(syntheticInitializers); 296 newStatements.addAll(stmts); 297 return functionNode.setBody(lc, body.setStatements(lc, newStatements)); 298 } 299 300 /** 301 * Defines a new symbol in the given block. 302 * 303 * @param block the block in which to define the symbol 304 * @param name name of symbol. 305 * @param origin origin node 306 * @param symbolFlags Symbol flags. 307 * 308 * @return Symbol for given name or null for redefinition. 309 */ 310 private Symbol defineSymbol(final Block block, final String name, final Node origin, final int symbolFlags) { 311 int flags = symbolFlags; 312 final boolean isBlockScope = (flags & IS_LET) != 0 || (flags & IS_CONST) != 0; 313 final boolean isGlobal = (flags & KINDMASK) == IS_GLOBAL; 314 315 Symbol symbol; 316 final FunctionNode function; 317 if (isBlockScope) { 318 // block scoped variables always live in current block, no need to look for existing symbols in parent blocks. 319 symbol = block.getExistingSymbol(name); 320 function = lc.getCurrentFunction(); 321 } else { 322 symbol = findSymbol(block, name); 323 function = lc.getFunction(block); 324 } 325 326 // Global variables are implicitly always scope variables too. 327 if (isGlobal) { 328 flags |= IS_SCOPE; 329 } 330 331 if (lc.getCurrentFunction().isProgram()) { 332 flags |= IS_PROGRAM_LEVEL; 333 } 334 335 final boolean isParam = (flags & KINDMASK) == IS_PARAM; 336 final boolean isVar = (flags & KINDMASK) == IS_VAR; 337 338 if (symbol != null) { 339 // Symbol was already defined. Check if it needs to be redefined. 340 if (isParam) { 341 if (!isLocal(function, symbol)) { 342 // Not defined in this function. Create a new definition. 343 symbol = null; 344 } else if (symbol.isParam()) { 345 // Duplicate parameter. Null return will force an error. 346 throwParserException(ECMAErrors.getMessage("syntax.error.duplicate.parameter", name), origin); 347 } 348 } else if (isVar) { 349 if (isBlockScope) { 350 // Check redeclaration in same block 351 if (symbol.hasBeenDeclared()) { 352 throwParserException(ECMAErrors.getMessage("syntax.error.redeclare.variable", name), origin); 353 } else { 354 symbol.setHasBeenDeclared(); 355 // Set scope flag on top-level block scoped symbols 356 if (function.isProgram() && function.getBody() == block) { 357 symbol.setIsScope(); 358 } 359 } 360 } else if ((flags & IS_INTERNAL) != 0) { 361 // Always create a new definition. 362 symbol = null; 363 } else { 364 // Found LET or CONST in parent scope of same function - s SyntaxError 365 if (symbol.isBlockScoped() && isLocal(lc.getCurrentFunction(), symbol)) { 366 throwParserException(ECMAErrors.getMessage("syntax.error.redeclare.variable", name), origin); 367 } 368 // Not defined in this function. Create a new definition. 369 if (!isLocal(function, symbol) || symbol.less(IS_VAR)) { 370 symbol = null; 371 } 372 } 373 } 374 } 375 376 if (symbol == null) { 377 // If not found, then create a new one. 378 final Block symbolBlock; 379 380 // Determine where to create it. 381 if (isVar && ((flags & IS_INTERNAL) != 0 || isBlockScope)) { 382 symbolBlock = block; //internal vars are always defined in the block closest to them 383 } else if (isGlobal) { 384 symbolBlock = lc.getOutermostFunction().getBody(); 385 } else { 386 symbolBlock = lc.getFunctionBody(function); 387 } 388 389 // Create and add to appropriate block. 390 symbol = createSymbol(name, flags); 391 symbolBlock.putSymbol(symbol); 392 393 if ((flags & IS_SCOPE) == 0) { 394 // Initial assumption; symbol can lose its slot later 395 symbol.setNeedsSlot(true); 396 } 397 } else if (symbol.less(flags)) { 398 symbol.setFlags(flags); 399 } 400 401 return symbol; 402 } 403 404 private <T extends Node> T end(final T node) { 405 return end(node, true); 406 } 407 408 private <T extends Node> T end(final T node, final boolean printNode) { 409 if (debug) { 410 final StringBuilder sb = new StringBuilder(); 411 412 sb.append("[LEAVE "). 413 append(name(node)). 414 append("] "). 415 append(printNode ? node.toString() : ""). 416 append(" in '"). 417 append(lc.getCurrentFunction().getName()). 418 append('\''); 419 420 if (node instanceof IdentNode) { 421 final Symbol symbol = ((IdentNode)node).getSymbol(); 422 if (symbol == null) { 423 sb.append(" <NO SYMBOL>"); 424 } else { 425 sb.append(" <symbol=").append(symbol).append('>'); 426 } 427 } 428 429 log.unindent(); 430 log.info(sb); 431 } 432 433 return node; 434 } 435 436 @Override 437 public boolean enterBlock(final Block block) { 438 start(block); 439 440 if (lc.isFunctionBody()) { 441 assert !block.hasSymbols(); 442 final FunctionNode fn = lc.getCurrentFunction(); 443 if (isUnparsedFunction(fn)) { 444 // It's a skipped nested function. Just mark the symbols being used by it as being in use. 445 for(final String name: compiler.getScriptFunctionData(fn.getId()).getExternalSymbolNames()) { 446 nameIsUsed(name, null); 447 } 448 // Don't bother descending into it, it must be empty anyway. 449 assert block.getStatements().isEmpty(); 450 return false; 451 } 452 453 enterFunctionBody(); 454 } 455 456 return true; 457 } 458 459 private boolean isUnparsedFunction(final FunctionNode fn) { 460 return isOnDemand && fn != lc.getOutermostFunction(); 461 } 462 463 @Override 464 public boolean enterCatchNode(final CatchNode catchNode) { 465 final IdentNode exception = catchNode.getException(); 466 final Block block = lc.getCurrentBlock(); 467 468 start(catchNode); 469 470 // define block-local exception variable 471 final String exname = exception.getName(); 472 // If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its 473 // symbol is naturally internal, and should be treated as such. 474 final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName()); 475 // IS_LET flag is required to make sure symbol is not visible outside catch block. However, we need to 476 // clear the IS_LET flag after creation to allow redefinition of symbol inside the catch block. 477 final Symbol symbol = defineSymbol(block, exname, catchNode, IS_VAR | IS_LET | (isInternal ? IS_INTERNAL : 0) | HAS_OBJECT_VALUE); 478 symbol.clearFlag(IS_LET); 479 480 return true; 481 } 482 483 private void enterFunctionBody() { 484 final FunctionNode functionNode = lc.getCurrentFunction(); 485 final Block body = lc.getCurrentBlock(); 486 487 initFunctionWideVariables(functionNode, body); 488 acceptDeclarations(functionNode, body); 489 defineFunctionSelfSymbol(functionNode, body); 490 } 491 492 private void defineFunctionSelfSymbol(final FunctionNode functionNode, final Block body) { 493 // Function self-symbol is only declared as a local variable for named function expressions. Declared functions 494 // don't need it as they are local variables in their declaring scope. 495 if (!functionNode.isNamedFunctionExpression()) { 496 return; 497 } 498 499 final String name = functionNode.getIdent().getName(); 500 assert name != null; // As it's a named function expression. 501 502 if (body.getExistingSymbol(name) != null) { 503 // Body already has a declaration for the name. It's either a parameter "function x(x)" or a 504 // top-level variable "function x() { ... var x; ... }". 505 return; 506 } 507 508 defineSymbol(body, name, functionNode, IS_VAR | IS_FUNCTION_SELF | HAS_OBJECT_VALUE); 509 if(functionNode.allVarsInScope()) { // basically, has deep eval 510 // We must conservatively presume that eval'd code can dynamically use the function symbol. 511 lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL); 512 } 513 } 514 515 @Override 516 public boolean enterFunctionNode(final FunctionNode functionNode) { 517 start(functionNode, false); 518 519 thisProperties.push(new HashSet<String>()); 520 521 // Every function has a body, even the ones skipped on reparse (they have an empty one). We're 522 // asserting this as even for those, enterBlock() must be invoked to correctly process symbols that 523 // are used in them. 524 assert functionNode.getBody() != null; 525 526 return true; 527 } 528 529 @Override 530 public boolean enterVarNode(final VarNode varNode) { 531 start(varNode); 532 // Normally, a symbol assigned in a var statement is not live for its RHS. Since we also represent function 533 // declarations as VarNodes, they are exception to the rule, as they need to have the symbol visible to the 534 // body of the declared function for self-reference. 535 if (varNode.isFunctionDeclaration()) { 536 defineVarIdent(varNode); 537 } 538 return true; 539 } 540 541 @Override 542 public Node leaveVarNode(final VarNode varNode) { 543 if (!varNode.isFunctionDeclaration()) { 544 defineVarIdent(varNode); 545 } 546 return super.leaveVarNode(varNode); 547 } 548 549 private void defineVarIdent(final VarNode varNode) { 550 final IdentNode ident = varNode.getName(); 551 final int flags; 552 if (!varNode.isBlockScoped() && lc.getCurrentFunction().isProgram()) { 553 flags = IS_SCOPE; 554 } else { 555 flags = 0; 556 } 557 defineSymbol(lc.getCurrentBlock(), ident.getName(), ident, varNode.getSymbolFlags() | flags); 558 } 559 560 private Symbol exceptionSymbol() { 561 return newObjectInternal(EXCEPTION_PREFIX); 562 } 563 564 /** 565 * This has to run before fix assignment types, store any type specializations for 566 * parameters, then turn them into objects for the generic version of this method. 567 * 568 * @param functionNode functionNode 569 */ 570 private FunctionNode finalizeParameters(final FunctionNode functionNode) { 571 final List<IdentNode> newParams = new ArrayList<>(); 572 final boolean isVarArg = functionNode.isVarArg(); 573 574 final Block body = functionNode.getBody(); 575 for (final IdentNode param : functionNode.getParameters()) { 576 final Symbol paramSymbol = body.getExistingSymbol(param.getName()); 577 assert paramSymbol != null; 578 assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags(); 579 newParams.add(param.setSymbol(paramSymbol)); 580 581 // parameters should not be slots for a function that uses variable arity signature 582 if (isVarArg) { 583 paramSymbol.setNeedsSlot(false); 584 } 585 } 586 587 return functionNode.setParameters(lc, newParams); 588 } 589 590 /** 591 * Search for symbol in the lexical context starting from the given block. 592 * @param name Symbol name. 593 * @return Found symbol or null if not found. 594 */ 595 private Symbol findSymbol(final Block block, final String name) { 596 for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) { 597 final Symbol symbol = blocks.next().getExistingSymbol(name); 598 if (symbol != null) { 599 return symbol; 600 } 601 } 602 return null; 603 } 604 605 /** 606 * Marks the current function as one using any global symbol. The function and all its parent functions will all be 607 * marked as needing parent scope. 608 * @see FunctionNode#needsParentScope() 609 */ 610 private void functionUsesGlobalSymbol() { 611 for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) { 612 lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE); 613 } 614 } 615 616 /** 617 * Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing 618 * its own scope to hold the variable. If the symbol is defined outside of the current function, it and all 619 * functions up to (but not including) the function containing the defining block will be marked as needing parent 620 * function scope. 621 * @see FunctionNode#needsParentScope() 622 */ 623 private void functionUsesScopeSymbol(final Symbol symbol) { 624 final String name = symbol.getName(); 625 for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) { 626 final LexicalContextNode node = contextNodeIter.next(); 627 if (node instanceof Block) { 628 final Block block = (Block)node; 629 if (block.getExistingSymbol(name) != null) { 630 assert lc.contains(block); 631 lc.setBlockNeedsScope(block); 632 break; 633 } 634 } else if (node instanceof FunctionNode) { 635 lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE); 636 } 637 } 638 } 639 640 /** 641 * Declares that the current function is using the symbol. 642 * @param symbol the symbol used by the current function. 643 */ 644 private void functionUsesSymbol(final Symbol symbol) { 645 assert symbol != null; 646 if (symbol.isScope()) { 647 if (symbol.isGlobal()) { 648 functionUsesGlobalSymbol(); 649 } else { 650 functionUsesScopeSymbol(symbol); 651 } 652 } else { 653 assert !symbol.isGlobal(); // Every global is also scope 654 } 655 } 656 657 private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) { 658 defineSymbol(block, cc.symbolName(), null, flags).setNeedsSlot(true); 659 } 660 661 private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) { 662 initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); 663 initCompileConstant(THIS, body, IS_PARAM | IS_THIS | HAS_OBJECT_VALUE); 664 665 if (functionNode.isVarArg()) { 666 initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); 667 if (functionNode.needsArguments()) { 668 initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); 669 defineSymbol(body, ARGUMENTS_VAR.symbolName(), null, IS_VAR | HAS_OBJECT_VALUE); 670 } 671 } 672 673 initParameters(functionNode, body); 674 initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); 675 initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL); 676 } 677 678 /** 679 * Initialize parameters for function node. 680 * @param functionNode the function node 681 */ 682 private void initParameters(final FunctionNode functionNode, final Block body) { 683 final boolean isVarArg = functionNode.isVarArg(); 684 final boolean scopeParams = functionNode.allVarsInScope() || isVarArg; 685 for (final IdentNode param : functionNode.getParameters()) { 686 final Symbol symbol = defineSymbol(body, param.getName(), param, IS_PARAM); 687 if(scopeParams) { 688 // NOTE: this "set is scope" is a poor substitute for clear expression of where the symbol is stored. 689 // It will force creation of scopes where they would otherwise not necessarily be needed (functions 690 // using arguments object and other variable arity functions). Tracked by JDK-8038942. 691 symbol.setIsScope(); 692 assert symbol.hasSlot(); 693 if(isVarArg) { 694 symbol.setNeedsSlot(false); 695 } 696 } 697 } 698 } 699 700 /** 701 * Is the symbol local to (that is, defined in) the specified function? 702 * @param function the function 703 * @param symbol the symbol 704 * @return true if the symbol is defined in the specified function 705 */ 706 private boolean isLocal(final FunctionNode function, final Symbol symbol) { 707 final FunctionNode definingFn = lc.getDefiningFunction(symbol); 708 assert definingFn != null; 709 return definingFn == function; 710 } 711 712 @Override 713 public Node leaveBinaryNode(final BinaryNode binaryNode) { 714 if (binaryNode.isTokenType(TokenType.ASSIGN)) { 715 return leaveASSIGN(binaryNode); 716 } 717 return super.leaveBinaryNode(binaryNode); 718 } 719 720 private Node leaveASSIGN(final BinaryNode binaryNode) { 721 // If we're assigning a property of the this object ("this.foo = ..."), record it. 722 final Expression lhs = binaryNode.lhs(); 723 if (lhs instanceof AccessNode) { 724 final AccessNode accessNode = (AccessNode) lhs; 725 final Expression base = accessNode.getBase(); 726 if (base instanceof IdentNode) { 727 final Symbol symbol = ((IdentNode)base).getSymbol(); 728 if(symbol.isThis()) { 729 thisProperties.peek().add(accessNode.getProperty()); 730 } 731 } 732 } 733 return binaryNode; 734 } 735 736 @Override 737 public Node leaveUnaryNode(final UnaryNode unaryNode) { 738 switch (unaryNode.tokenType()) { 739 case DELETE: 740 return leaveDELETE(unaryNode); 741 case TYPEOF: 742 return leaveTYPEOF(unaryNode); 743 default: 744 return super.leaveUnaryNode(unaryNode); 745 } 746 } 747 748 private Node leaveDELETE(final UnaryNode unaryNode) { 749 final FunctionNode currentFunctionNode = lc.getCurrentFunction(); 750 final boolean strictMode = currentFunctionNode.isStrict(); 751 final Expression rhs = unaryNode.getExpression(); 752 final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this); 753 754 Request request = Request.DELETE; 755 final List<Expression> args = new ArrayList<>(); 756 757 if (rhs instanceof IdentNode) { 758 final IdentNode ident = (IdentNode)rhs; 759 // If this is a declared variable or a function parameter, delete always fails (except for globals). 760 final String name = ident.getName(); 761 final Symbol symbol = ident.getSymbol(); 762 763 if (symbol.isThis()) { 764 // Can't delete "this", ignore and return true 765 return LiteralNode.newInstance(unaryNode, true); 766 } 767 final Expression literalNode = LiteralNode.newInstance(unaryNode, name); 768 final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()))); 769 770 if (!failDelete) { 771 args.add(compilerConstantIdentifier(SCOPE)); 772 } 773 args.add(literalNode); 774 args.add(strictFlagNode); 775 776 if (failDelete) { 777 request = Request.FAIL_DELETE; 778 } else if ((symbol.isGlobal() && !symbol.isFunctionDeclaration()) || symbol.isProgramLevel()) { 779 request = Request.SLOW_DELETE; 780 } 781 } else if (rhs instanceof AccessNode) { 782 final Expression base = ((AccessNode)rhs).getBase(); 783 final String property = ((AccessNode)rhs).getProperty(); 784 785 args.add(base); 786 args.add(LiteralNode.newInstance(unaryNode, property)); 787 args.add(strictFlagNode); 788 789 } else if (rhs instanceof IndexNode) { 790 final IndexNode indexNode = (IndexNode)rhs; 791 final Expression base = indexNode.getBase(); 792 final Expression index = indexNode.getIndex(); 793 794 args.add(base); 795 args.add(index); 796 args.add(strictFlagNode); 797 798 } else { 799 return LiteralNode.newInstance(unaryNode, true); 800 } 801 return new RuntimeNode(unaryNode, request, args); 802 } 803 804 @Override 805 public Node leaveForNode(final ForNode forNode) { 806 if (forNode.isForInOrOf()) { 807 return forNode.setIterator(lc, newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73 808 } 809 810 return end(forNode); 811 } 812 813 @Override 814 public Node leaveFunctionNode(final FunctionNode functionNode) { 815 final FunctionNode finalizedFunction; 816 if (isUnparsedFunction(functionNode)) { 817 finalizedFunction = functionNode; 818 } else { 819 finalizedFunction = 820 markProgramBlock( 821 removeUnusedSlots( 822 createSyntheticInitializers( 823 finalizeParameters( 824 lc.applyTopFlags(functionNode)))) 825 .setThisProperties(lc, thisProperties.pop().size())); 826 } 827 return finalizedFunction; 828 } 829 830 @Override 831 public Node leaveIdentNode(final IdentNode identNode) { 832 if (identNode.isPropertyName()) { 833 return identNode; 834 } 835 836 final Symbol symbol = nameIsUsed(identNode.getName(), identNode); 837 838 if (!identNode.isInitializedHere()) { 839 symbol.increaseUseCount(); 840 } 841 842 IdentNode newIdentNode = identNode.setSymbol(symbol); 843 844 // If a block-scoped var is used before its declaration mark it as dead. 845 // We can only statically detect this for local vars, cross-function symbols require runtime checks. 846 if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) { 847 newIdentNode = newIdentNode.markDead(); 848 } 849 850 return end(newIdentNode); 851 } 852 853 private Symbol nameIsUsed(final String name, final IdentNode origin) { 854 final Block block = lc.getCurrentBlock(); 855 856 Symbol symbol = findSymbol(block, name); 857 858 //If an existing symbol with the name is found, use that otherwise, declare a new one 859 if (symbol != null) { 860 log.info("Existing symbol = ", symbol); 861 if (symbol.isFunctionSelf()) { 862 final FunctionNode functionNode = lc.getDefiningFunction(symbol); 863 assert functionNode != null; 864 assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null; 865 lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL); 866 } 867 868 // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already) 869 maybeForceScope(symbol); 870 } else { 871 log.info("No symbol exists. Declare as global: ", name); 872 symbol = defineSymbol(block, name, origin, IS_GLOBAL | IS_SCOPE); 873 } 874 875 functionUsesSymbol(symbol); 876 return symbol; 877 } 878 879 @Override 880 public Node leaveSwitchNode(final SwitchNode switchNode) { 881 // We only need a symbol for the tag if it's not an integer switch node 882 if(!switchNode.isUniqueInteger()) { 883 return switchNode.setTag(lc, newObjectInternal(SWITCH_TAG_PREFIX)); 884 } 885 return switchNode; 886 } 887 888 @Override 889 public Node leaveTryNode(final TryNode tryNode) { 890 assert tryNode.getFinallyBody() == null; 891 892 end(tryNode); 893 894 return tryNode.setException(lc, exceptionSymbol()); 895 } 896 897 private Node leaveTYPEOF(final UnaryNode unaryNode) { 898 final Expression rhs = unaryNode.getExpression(); 899 900 final List<Expression> args = new ArrayList<>(); 901 if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) { 902 args.add(compilerConstantIdentifier(SCOPE)); 903 args.add(LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName())); //null 904 } else { 905 args.add(rhs); 906 args.add(LiteralNode.newInstance(unaryNode)); //null, do not reuse token of identifier rhs, it can be e.g. 'this' 907 } 908 909 final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args); 910 911 end(unaryNode); 912 913 return runtimeNode; 914 } 915 916 private FunctionNode markProgramBlock(final FunctionNode functionNode) { 917 if (isOnDemand || !functionNode.isProgram()) { 918 return functionNode; 919 } 920 921 return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE)); 922 } 923 924 /** 925 * If the symbol isn't already a scope symbol, but it needs to be (see {@link #symbolNeedsToBeScope(Symbol)}, it is 926 * promoted to a scope symbol and its block marked as needing a scope. 927 * @param symbol the symbol that might be scoped 928 */ 929 private void maybeForceScope(final Symbol symbol) { 930 if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) { 931 Symbol.setSymbolIsScope(lc, symbol); 932 } 933 } 934 935 private Symbol newInternal(final CompilerConstants cc, final int flags) { 936 return defineSymbol(lc.getCurrentBlock(), lc.getCurrentFunction().uniqueName(cc.symbolName()), null, IS_VAR | IS_INTERNAL | flags); //NASHORN-73 937 } 938 939 private Symbol newObjectInternal(final CompilerConstants cc) { 940 return newInternal(cc, HAS_OBJECT_VALUE); 941 } 942 943 private boolean start(final Node node) { 944 return start(node, true); 945 } 946 947 private boolean start(final Node node, final boolean printNode) { 948 if (debug) { 949 final StringBuilder sb = new StringBuilder(); 950 951 sb.append("[ENTER "). 952 append(name(node)). 953 append("] "). 954 append(printNode ? node.toString() : ""). 955 append(" in '"). 956 append(lc.getCurrentFunction().getName()). 957 append("'"); 958 log.info(sb); 959 log.indent(); 960 } 961 962 return true; 963 } 964 965 /** 966 * Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only 967 * be reached from the current block by traversing a function node, a split node, or a with node. 968 * @param symbol the symbol checked for needing to be a scope symbol 969 * @return true if the symbol has to be a scope symbol. 970 */ 971 private boolean symbolNeedsToBeScope(final Symbol symbol) { 972 if (symbol.isThis() || symbol.isInternal()) { 973 return false; 974 } 975 976 final FunctionNode func = lc.getCurrentFunction(); 977 if ( func.allVarsInScope() || (!symbol.isBlockScoped() && func.isProgram())) { 978 return true; 979 } 980 981 boolean previousWasBlock = false; 982 for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { 983 final LexicalContextNode node = it.next(); 984 if (node instanceof FunctionNode || isSplitLiteral(node)) { 985 // We reached the function boundary or a splitting boundary without seeing a definition for the symbol. 986 // It needs to be in scope. 987 return true; 988 } else if (node instanceof WithNode) { 989 if (previousWasBlock) { 990 // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately 991 // preceded by a block, this means we're currently processing its expression, not its body, 992 // therefore it doesn't count. 993 return true; 994 } 995 previousWasBlock = false; 996 } else if (node instanceof Block) { 997 if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) { 998 // We reached the block that defines the symbol without reaching either the function boundary, or a 999 // WithNode. The symbol need not be scoped. 1000 return false; 1001 } 1002 previousWasBlock = true; 1003 } else { 1004 previousWasBlock = false; 1005 } 1006 } 1007 throw new AssertionError(); 1008 } 1009 1010 private static boolean isSplitLiteral(final LexicalContextNode expr) { 1011 return expr instanceof Splittable && ((Splittable) expr).getSplitRanges() != null; 1012 } 1013 1014 private void throwUnprotectedSwitchError(final VarNode varNode) { 1015 // Block scoped declarations in switch statements without explicit blocks should be declared 1016 // in a common block that contains all the case clauses. We cannot support this without a 1017 // fundamental rewrite of how switch statements are handled (case nodes contain blocks and are 1018 // directly contained by switch node). As a temporary solution we throw a reference error here. 1019 final String msg = ECMAErrors.getMessage("syntax.error.unprotected.switch.declaration", varNode.isLet() ? "let" : "const"); 1020 throwParserException(msg, varNode); 1021 } 1022 1023 private void throwParserException(final String message, final Node origin) { 1024 if (origin == null) { 1025 throw new ParserException(message); 1026 } 1027 final Source source = compiler.getSource(); 1028 final long token = origin.getToken(); 1029 final int line = source.getLine(origin.getStart()); 1030 final int column = source.getColumn(origin.getStart()); 1031 final String formatted = ErrorManager.format(message, source, line, column, token); 1032 throw new ParserException(JSErrorType.SYNTAX_ERROR, formatted, source, line, column, token); 1033 } 1034 }