private boolean checkValidLValue(final Expression init, final String contextString) { if (init instanceof IdentNode) { if (!checkIdentLValue((IdentNode)init)) { return false; } verifyIdent((IdentNode)init, contextString); return true; } else if (init instanceof AccessNode || init instanceof IndexNode) { return true; } else if (isDestructuringLhs(init)) { verifyDestructuringAssignmentPattern(init, contextString); return true; } else { return false; } }
/** * Create an IdentNode from the current token * * @return an IdentNode representing the current token */ protected final IdentNode getIdentifierName() { if (type == IDENT) { return getIdent(); } else if (isIdentifierName()) { // Fake out identifier. final long identToken = Token.recast(token, IDENT); // Get IDENT. final String ident = (String)getValue(identToken); next(); // Create IDENT node. return createIdentNode(identToken, finish, ident); } else { expect(IDENT); return null; } }
/** * Make sure that in strict mode, the identifier name used is allowed. * * @param ident Identifier that is verified * @param contextString String used in error message to give context to the user */ private void verifyStrictIdent(final IdentNode ident, final String contextString) { if (isStrictMode) { switch (ident.getName()) { case "eval": case "arguments": throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); default: break; } if (ident.isFutureStrictName()) { throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); } } }
/** * Convert execString to a call to $EXEC. * * @param primaryToken Original string token. * @return callNode to $EXEC. */ CallNode execString(final int primaryLine, final long primaryToken) { // Synthesize an ident to call $EXEC. final IdentNode execIdent = new IdentNode(primaryToken, finish, ScriptingFunctions.EXEC_NAME); // Skip over EXECSTRING. next(); // Set up argument list for call. // Skip beginning of edit string expression. expect(LBRACE); // Add the following expression to arguments. final List<Expression> arguments = Collections.singletonList(expression()); // Skip ending of edit string expression. expect(RBRACE); return new CallNode(primaryLine, primaryToken, finish, execIdent, arguments, false); }
private PropertyFunction propertySetterFunction(final long getSetToken, final int functionLine) { final PropertyKey setIdent = propertyName(); final String setterName = setIdent.getPropertyName(); final IdentNode setNameNode = createIdentNode(((Node)setIdent).getToken(), finish, NameCodec.encode("set " + setterName)); expect(LPAREN); // be sloppy and allow missing setter parameter even though // spec does not permit it! final IdentNode argIdent; if (type == IDENT || isNonStrictModeIdent()) { argIdent = getIdent(); verifyStrictIdent(argIdent, "setter argument"); } else { argIdent = null; } expect(RPAREN); final List<IdentNode> parameters = new ArrayList<>(); if (argIdent != null) { parameters.add(argIdent); } final FunctionNode functionNode = functionBody(getSetToken, setNameNode, parameters, FunctionNode.Kind.SETTER, functionLine); return new PropertyFunction(setIdent, functionNode); }
@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 Object evaluateSafely(final Expression expr) { if (expr instanceof IdentNode) { return runtimeScope == null ? null : evaluatePropertySafely(runtimeScope, ((IdentNode)expr).getName()); } if (expr instanceof AccessNode) { final AccessNode accessNode = (AccessNode)expr; final Object base = evaluateSafely(accessNode.getBase()); if (!(base instanceof ScriptObject)) { return null; } return evaluatePropertySafely((ScriptObject)base, accessNode.getProperty()); } return null; }
private void verifyParameterList(final List<IdentNode> parameters, final ParserContextFunctionNode functionNode) { final IdentNode duplicateParameter = functionNode.getDuplicateParameterBinding(); if (duplicateParameter != null) { if (functionNode.isStrict() || functionNode.getKind() == FunctionNode.Kind.ARROW || !functionNode.isSimpleParameterList()) { throw error(AbstractParser.message("strict.param.redefinition", duplicateParameter.getName()), duplicateParameter.getToken()); } final int arity = parameters.size(); final HashSet<String> parametersSet = new HashSet<>(arity); for (int i = arity - 1; i >= 0; i--) { final IdentNode parameter = parameters.get(i); String parameterName = parameter.getName(); if (parametersSet.contains(parameterName)) { // redefinition of parameter name, rename in non-strict mode parameterName = functionNode.uniqueName(parameterName); final long parameterToken = parameter.getToken(); parameters.set(i, new IdentNode(parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName))); } parametersSet.add(parameterName); } } }
@Override public Node leaveIdentNode(final IdentNode identNode) { if (identNode.isPropertyName()) { return identNode; } final Symbol symbol = nameIsUsed(identNode.getName(), identNode); if (!identNode.isInitializedHere()) { symbol.increaseUseCount(); } IdentNode newIdentNode = identNode.setSymbol(symbol); // If a block-scoped var is used before its declaration mark it as dead. // We can only statically detect this for local vars, cross-function symbols require runtime checks. if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) { newIdentNode = newIdentNode.markDead(); } return end(newIdentNode); }
private Symbol nameIsUsed(final String name, final IdentNode origin) { final Block block = lc.getCurrentBlock(); Symbol symbol = findSymbol(block, name); //If an existing symbol with the name is found, use that otherwise, declare a new one if (symbol != null) { log.info("Existing symbol = ", symbol); if (symbol.isFunctionSelf()) { final FunctionNode functionNode = lc.getDefiningFunction(symbol); assert functionNode != null; assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null; lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL); } // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already) maybeForceScope(symbol); } else { log.info("No symbol exists. Declare as global: ", name); symbol = defineSymbol(block, name, origin, IS_GLOBAL | IS_SCOPE); } functionUsesSymbol(symbol); return symbol; }
private Node leaveTYPEOF(final UnaryNode unaryNode) { final Expression rhs = unaryNode.getExpression(); final List<Expression> args = new ArrayList<>(); if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) { args.add(compilerConstantIdentifier(SCOPE)); args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null } else { args.add(rhs); args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this' } final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args).accept(this); end(unaryNode); return runtimeNode; }
@Override public boolean enterBinaryNode(final BinaryNode binaryNode) { if(binaryNode.isAssignment()) { final Expression lhs = binaryNode.lhs(); if(!binaryNode.isSelfModifying()) { tagNeverOptimistic(lhs); } if(lhs instanceof IdentNode) { final Symbol symbol = ((IdentNode)lhs).getSymbol(); // Assignment to internal symbols is never optimistic, except for self-assignment expressions if(symbol.isInternal() && !binaryNode.rhs().isSelfModifying()) { tagNeverOptimistic(binaryNode.rhs()); } } } else if(binaryNode.isTokenType(TokenType.INSTANCEOF)) { tagNeverOptimistic(binaryNode.lhs()); tagNeverOptimistic(binaryNode.rhs()); } return true; }
@Override public Node leaveIdentNode(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); if(symbol == null) { assert identNode.isPropertyName(); return identNode; } else if(symbol.isBytecodeLocal()) { // Identifiers accessing bytecode local variables will never be optimistic, as type calculation phase over // them will always assign them statically provable types. Note that access to function parameters can still // be optimistic if the parameter needs to be in scope as it's used by a nested function. return identNode; } else if(symbol.isParam() && lc.getCurrentFunction().isVarArg()) { // Parameters in vararg methods are not optimistic; we always access them using Object getters. return identNode.setType(identNode.getMostPessimisticType()); } else { assert symbol.isScope(); return leaveOptimistic(identNode); } }
@Override public boolean enterBinaryNode(final BinaryNode binaryNode) { if(binaryNode.isAssignment()) { final Expression lhs = binaryNode.lhs(); if(!binaryNode.isSelfModifying()) { tagNeverOptimistic(lhs); } if(lhs instanceof IdentNode) { final Symbol symbol = ((IdentNode)lhs).getSymbol(); // Assignment to internal symbols is never optimistic, except for self-assignment expressions if(symbol.isInternal() && !binaryNode.rhs().isSelfModifying()) { tagNeverOptimistic(binaryNode.rhs()); } } } else if(binaryNode.isTokenType(TokenType.INSTANCEOF) || binaryNode.isTokenType(TokenType.EQ_STRICT) || binaryNode.isTokenType(TokenType.NE_STRICT)) { tagNeverOptimistic(binaryNode.lhs()); tagNeverOptimistic(binaryNode.rhs()); } return true; }
/** * Incoming method parameters are always declared on method entry; declare them in the local variable table. * @param function function for which code is being generated. */ private void initializeMethodParameters(final FunctionNode function) { final Label functionStart = new Label("fn_start"); method.label(functionStart); int nextSlot = 0; if(function.needsCallee()) { initializeInternalFunctionParameter(CALLEE, function, functionStart, nextSlot++); } initializeInternalFunctionParameter(THIS, function, functionStart, nextSlot++); if(function.isVarArg()) { initializeInternalFunctionParameter(VARARGS, function, functionStart, nextSlot++); } else { for(final IdentNode param: function.getParameters()) { final Symbol symbol = param.getSymbol(); if(symbol.isBytecodeLocal()) { method.initializeMethodParameter(symbol, param.getType(), functionStart); } } } }
/** * Execute parse and return the resulting function node. * Errors will be thrown and the error manager will contain information * if parsing should fail. This method is used to check if code String * passed to "Function" constructor is a valid function body or not. * * @return function node resulting from successful parse */ public FunctionNode parseFunctionBody() { try { stream = new TokenStream(); lexer = new Lexer(source, stream, scripting && !env._no_syntax_extensions); final int functionLine = line; // Set up first token (skips opening EOL.) k = -1; next(); // Make a fake token for the function. final long functionToken = Token.toDesc(FUNCTION, 0, source.getLength()); // Set up the function to append elements. FunctionNode function = newFunctionNode( functionToken, new IdentNode(functionToken, Token.descPosition(functionToken), PROGRAM.symbolName()), new ArrayList<IdentNode>(), FunctionNode.Kind.NORMAL, functionLine); functionDeclarations = new ArrayList<>(); sourceElements(false); addFunctionDeclarations(function); functionDeclarations = null; expect(EOF); function.setFinish(source.getLength() - 1); function = restoreFunctionNode(function, token); //commit code function = function.setBody(lc, function.getBody().setNeedsScope(lc)); printAST(function); return function; } catch (final Exception e) { handleParseException(e); return null; } }
void replaceCompileTimeProperty() { final IdentNode identNode = (IdentNode)expression; final String name = identNode.getSymbol().getName(); if (CompilerConstants.__FILE__.name().equals(name)) { replaceCompileTimeProperty(getCurrentSource().getName()); } else if (CompilerConstants.__DIR__.name().equals(name)) { replaceCompileTimeProperty(getCurrentSource().getBase()); } else if (CompilerConstants.__LINE__.name().equals(name)) { replaceCompileTimeProperty(getCurrentSource().getLine(identNode.position())); } }
private PropertyFunction propertyGetterFunction(final long getSetToken, final int functionLine) { final PropertyKey getIdent = propertyName(); final String getterName = getIdent.getPropertyName(); final IdentNode getNameNode = createIdentNode(((Node)getIdent).getToken(), finish, NameCodec.encode("get " + getterName)); expect(LPAREN); expect(RPAREN); final FunctionNode functionNode = functionBody(getSetToken, getNameNode, new ArrayList<IdentNode>(), FunctionNode.Kind.GETTER, functionLine); return new PropertyFunction(getIdent, functionNode); }
@Override public boolean enterIdentNode(final IdentNode identNode) { if (identNode.isInternal()) { noProgramPoint.add(identNode); } return true; }
/** * Generates an extra local variable, always using the same slot, one that is available after the end of the * frame. * * @param type the type of the variable * * @return the quick variable */ private IdentNode quickLocalVariable(final Type type) { final String name = lc.getCurrentFunction().uniqueName(QUICK_PREFIX.symbolName()); final Symbol symbol = new Symbol(name, IS_INTERNAL | HAS_SLOT); symbol.setHasSlotFor(type); symbol.setFirstSlot(lc.quickSlot(type)); final IdentNode quickIdent = IdentNode.createInternalIdentifier(symbol).setType(type); return quickIdent; }
/** * Load a key value in the proper form. * * @param key */ //TODO move this and break it apart MethodEmitter loadKey(final Object key) { if (key instanceof IdentNode) { method.visitLdcInsn(((IdentNode) key).getName()); } else if (key instanceof LiteralNode) { method.visitLdcInsn(((LiteralNode<?>)key).getString()); } else { method.visitLdcInsn(JSType.toString(key)); } pushType(Type.OBJECT); //STRING return this; }
/** * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically * used to create assignmnent of {@code :callee} to the function name symbol in self-referential function * expressions as well as for assignment of {@code :arguments} to {@code arguments}. * * @param name the ident node identifying the variable to initialize * @param initConstant the compiler constant it is initialized to * @param fn the function node the assignment is for * @return a var node with the appropriate assignment */ private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) { final IdentNode init = compilerConstantIdentifier(initConstant); assert init.getSymbol() != null && init.getSymbol().isBytecodeLocal(); final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init); final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); assert nameSymbol != null; return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this); }
private FunctionNode createFunctionNode(final ParserContextFunctionNode function, final long startToken, final IdentNode ident, final List<IdentNode> parameters, final FunctionNode.Kind kind, final int functionLine, final Block body) { // assert body.isFunctionBody() || body.getFlag(Block.IS_PARAMETER_BLOCK) && ((BlockStatement) body.getLastStatement()).getBlock().isFunctionBody(); // Start new block. final FunctionNode functionNode = new FunctionNode( source, functionLine, body.getToken(), Token.descPosition(body.getToken()), startToken, function.getLastToken(), namespace, ident, function.getName(), parameters, function.getParameterExpressions(), kind, function.getFlags(), body, function.getEndParserState(), function.getModule(), function.getDebugFlags()); printAST(functionNode); return functionNode; }
@Override public boolean enterUnaryNode(final UnaryNode unaryNode) { final Expression expr = unaryNode.getExpression(); final LvarType unaryType = toLvarType(unaryNode.setExpression(visitExpression(expr).typeExpression).getType()); if(unaryNode.isSelfModifying() && expr instanceof IdentNode) { onSelfAssignment((IdentNode)expr, unaryType); } typeStack.push(unaryType); return false; }
@Override public Node leaveBinaryNode(final BinaryNode binaryNode) { if (binaryNode.isAssignment() && binaryNode.lhs() instanceof IdentNode) { checkConstAssignment((IdentNode) binaryNode.lhs()); } switch (binaryNode.tokenType()) { case ASSIGN: return leaveASSIGN(binaryNode); default: return super.leaveBinaryNode(binaryNode); } }
private Node leaveASSIGN(final BinaryNode binaryNode) { // If we're assigning a property of the this object ("this.foo = ..."), record it. final Expression lhs = binaryNode.lhs(); if (lhs instanceof AccessNode) { final AccessNode accessNode = (AccessNode) lhs; final Expression base = accessNode.getBase(); if (base instanceof IdentNode) { final Symbol symbol = ((IdentNode)base).getSymbol(); if(symbol.isThis()) { thisProperties.peek().add(accessNode.getProperty()); } } } return binaryNode; }
@Override public Node leaveUnaryNode(final UnaryNode unaryNode) { if (unaryNode.isAssignment() && unaryNode.getExpression() instanceof IdentNode) { checkConstAssignment((IdentNode) unaryNode.getExpression()); } switch (unaryNode.tokenType()) { case DELETE: return leaveDELETE(unaryNode); case TYPEOF: return leaveTYPEOF(unaryNode); default: return super.leaveUnaryNode(unaryNode); } }
@Override public Node leaveVarNode(final VarNode varNode) { addStatement(varNode); if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram()) { new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this); } return varNode; }
/** * Given a function node that is a callee in a CallNode, replace it with * the appropriate marker function. This is used by {@link CodeGenerator} * for fast scope calls * * @param function function called by a CallNode * @return transformed node to marker function or identity if not ident/access/indexnode */ private static Expression markerFunction(final Expression function) { if (function instanceof IdentNode) { return ((IdentNode)function).setIsFunction(); } else if (function instanceof BaseNode) { return ((BaseNode)function).setIsFunction(); } return function; }
/** * Detect use of special properties. * @param ident Referenced property. */ private void detectSpecialProperty(final IdentNode ident) { if (isArguments(ident)) { // skip over arrow functions, e.g. function f() { return (() => arguments.length)(); } getCurrentNonArrowFunction().setFlag(FunctionNode.USES_ARGUMENTS); } }
/** * An internal expression has a symbol that is tagged internal. Check if * this is such a node * * @param expression expression to check for internal symbol * @return true if internal, false otherwise */ private static boolean isInternalExpression(final Expression expression) { if (!(expression instanceof IdentNode)) { return false; } final Symbol symbol = ((IdentNode)expression).getSymbol(); return symbol != null && symbol.isInternal(); }
/** * Is this an assignment to the special variable that hosts scripting eval * results, i.e. __return__? * * @param expression expression to check whether it is $evalresult = X * @return true if an assignment to eval result, false otherwise */ private static boolean isEvalResultAssignment(final Node expression) { final Node e = expression; if (e instanceof BinaryNode) { final Node lhs = ((BinaryNode)e).lhs(); if (lhs instanceof IdentNode) { return ((IdentNode)lhs).getName().equals(RETURN.symbolName()); } } return false; }
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); }
@Override public Node leaveVarNode(final VarNode varNode) { addStatement(varNode); if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram() && ((FunctionNode) varNode.getInit()).isAnonymous()) { new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this); } return varNode; }
@Override public boolean enterIdentNode(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); if(symbol.isBytecodeLocal()) { symbolIsUsed(symbol); setIdentifierLvarType(identNode, getLocalVariableType(symbol)); } return false; }
private void onSelfAssignment(final IdentNode identNode, final LvarType type) { final Symbol symbol = identNode.getSymbol(); assert symbol != null : identNode.getName(); if(!symbol.isBytecodeLocal()) { return; } // Self-assignment never produce either a boolean or undefined assert type != null && type != LvarType.UNDEFINED && type != LvarType.BOOLEAN; setType(symbol, type); jumpToCatchBlock(identNode); }
/** * Load an identity node * * @param identNode an identity node to load * @return the method generator used */ private MethodEmitter loadIdent(final IdentNode identNode, final TypeBounds resultBounds) { checkTemporalDeadZone(identNode); final Symbol symbol = identNode.getSymbol(); if (!symbol.isScope()) { final Type type = identNode.getType(); if(type == Type.UNDEFINED) { return method.loadUndefined(resultBounds.widest); } assert symbol.hasSlot() || symbol.isParam(); return method.load(identNode); } assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; final int flags = CALLSITE_SCOPE | getCallSiteFlags(); if (isFastScope(symbol)) { // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope. if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD && !isOptimisticOrRestOf()) { method.loadCompilerConstant(SCOPE); // As shared scope vars are only used in non-optimistic compilation, we switch from using TypeBounds to // just a single definitive type, resultBounds.widest. loadSharedScopeVar(resultBounds.widest, symbol, flags); } else { new LoadFastScopeVar(identNode, resultBounds, flags).emit(); } } else { //slow scope load, we have no proto depth new LoadScopeVar(identNode, resultBounds, flags).emit(); } return method; }
@Override public Node leaveCallNode(final CallNode callNode) { //apply needs to be a global symbol or we don't allow it final List<IdentNode> newParams = explodedArguments.peek(); if (isApply(callNode)) { final List<Expression> newArgs = new ArrayList<>(); for (final Expression arg : callNode.getArgs()) { if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { newArgs.addAll(newParams); } else { newArgs.add(arg); } } changed.add(lc.getCurrentFunction().getId()); final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); if (log.isEnabled()) { log.fine("Transformed ", callNode, " from apply to call => ", newCallNode, " in ", DebugLogger.quote(lc.getCurrentFunction().getName())); } return newCallNode; } return callNode; }