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; }
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; }
@Override public boolean enterCatchNode(final CatchNode catchNode) { final IdentNode exception = catchNode.getException(); 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; }
/** * Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing * its own scope to hold the variable. If the symbol is defined outside of the current function, it and all * functions up to (but not including) the function containing the defining block will be marked as needing parent * function scope. * @see FunctionNode#needsParentScope() */ private void functionUsesScopeSymbol(final Symbol symbol) { final String name = symbol.getName(); for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) { final LexicalContextNode node = contextNodeIter.next(); if (node instanceof Block) { final Block block = (Block)node; if (block.getExistingSymbol(name) != null) { assert lc.contains(block); lc.setBlockNeedsScope(block); break; } } else if (node instanceof FunctionNode) { lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE); } } }
@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; }
@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; }
private LocalVariableConversion createConversion(final Symbol symbol, final LvarType branchLvarType, final Map<Symbol, LvarType> joinLvarTypes, final LocalVariableConversion next) { final LvarType targetType = joinLvarTypes.get(symbol); assert targetType != null; if(targetType == branchLvarType) { return next; } // NOTE: we could naively just use symbolIsUsed(symbol, branchLvarType) here, but that'd be wrong. While // technically a conversion will read the value of the symbol with that type, but it will also write it to a new // type, and that type might be dead (we can't know yet). For this reason, we don't treat conversion reads as // real uses until we know their target type is live. If we didn't do this, and just did a symbolIsUsed here, // we'd introduce false live variables which could nevertheless turn into dead ones in a subsequent // deoptimization, causing a shift in the list of live locals that'd cause erroneous restoration of // continuations (since RewriteException's byteCodeSlots carries an array and not a name-value map). symbolIsConverted(symbol, branchLvarType, targetType); //symbolIsUsed(symbol, branchLvarType); return new LocalVariableConversion(symbol, branchLvarType.type, targetType.type, next); }
void calculateTypeLiveness(final Symbol symbol) { if(symbol.hasSlotFor(Type.OBJECT)) { if(hasConversion(D2O)) { symbol.setHasSlotFor(Type.NUMBER); } if(hasConversion(L2O)) { symbol.setHasSlotFor(Type.LONG); } if(hasConversion(I2O)) { symbol.setHasSlotFor(Type.INT); } } if(symbol.hasSlotFor(Type.NUMBER)) { if(hasConversion(L2D)) { symbol.setHasSlotFor(Type.LONG); } if(hasConversion(I2D)) { symbol.setHasSlotFor(Type.INT); } } if(symbol.hasSlotFor(Type.LONG)) { if(hasConversion(I2L)) { symbol.setHasSlotFor(Type.INT); } } }
@Override public boolean enterTernaryNode(final TernaryNode ternaryNode) { final Expression test = ternaryNode.getTest(); final Expression trueExpr = ternaryNode.getTrueExpression(); final Expression falseExpr = ternaryNode.getFalseExpression(); test.accept(this); final Map<Symbol, LvarType> testExitLvarTypes = localVariableTypes; if(!isAlwaysFalse(test)) { trueExpr.accept(this); } final Map<Symbol, LvarType> trueExitLvarTypes = localVariableTypes; localVariableTypes = testExitLvarTypes; if(!isAlwaysTrue(test)) { falseExpr.accept(this); } final Map<Symbol, LvarType> falseExitLvarTypes = localVariableTypes; localVariableTypes = getUnionTypes(trueExitLvarTypes, falseExitLvarTypes); setConversion((JoinPredecessor)trueExpr, trueExitLvarTypes, localVariableTypes); setConversion((JoinPredecessor)falseExpr, falseExitLvarTypes, localVariableTypes); return false; }
private void loadFastScopeProto(final Symbol symbol, final boolean swap) { final int depth = getScopeProtoDepth(lc.getCurrentBlock(), symbol); assert depth != -1 : "Couldn't find scope depth for symbol " + symbol.getName() + " in " + lc.getCurrentFunction(); if (depth > 0) { if (swap) { method.swap(); } if (depth > 1) { method.load(depth); method.invoke(ScriptObject.GET_PROTO_DEPTH); } else { method.invoke(ScriptObject.GET_PROTO); } if (swap) { method.swap(); } } }
private Map<Symbol, LvarType> getBreakTargetTypes(final LexicalContextNode 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 LocalVariableConversion createConversion(final Symbol symbol, final LvarType branchLvarType, final Map<Symbol, LvarType> joinLvarTypes, final LocalVariableConversion next) { if (invalidatedSymbols.contains(symbol)) { return next; } final LvarType targetType = joinLvarTypes.get(symbol); assert targetType != null; if(targetType == branchLvarType) { return next; } // NOTE: we could naively just use symbolIsUsed(symbol, branchLvarType) here, but that'd be wrong. While // technically a conversion will read the value of the symbol with that type, but it will also write it to a new // type, and that type might be dead (we can't know yet). For this reason, we don't treat conversion reads as // real uses until we know their target type is live. If we didn't do this, and just did a symbolIsUsed here, // we'd introduce false live variables which could nevertheless turn into dead ones in a subsequent // deoptimization, causing a shift in the list of live locals that'd cause erroneous restoration of // continuations (since RewriteException's byteCodeSlots carries an array and not a name-value map). symbolIsConverted(symbol, branchLvarType, targetType); return new LocalVariableConversion(symbol, branchLvarType.type, targetType.type, next); }
/** * Checks if various symbols that were provisionally marked as needing a slot ended up unused, and marks them as not * needing a slot after all. * @param functionNode the function node * @return the passed in node, for easy chaining */ private static FunctionNode removeUnusedSlots(final FunctionNode functionNode) { if (!functionNode.needsCallee()) { functionNode.compilerConstant(CALLEE).setNeedsSlot(false); } if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) { functionNode.compilerConstant(SCOPE).setNeedsSlot(false); } // Named function expressions that end up not referencing themselves won't need a local slot for the self symbol. if(functionNode.isNamedFunctionExpression() && !functionNode.usesSelfSymbol()) { final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName()); if(selfSymbol != null && selfSymbol.isFunctionSelf()) { selfSymbol.setNeedsSlot(false); selfSymbol.clearFlag(Symbol.IS_VAR); } } return functionNode; }
/** * Pushes the value of the symbol to the stack with the specified type. No type conversion is being performed, and * the type is only being used if the symbol addresses a local variable slot. The value of the symbol is loaded if * it addresses a local variable slot, or it is a parameter (in which case it can also be loaded from a vararg array * or the arguments object). If it is neither, the operation is a no-op. * * @param symbol the symbol addressing the value being loaded * @param type the presumed type of the value when it is loaded from a local variable slot * @return the method emitter */ MethodEmitter load(final Symbol symbol, final Type type) { assert symbol != null; if (symbol.hasSlot()) { final int slot = symbol.getSlot(type); debug("load symbol", symbol.getName(), " slot=", slot, "type=", type); load(type, slot); // _try(new Label("dummy"), new Label("dummy2"), recovery); // method.visitTryCatchBlock(new Label(), arg1, arg2, arg3); } else if (symbol.isParam()) { assert functionNode.isVarArg() : "Non-vararg functions have slotted parameters"; final int index = symbol.getFieldIndex(); if (functionNode.needsArguments()) { // ScriptObject.getArgument(int) on arguments debug("load symbol", symbol.getName(), " arguments index=", index); loadCompilerConstant(ARGUMENTS); load(index); ScriptObject.GET_ARGUMENT.invoke(this); } else { // array load from __varargs__ debug("load symbol", symbol.getName(), " array index=", index); loadCompilerConstant(VARARGS); load(symbol.getFieldIndex()); arrayload(); } } return this; }
void storeCompilerConstant(final CompilerConstants cc, final Type type) { final Symbol symbol = getCompilerConstantSymbol(cc); if(!symbol.hasSlot()) { return; } debug("store compiler constant ", symbol); store(symbol, type != null ? type : getCompilerConstantType(cc)); }
void closeLocalVariable(final Symbol symbol, final Label label) { final LocalVariableDef def = localVariableDefs.get(symbol); if(def != null) { endLocalValueDef(symbol, def, label.getLabel()); } if(isReachable()) { markDeadLocalVariable(symbol); } }
private void endLocalValueDef(final Symbol symbol, final LocalVariableDef def, final jdk.internal.org.objectweb.asm.Label label) { String name = symbol.getName(); if (name.equals(THIS.symbolName())) { name = THIS_DEBUGGER.symbolName(); } method.visitLocalVariable(name, def.type.getDescriptor(), null, def.label, label, symbol.getSlot(def.type)); }
/** * Constructor. * * @param symbol the symbol * @param valueType the type of the value * @param returnType the return type * @param paramTypes the function parameter types * @param flags the callsite flags */ SharedScopeCall(final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, final int flags) { this.symbol = symbol; this.valueType = valueType; this.returnType = returnType; this.paramTypes = paramTypes; assert (flags & CALLSITE_OPTIMISTIC) == 0; this.flags = flags; // If paramTypes is not null this is a call, otherwise it's just a get. this.isCall = paramTypes != null; }
/** * Define symbols for all variable declarations at the top of the function scope. This way we can get around * problems like * * while (true) { * break; * if (true) { * var s; * } * } * * to an arbitrary nesting depth. * * see NASHORN-73 * * @param functionNode the FunctionNode we are entering * @param body the body of the FunctionNode we are entering */ private void acceptDeclarations(final FunctionNode functionNode, final Block body) { // This visitor will assign symbol to all declared variables. body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { @Override protected boolean enterDefault(final Node node) { // Don't bother visiting expressions; var is a statement, it can't be inside an expression. // This will also prevent visiting nested functions (as FunctionNode is an expression). return !(node instanceof Expression); } @Override public Node leaveVarNode(final VarNode varNode) { final IdentNode ident = varNode.getName(); final boolean blockScoped = varNode.isBlockScoped(); if (blockScoped && lc.inUnprotectedSwitchContext()) { throwUnprotectedSwitchError(varNode); } final Block block = blockScoped ? lc.getCurrentBlock() : body; final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags()); if (varNode.isFunctionDeclaration()) { symbol.setIsFunctionDeclaration(); } return varNode.setName(ident.setSymbol(symbol)); } }); }
private <T extends Node> T end(final T node, final boolean printNode) { if (debug) { final StringBuilder sb = new StringBuilder(); sb.append("[LEAVE "). append(name(node)). append("] "). append(printNode ? node.toString() : ""). append(" in '"). append(lc.getCurrentFunction().getName()). append('\''); if (node instanceof IdentNode) { final Symbol symbol = ((IdentNode)node).getSymbol(); if (symbol == null) { sb.append(" <NO SYMBOL>"); } else { sb.append(" <symbol=").append(symbol).append('>'); } } log.unindent(); log.info(sb); } return node; }
private void symbolIsConverted(final Symbol symbol, final LvarType from, final LvarType to) { SymbolConversions conversions = symbolConversions.get(symbol); if(conversions == null) { conversions = new SymbolConversions(); symbolConversions.put(symbol, conversions); } conversions.recordConversion(from, to); }
/** * Search for symbol in the lexical context starting from the given block. * @param name Symbol name. * @return Found symbol or null if not found. */ private Symbol findSymbol(final Block block, final String name) { for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) { final Symbol symbol = blocks.next().getExistingSymbol(name); if (symbol != null) { return symbol; } } return null; }
/** * Declares that the current function is using the symbol. * @param symbol the symbol used by the current function. */ private void functionUsesSymbol(final Symbol symbol) { assert symbol != null; if (symbol.isScope()) { if (symbol.isGlobal()) { functionUsesGlobalSymbol(); } else { functionUsesScopeSymbol(symbol); } } else { assert !symbol.isGlobal(); // Every global is also scope } }
private void checkConstAssignment(final IdentNode ident) { // Check for reassignment of constant final Symbol symbol = ident.getSymbol(); if (symbol.isConst()) { throwParserException(ECMAErrors.getMessage("syntax.error.assign.constant", symbol.getName()), ident); } }
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; }
/** * 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 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; }
private void addExternalSymbol(final FunctionNode functionNode, final Symbol symbol, final int depthAtStart) { final int fnId = functionNode.getId(); Map<String, Integer> depths = externalSymbolDepths.get(fnId); if (depths == null) { depths = new HashMap<>(); externalSymbolDepths.put(fnId, depths); } depths.put(symbol.getName(), depthAtStart); }
/** * Marks a local variable as having a specific type from this point onward. Invoked by stores to local variables. * @param symbol the symbol representing the variable * @param type the type */ private void setType(final Symbol symbol, final LvarType type) { if(getLocalVariableTypeOrNull(symbol) == type) { return; } assert symbol.hasSlot(); assert !symbol.isGlobal(); cloneOrNewLocalVariableTypes(); localVariableTypes.put(symbol, type); }
@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; }
private int assignSlots(final Block block, final int firstSlot) { int fromSlot = firstSlot; final MethodEmitter method = methodEmitters.peek(); for (final Symbol symbol : block.getSymbols()) { if (symbol.hasSlot()) { symbol.setFirstSlot(fromSlot); final int toSlot = fromSlot + symbol.slotCount(); method.defineBlockLocalVariable(fromSlot, toSlot); fromSlot = toSlot; } } return fromSlot; }
@Override public boolean enterIdentNode(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); if(symbol.isBytecodeLocal()) { symbolIsUsed(symbol); final LvarType type = getLocalVariableType(symbol); setIdentifierLvarType(identNode, type); typeStack.push(type); } else { pushExpressionType(identNode); } return false; }
@Override public boolean enterBlock(final Block block) { for(final Symbol symbol: block.getSymbols()) { if(symbol.isBytecodeLocal() && getLocalVariableTypeOrNull(symbol) == null) { setType(symbol, LvarType.UNDEFINED); } } return true; }
private void enterDoWhileLoop(final WhileNode loopNode) { 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; } test.accept(this); 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); }
@Override public boolean enterIdentNode(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); if(symbol.isBytecodeLocal()) { symbolIsUsed(symbol); setIdentifierLvarType(identNode, getLocalVariableType(symbol)); } return false; }
@Override public boolean enterTernaryNode(final TernaryNode ternaryNode) { final Expression test = ternaryNode.getTest(); final Expression trueExpr = ternaryNode.getTrueExpression(); final Expression falseExpr = ternaryNode.getFalseExpression(); visitExpression(test); final Map<Symbol, LvarType> testExitLvarTypes = localVariableTypes; final LvarType trueType; if(!isAlwaysFalse(test)) { trueType = visitExpression(trueExpr); } else { trueType = null; } final Map<Symbol, LvarType> trueExitLvarTypes = localVariableTypes; localVariableTypes = testExitLvarTypes; final LvarType falseType; if(!isAlwaysTrue(test)) { falseType = visitExpression(falseExpr); } else { falseType = null; } final Map<Symbol, LvarType> falseExitLvarTypes = localVariableTypes; localVariableTypes = getUnionTypes(trueExitLvarTypes, falseExitLvarTypes); setConversion((JoinPredecessor)trueExpr, trueExitLvarTypes, localVariableTypes); setConversion((JoinPredecessor)falseExpr, falseExitLvarTypes, localVariableTypes); typeStack.push(trueType != null ? falseType != null ? widestLvarType(trueType, falseType) : trueType : assertNotNull(falseType)); return false; }