/** * IfStatement : * if ( Expression ) Statement else Statement * if ( Expression ) Statement * * See 12.5 * * Parse an IF statement. */ private void ifStatement() { // Capture IF token. final int ifLine = line; final long ifToken = token; // IF tested in caller. next(); expect(LPAREN); final Expression test = expression(); expect(RPAREN); final Block pass = getStatement(); Block fail = null; if (type == ELSE) { next(); fail = getStatement(); } appendStatement(new IfNode(ifLine, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail)); }
@Override public boolean enterTryNode(final TryNode tryNode) { tryNode.toString(sb, printTypes); printLocalVariableConversion(tryNode); tryNode.getBody().accept(this); final List<Block> catchBlocks = tryNode.getCatchBlocks(); for (final Block catchBlock : catchBlocks) { final CatchNode catchNode = (CatchNode)catchBlock.getStatements().get(0); catchNode.toString(sb, printTypes); catchNode.getBody().accept(this); } final Block finallyBody = tryNode.getFinallyBody(); if (finallyBody != null) { sb.append(" finally "); finallyBody.accept(this); } return false; }
@Override public boolean enterBlock(final Block block) { start(block); if (lc.isFunctionBody()) { block.clearSymbols(); final FunctionNode fn = lc.getCurrentFunction(); if (isUnparsedFunction(fn)) { // It's a skipped nested function. Just mark the symbols being used by it as being in use. for(final String name: compiler.getScriptFunctionData(fn.getId()).getExternalSymbolNames()) { nameIsUsed(name, null); } // Don't bother descending into it, it must be empty anyway. assert block.getStatements().isEmpty(); return false; } enterFunctionBody(); } return true; }
@Override public boolean enterBlock(final Block block) { boolean cloned = false; for(final Symbol symbol: block.getSymbols()) { if(symbol.isBytecodeLocal()) { if (getLocalVariableTypeOrNull(symbol) == null) { if (!cloned) { cloneOrNewLocalVariableTypes(); cloned = true; } localVariableTypes.put(symbol, LvarType.UNDEFINED); } // In case we're repeating analysis of a lexical scope (e.g. it's in a loop), // make sure all symbols lexically scoped by the block become valid again. invalidatedSymbols.remove(symbol); } } return true; }
/** * Initialize parameters for function node. * @param functionNode the function node */ private void initParameters(final FunctionNode functionNode, final Block body) { final boolean isVarArg = functionNode.isVarArg(); final boolean scopeParams = functionNode.allVarsInScope() || isVarArg; for (final IdentNode param : functionNode.getParameters()) { final Symbol symbol = defineSymbol(body, param.getName(), param, IS_PARAM); if(scopeParams) { // NOTE: this "set is scope" is a poor substitute for clear expression of where the symbol is stored. // It will force creation of scopes where they would otherwise not necessarily be needed (functions // using arguments object and other variable arity functions). Tracked by JDK-8038942. symbol.setIsScope(); assert symbol.hasSlot(); if(isVarArg) { symbol.setNeedsSlot(false); } } } }
private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) { initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); initCompileConstant(THIS, body, IS_PARAM | IS_THIS | HAS_OBJECT_VALUE); if (functionNode.isVarArg()) { initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); if (functionNode.needsArguments()) { initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); defineSymbol(body, ARGUMENTS_VAR.symbolName(), null, IS_VAR | HAS_OBJECT_VALUE); } } initParameters(functionNode, body); initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL); }
private int getScopeProtoDepth(final Block startingBlock, final Symbol symbol) { //walk up the chain from starting block and when we bump into the current function boundary, add the external //information. final FunctionNode fn = lc.getCurrentFunction(); final int externalDepth = compiler.getScriptFunctionData(fn.getId()).getExternalSymbolDepth(symbol.getName()); //count the number of scopes from this place to the start of the function final int internalDepth = FindScopeDepths.findInternalDepth(lc, fn, startingBlock, symbol); final int scopesToStart = FindScopeDepths.findScopesToStart(lc, fn, startingBlock); int depth = 0; if (internalDepth == -1) { depth = scopesToStart + externalDepth; } else { assert internalDepth <= scopesToStart; depth = internalDepth; } return depth; }
private Block catchAllBlock(final TryNode tryNode) { final int lineNumber = tryNode.getLineNumber(); final long token = tryNode.getToken(); final int finish = tryNode.getFinish(); final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName())); final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), true)); assert catchBody.isTerminal(); //ends with throw, so terminal final CatchNode catchAllNode = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, true); final Block catchAllBlock = new Block(token, finish, catchAllNode); //catchallblock -> catchallnode (catchnode) -> exception -> throw return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower }
static int findScopesToStart(final LexicalContext lc, final FunctionNode fn, final Block block) { final Block bodyBlock = findBodyBlock(lc, fn, block); final Iterator<Block> iter = lc.getBlocks(block); Block b = iter.next(); int scopesToStart = 0; while (true) { if (b.needsScope()) { scopesToStart++; } if (b == bodyBlock) { break; } b = iter.next(); } return scopesToStart; }
static int findInternalDepth(final LexicalContext lc, final FunctionNode fn, final Block block, final Symbol symbol) { final Block bodyBlock = findBodyBlock(lc, fn, block); final Iterator<Block> iter = lc.getBlocks(block); Block b = iter.next(); int scopesToStart = 0; while (true) { if (definedInBlock(b, symbol)) { return scopesToStart; } if (b.needsScope()) { scopesToStart++; } if (b == bodyBlock) { break; //don't go past body block, but process it } b = iter.next(); } return -1; }
@Override public Node leaveBlock(final Block block) { assert !block.isCatchBlock(); Block newBlock = block; // Block was heavier than SLIT_THRESHOLD in enter, but a sub-block may have // been split already, so weigh again before splitting. long weight = WeighNodes.weigh(block, weightCache); if (weight >= SPLIT_THRESHOLD) { final FunctionNode currentFunction = lc.getCurrentFunction(); newBlock = splitBlock(block, currentFunction); weight = WeighNodes.weigh(newBlock, weightCache); lc.setFlag(currentFunction, FunctionNode.IS_SPLIT); } weightCache.put(newBlock, weight); return newBlock; }
@Override public boolean enterBlock(final Block block) { final Label entryLabel = block.getEntryLabel(); if (entryLabel.isBreakTarget()) { // Entry label is a break target only for an inlined finally block. assert !method.isReachable(); method.breakLabel(entryLabel, lc.getUsedSlotCount()); } else { method.label(entryLabel); } if(!method.isReachable()) { return false; } if(lc.isFunctionBody() && emittedMethods.contains(lc.getCurrentFunction().getName())) { return false; } initLocals(block); assert lc.getUsedSlotCount() == method.getFirstTemp(); return true; }
@Override public Node leaveBlock(final Block block) { if (!artificialBlock) { if (lc.isFunctionBody()) { // Prepend declaration-only var statements to the top of the statement list. lc.prependStatements(getCurrentFunctionState().varStatements); } else if (lc.isSplitBody()) { appendSplitReturn(FALLTHROUGH_STATE, NO_LINE_NUMBER); if (getCurrentFunctionState().fn.isProgram()) { // If we're splitting the program, make sure every shard ends with "return :return" and // begins with ":return = :return-in;". lc.prependStatement(new ExpressionStatement(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, new BinaryNode(Token.toDesc(TokenType.ASSIGN, 0, 0), createReturnIdent(), createReturnParamIdent()))); } } } return block; }
@Override public boolean enterCatchNode(final CatchNode catchNode) { final IdentNode exception = catchNode.getExceptionIdentifier(); final Block block = lc.getCurrentBlock(); start(catchNode); // define block-local exception variable final String exname = exception.getName(); // If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its // symbol is naturally internal, and should be treated as such. final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName()); // IS_LET flag is required to make sure symbol is not visible outside catch block. However, we need to // clear the IS_LET flag after creation to allow redefinition of symbol inside the catch block. final Symbol symbol = defineSymbol(block, exname, catchNode, IS_VAR | IS_LET | (isInternal ? IS_INTERNAL : 0) | HAS_OBJECT_VALUE); symbol.clearFlag(IS_LET); return true; }
private Map<Symbol, LvarType> getBreakTargetTypes(final BreakableNode target) { // Remove symbols defined in the the blocks that are being broken out of. Map<Symbol, LvarType> types = localVariableTypes; for(final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); if(node instanceof Block) { for(final Symbol symbol: ((Block)node).getSymbols()) { if(localVariableTypes.containsKey(symbol)) { if(types == localVariableTypes) { types = cloneMap(localVariableTypes); } types.remove(symbol); } } } if(node == target) { break; } } return types; }
private boolean handleBlock(final Block block, final boolean sortStats) { // FIXME: revisit this! if (block.isSynthetic()) { final int statCount = block.getStatementCount(); switch (statCount) { case 0: { final EmptyNode emptyNode = new EmptyNode(-1, block.getToken(), block.getFinish()); curStat = new EmptyStatementTreeImpl(emptyNode); return false; } case 1: { curStat = translateStat(block.getStatements().get(0)); return false; } default: { // fall through break; } } } final List<? extends Statement> stats = block.getStatements(); curStat = new BlockTreeImpl(block, translateStats(sortStats? getOrderedStatements(stats) : stats)); return false; }
/** * WithStatement : * with ( Expression ) Statement * * See 12.10 * * Parse WITH statement. */ private void withStatement() { // Capture WITH token. final int withLine = line; final long withToken = token; // WITH tested in caller. next(); // ECMA 12.10.1 strict mode restrictions if (isStrictMode) { throw error(AbstractParser.message("strict.no.with"), withToken); } expect(LPAREN); final Expression expression = expression(); expect(RPAREN); final Block body = getStatement(); appendStatement(new WithNode(withLine, withToken, finish, expression, body)); }
@Override public boolean enterBlockStatement(final BlockStatement blockStatement) { if (blockStatement.isSynthetic()) { final Block blk = blockStatement.getBlock(); blk.getStatements().get(0).accept(this); return false; } enterDefault(blockStatement); type("BlockStatement"); comma(); array("body", blockStatement.getBlock().getStatements()); return leave(); }
@Override public boolean enterTryNode(final TryNode tryNode) { tryNode.toString(sb, printTypes); printLocalVariableConversion(tryNode); tryNode.getBody().accept(this); final List<Block> catchBlocks = tryNode.getCatchBlocks(); for (final Block catchBlock : catchBlocks) { final CatchNode catchNode = (CatchNode)catchBlock.getStatements().get(0); catchNode.toString(sb, printTypes); catchNode.getBody().accept(this); } final Block finallyBody = tryNode.getFinallyBody(); if (finallyBody != null) { sb.append(" finally "); finallyBody.accept(this); } for (final Block inlinedFinally : tryNode.getInlinedFinallies()) { inlinedFinally.accept(this); } return false; }
private FunctionNode restoreFunctionNode(final FunctionNode functionNode, final long lastToken) { final Block newBody = restoreBlock(lc.getFunctionBody(functionNode)); return lc.pop(functionNode). setBody(lc, newBody). setLastToken(lc, lastToken). setState(lc, errors.hasErrors() ? CompilationState.PARSE_ERROR : CompilationState.PARSED); }
/** * Get all the statements generated by a single statement. * @return Statements. */ private Block getStatement() { if (type == LBRACE) { return getBlock(true); } // Set up new block. Captures first token. Block newBlock = newBlock(); try { statement(false, false, true); } finally { newBlock = restoreBlock(newBlock); } return newBlock; }
@Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { // It's not necessary to guard the marking of symbols as locals with this "if" condition for // correctness, it's just an optimization -- runtime type calculation is not used when the compilation // is not an on-demand optimistic compilation, so we can skip locals marking then. if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) { fn.getBody().accept(new SimpleNodeVisitor() { @Override public boolean enterFunctionNode(final FunctionNode functionNode) { // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand // compilation, and we're skipping parsing the function bodies for nested functions, this // basically only means their parameters. It'd be enough to mistakenly declare to be a local a // symbol in the outer function named the same as one of the parameters, though. return false; }; @Override public boolean enterBlock(final Block block) { for (final Symbol symbol: block.getSymbols()) { if (!symbol.isScope()) { compiler.declareLocalSymbol(symbol.getName()); } } return true; }; }); } return fn; }
/** * ...IterationStatement : * ... * do Statement while( Expression ) ; * ... * * See 12.6 * * Parse DO WHILE statement. */ private void doStatement() { // Capture DO token. final long doToken = token; // DO tested in the caller. next(); WhileNode doWhileNode = new WhileNode(-1, doToken, Token.descPosition(doToken), true); lc.push(doWhileNode); try { // Get DO body. final Block body = getStatement(); expect(WHILE); expect(LPAREN); final int doLine = line; final JoinPredecessorExpression test = joinPredecessorExpression(); expect(RPAREN); if (type == SEMICOLON) { endOfLine(); } doWhileNode.setFinish(finish); //line number is last appendStatement(doWhileNode = new WhileNode(doLine, doToken, finish, true). setBody(lc, body). setTest(lc, test)); } finally { lc.pop(doWhileNode); } }
@Override public boolean enterBlock(final Block block) { enterDefault(block); type("BlockStatement"); comma(); array("body", block.getStatements()); return leave(); }
private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) { final List<VarNode> syntheticInitializers = new ArrayList<>(2); // Must visit the new var nodes in the context of the body. We could also just set the new statements into the // block and then revisit the entire block, but that seems to be too much double work. final Block body = functionNode.getBody(); lc.push(body); try { if (functionNode.usesSelfSymbol()) { // "var fn = :callee" syntheticInitializers.add(createSyntheticInitializer(functionNode.getIdent(), CALLEE, functionNode)); } if (functionNode.needsArguments()) { // "var arguments = :arguments" syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()), ARGUMENTS, functionNode)); } if (syntheticInitializers.isEmpty()) { return functionNode; } for(final ListIterator<VarNode> it = syntheticInitializers.listIterator(); it.hasNext();) { it.set((VarNode)it.next().accept(this)); } } finally { lc.pop(body); } final List<Statement> stmts = body.getStatements(); final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size()); newStatements.addAll(syntheticInitializers); newStatements.addAll(stmts); return functionNode.setBody(lc, body.setStatements(lc, newStatements)); }
private void createSyntheticReturn(final Block body) { final FunctionNode functionNode = lc.getCurrentFunction(); final long token = functionNode.getToken(); final int finish = functionNode.getFinish(); final List<Statement> statements = body.getStatements(); final int lineNumber = statements.isEmpty() ? functionNode.getLineNumber() : statements.get(statements.size() - 1).getLineNumber(); final IdentNode returnExpr; if(functionNode.isProgram()) { returnExpr = new IdentNode(token, finish, RETURN.symbolName()).setSymbol(getCompilerConstantSymbol(functionNode, RETURN)); } else { returnExpr = null; } syntheticReturn = new ReturnNode(lineNumber, token, finish, returnExpr); syntheticReturn.accept(this); }
private void enterDoWhileLoop(final WhileNode loopNode) { assertTypeStackIsEmpty(); final JoinPredecessorExpression test = loopNode.getTest(); final Block body = loopNode.getBody(); final Label continueLabel = loopNode.getContinueLabel(); final Label breakLabel = loopNode.getBreakLabel(); final Map<Symbol, LvarType> beforeLoopTypes = localVariableTypes; final Label repeatLabel = new Label(""); for(;;) { jumpToLabel(loopNode, repeatLabel, beforeLoopTypes); final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes; body.accept(this); if(reachable) { jumpToLabel(body, continueLabel); } joinOnLabel(continueLabel); if(!reachable) { break; } visitExpressionOnEmptyStack(test); jumpToLabel(test, breakLabel); if(isAlwaysFalse(test)) { break; } jumpToLabel(test, repeatLabel); joinOnLabel(repeatLabel); if(localVariableTypes.equals(beforeRepeatTypes)) { break; } resetJoinPoint(continueLabel); resetJoinPoint(breakLabel); resetJoinPoint(repeatLabel); } if(isAlwaysTrue(test)) { doesNotContinueSequentially(); } leaveBreakable(loopNode); }
private static Block findGlobalBlock(final LexicalContext lc, final Block block) { final Iterator<Block> iter = lc.getBlocks(block); Block globalBlock = null; while (iter.hasNext()) { globalBlock = iter.next(); } return globalBlock; }
private FunctionNode markProgramBlock(final FunctionNode functionNode) { if (compiler.isOnDemandCompilation() || !functionNode.isProgram()) { return functionNode; } return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE)); }
/** * Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only * be reached from the current block by traversing a function node, a split node, or a with node. * @param symbol the symbol checked for needing to be a scope symbol * @return true if the symbol has to be a scope symbol. */ private boolean symbolNeedsToBeScope(final Symbol symbol) { if (symbol.isThis() || symbol.isInternal()) { return false; } final FunctionNode func = lc.getCurrentFunction(); if ( func.allVarsInScope() || (!symbol.isBlockScoped() && func.isProgram())) { return true; } boolean previousWasBlock = false; for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); if (node instanceof FunctionNode || isSplitArray(node)) { // We reached the function boundary or a splitting boundary without seeing a definition for the symbol. // It needs to be in scope. return true; } else if (node instanceof WithNode) { if (previousWasBlock) { // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately // preceded by a block, this means we're currently processing its expression, not its body, // therefore it doesn't count. return true; } previousWasBlock = false; } else if (node instanceof Block) { if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) { // We reached the block that defines the symbol without reaching either the function boundary, or a // WithNode. The symbol need not be scoped. return false; } previousWasBlock = true; } else { previousWasBlock = false; } } throw new AssertionError(); }
private static void extractVarNodes(final Block block, final List<Statement> statements) { final LexicalContext lc = new LexicalContext(); block.accept(lc, new NodeVisitor<LexicalContext>(lc) { @Override public boolean enterVarNode(final VarNode varNode) { statements.add(varNode.setInit(null)); return false; } }); }
/** * Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only * be reached from the current block by traversing a function node, a split node, or a with node. * @param symbol the symbol checked for needing to be a scope symbol * @return true if the symbol has to be a scope symbol. */ private boolean symbolNeedsToBeScope(final Symbol symbol) { if (symbol.isThis() || symbol.isInternal()) { return false; } final FunctionNode func = lc.getCurrentFunction(); if ( func.allVarsInScope() || (!symbol.isBlockScoped() && func.isProgram())) { return true; } boolean previousWasBlock = false; for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); if (node instanceof FunctionNode || isSplitLiteral(node)) { // We reached the function boundary or a splitting boundary without seeing a definition for the symbol. // It needs to be in scope. return true; } else if (node instanceof WithNode) { if (previousWasBlock) { // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately // preceded by a block, this means we're currently processing its expression, not its body, // therefore it doesn't count. return true; } previousWasBlock = false; } else if (node instanceof Block) { if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) { // We reached the block that defines the symbol without reaching either the function boundary, or a // WithNode. The symbol need not be scoped. return false; } previousWasBlock = true; } else { previousWasBlock = false; } } throw new AssertionError(); }
private static Block createFinallyBlock(final Block finallyBody) { final List<Statement> newStatements = new ArrayList<>(); for (final Statement statement : finallyBody.getStatements()) { newStatements.add(statement); if (statement.hasTerminalFlags()) { break; } } return finallyBody.setStatements(null, newStatements); }
private static boolean definedInBlock(final Block block, final Symbol symbol) { if (symbol.isGlobal()) { if (block.isGlobalScope()) { return true; } //globals cannot be defined anywhere else return false; } return block.getExistingSymbol(symbol.getName()) == symbol; }