0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.dev.jjs.impl;
0017:
0018: import com.google.gwt.dev.jjs.InternalCompilerException;
0019: import com.google.gwt.dev.jjs.JsOutputOption;
0020: import com.google.gwt.dev.jjs.ast.Context;
0021: import com.google.gwt.dev.jjs.ast.HasEnclosingType;
0022: import com.google.gwt.dev.jjs.ast.HasName;
0023: import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
0024: import com.google.gwt.dev.jjs.ast.JAbstractMethodBody;
0025: import com.google.gwt.dev.jjs.ast.JArrayRef;
0026: import com.google.gwt.dev.jjs.ast.JArrayType;
0027: import com.google.gwt.dev.jjs.ast.JAssertStatement;
0028: import com.google.gwt.dev.jjs.ast.JBinaryOperation;
0029: import com.google.gwt.dev.jjs.ast.JBinaryOperator;
0030: import com.google.gwt.dev.jjs.ast.JBlock;
0031: import com.google.gwt.dev.jjs.ast.JBreakStatement;
0032: import com.google.gwt.dev.jjs.ast.JCaseStatement;
0033: import com.google.gwt.dev.jjs.ast.JCastOperation;
0034: import com.google.gwt.dev.jjs.ast.JClassLiteral;
0035: import com.google.gwt.dev.jjs.ast.JClassType;
0036: import com.google.gwt.dev.jjs.ast.JConditional;
0037: import com.google.gwt.dev.jjs.ast.JContinueStatement;
0038: import com.google.gwt.dev.jjs.ast.JDoStatement;
0039: import com.google.gwt.dev.jjs.ast.JExpressionStatement;
0040: import com.google.gwt.dev.jjs.ast.JField;
0041: import com.google.gwt.dev.jjs.ast.JFieldRef;
0042: import com.google.gwt.dev.jjs.ast.JForStatement;
0043: import com.google.gwt.dev.jjs.ast.JIfStatement;
0044: import com.google.gwt.dev.jjs.ast.JInstanceOf;
0045: import com.google.gwt.dev.jjs.ast.JInterfaceType;
0046: import com.google.gwt.dev.jjs.ast.JLabel;
0047: import com.google.gwt.dev.jjs.ast.JLabeledStatement;
0048: import com.google.gwt.dev.jjs.ast.JLocal;
0049: import com.google.gwt.dev.jjs.ast.JLocalDeclarationStatement;
0050: import com.google.gwt.dev.jjs.ast.JLocalRef;
0051: import com.google.gwt.dev.jjs.ast.JMethod;
0052: import com.google.gwt.dev.jjs.ast.JMethodBody;
0053: import com.google.gwt.dev.jjs.ast.JMethodCall;
0054: import com.google.gwt.dev.jjs.ast.JNewArray;
0055: import com.google.gwt.dev.jjs.ast.JNewInstance;
0056: import com.google.gwt.dev.jjs.ast.JParameter;
0057: import com.google.gwt.dev.jjs.ast.JParameterRef;
0058: import com.google.gwt.dev.jjs.ast.JPostfixOperation;
0059: import com.google.gwt.dev.jjs.ast.JPrefixOperation;
0060: import com.google.gwt.dev.jjs.ast.JProgram;
0061: import com.google.gwt.dev.jjs.ast.JReferenceType;
0062: import com.google.gwt.dev.jjs.ast.JReturnStatement;
0063: import com.google.gwt.dev.jjs.ast.JStatement;
0064: import com.google.gwt.dev.jjs.ast.JSwitchStatement;
0065: import com.google.gwt.dev.jjs.ast.JThisRef;
0066: import com.google.gwt.dev.jjs.ast.JThrowStatement;
0067: import com.google.gwt.dev.jjs.ast.JTryStatement;
0068: import com.google.gwt.dev.jjs.ast.JType;
0069: import com.google.gwt.dev.jjs.ast.JTypeOracle;
0070: import com.google.gwt.dev.jjs.ast.JUnaryOperator;
0071: import com.google.gwt.dev.jjs.ast.JVisitor;
0072: import com.google.gwt.dev.jjs.ast.JWhileStatement;
0073: import com.google.gwt.dev.jjs.ast.js.JClassSeed;
0074: import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
0075: import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
0076: import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
0077: import com.google.gwt.dev.jjs.ast.js.JsonArray;
0078: import com.google.gwt.dev.jjs.ast.js.JsonObject;
0079: import com.google.gwt.dev.jjs.ast.js.JsonObject.JsonPropInit;
0080: import com.google.gwt.dev.js.ast.JsArrayAccess;
0081: import com.google.gwt.dev.js.ast.JsArrayLiteral;
0082: import com.google.gwt.dev.js.ast.JsBinaryOperation;
0083: import com.google.gwt.dev.js.ast.JsBinaryOperator;
0084: import com.google.gwt.dev.js.ast.JsBlock;
0085: import com.google.gwt.dev.js.ast.JsBreak;
0086: import com.google.gwt.dev.js.ast.JsCase;
0087: import com.google.gwt.dev.js.ast.JsCatch;
0088: import com.google.gwt.dev.js.ast.JsConditional;
0089: import com.google.gwt.dev.js.ast.JsContext;
0090: import com.google.gwt.dev.js.ast.JsContinue;
0091: import com.google.gwt.dev.js.ast.JsDefault;
0092: import com.google.gwt.dev.js.ast.JsDoWhile;
0093: import com.google.gwt.dev.js.ast.JsExprStmt;
0094: import com.google.gwt.dev.js.ast.JsExpression;
0095: import com.google.gwt.dev.js.ast.JsFor;
0096: import com.google.gwt.dev.js.ast.JsFunction;
0097: import com.google.gwt.dev.js.ast.JsIf;
0098: import com.google.gwt.dev.js.ast.JsIntegralLiteral;
0099: import com.google.gwt.dev.js.ast.JsInvocation;
0100: import com.google.gwt.dev.js.ast.JsLabel;
0101: import com.google.gwt.dev.js.ast.JsModVisitor;
0102: import com.google.gwt.dev.js.ast.JsName;
0103: import com.google.gwt.dev.js.ast.JsNameRef;
0104: import com.google.gwt.dev.js.ast.JsNew;
0105: import com.google.gwt.dev.js.ast.JsNode;
0106: import com.google.gwt.dev.js.ast.JsObjectLiteral;
0107: import com.google.gwt.dev.js.ast.JsParameter;
0108: import com.google.gwt.dev.js.ast.JsPostfixOperation;
0109: import com.google.gwt.dev.js.ast.JsPrefixOperation;
0110: import com.google.gwt.dev.js.ast.JsProgram;
0111: import com.google.gwt.dev.js.ast.JsPropertyInitializer;
0112: import com.google.gwt.dev.js.ast.JsReturn;
0113: import com.google.gwt.dev.js.ast.JsScope;
0114: import com.google.gwt.dev.js.ast.JsStatement;
0115: import com.google.gwt.dev.js.ast.JsSwitch;
0116: import com.google.gwt.dev.js.ast.JsSwitchMember;
0117: import com.google.gwt.dev.js.ast.JsThisRef;
0118: import com.google.gwt.dev.js.ast.JsThrow;
0119: import com.google.gwt.dev.js.ast.JsTry;
0120: import com.google.gwt.dev.js.ast.JsUnaryOperation;
0121: import com.google.gwt.dev.js.ast.JsUnaryOperator;
0122: import com.google.gwt.dev.js.ast.JsVars;
0123: import com.google.gwt.dev.js.ast.JsWhile;
0124: import com.google.gwt.dev.js.ast.JsVars.JsVar;
0125:
0126: import java.math.BigInteger;
0127: import java.util.Arrays;
0128: import java.util.Collections;
0129: import java.util.HashMap;
0130: import java.util.HashSet;
0131: import java.util.IdentityHashMap;
0132: import java.util.Iterator;
0133: import java.util.List;
0134: import java.util.Map;
0135: import java.util.Set;
0136: import java.util.Stack;
0137: import java.util.TreeMap;
0138:
0139: /**
0140: * Creates a JavaScript AST from a <code>JProgram</code> node.
0141: */
0142: public class GenerateJavaScriptAST {
0143:
0144: private class CreateNamesAndScopesVisitor extends JVisitor {
0145:
0146: private final JField arrayLengthField = program
0147: .getIndexedField("Array.length");
0148: private final Stack<JsScope> scopeStack = new Stack<JsScope>();
0149:
0150: @Override
0151: public void endVisit(JClassType x, Context ctx) {
0152: pop();
0153: }
0154:
0155: @Override
0156: public void endVisit(JField x, Context ctx) {
0157: String name = x.getName();
0158: String mangleName = mangleName(x);
0159: if (x.isStatic()) {
0160: names.put(x, topScope.declareName(mangleName, name));
0161: } else {
0162: JsName jsName;
0163: if (x == arrayLengthField) {
0164: jsName = peek().declareName(name);
0165: jsName.setObfuscatable(false);
0166: } else if (belongsToSpecialObfuscatedType(x)) {
0167: jsName = peek().declareName(
0168: mangleNameSpecialObfuscate(x));
0169: jsName.setObfuscatable(false);
0170: } else {
0171: jsName = peek().declareName(mangleName, name);
0172: }
0173: names.put(x, jsName);
0174: }
0175: }
0176:
0177: @Override
0178: public void endVisit(JInterfaceType x, Context ctx) {
0179: pop();
0180: }
0181:
0182: @Override
0183: public void endVisit(JLabel x, Context ctx) {
0184: if (names.get(x) != null) {
0185: return;
0186: }
0187: names.put(x, peek().declareName(x.getName()));
0188: }
0189:
0190: @Override
0191: public void endVisit(JLocal x, Context ctx) {
0192: // locals can conflict, that's okay just reuse the same variable
0193: JsScope scope = peek();
0194: JsName jsName = scope.declareName(x.getName());
0195: names.put(x, jsName);
0196: }
0197:
0198: @Override
0199: public void endVisit(JMethod x, Context ctx) {
0200: pop();
0201: }
0202:
0203: @Override
0204: public void endVisit(JParameter x, Context ctx) {
0205: names.put(x, peek().declareName(x.getName()));
0206: }
0207:
0208: @Override
0209: public void endVisit(JProgram x, Context ctx) {
0210: /*
0211: * put the null method and field into objectScope since they can be
0212: * referenced as instance on null-types (as determined by type flow)
0213: */
0214: JMethod nullMethod = x.getNullMethod();
0215: polymorphicNames.put(nullMethod, objectScope
0216: .declareName(nullMethod.getName()));
0217: JField nullField = x.getNullField();
0218: JsName nullFieldName = objectScope.declareName(nullField
0219: .getName());
0220: names.put(nullField, nullFieldName);
0221:
0222: /*
0223: * put nullMethod in the global scope, too; it's the replacer for clinits
0224: */
0225: nullMethodName = topScope.declareName(nullMethod.getName());
0226: names.put(nullMethod, nullMethodName);
0227: }
0228:
0229: @Override
0230: public boolean visit(JClassType x, Context ctx) {
0231: // have I already been visited as a super type?
0232: JsScope myScope = classScopes.get(x);
0233: if (myScope != null) {
0234: push(myScope);
0235: return false;
0236: }
0237:
0238: // My seed function name
0239: names.put(x, topScope.declareName(getNameString(x), x
0240: .getShortName()));
0241:
0242: // My class scope
0243: if (x.extnds == null) {
0244: myScope = objectScope;
0245: } else {
0246: JsScope parentScope = classScopes.get(x.extnds);
0247: // Run my superclass first!
0248: if (parentScope == null) {
0249: accept(x.extnds);
0250: }
0251: parentScope = classScopes.get(x.extnds);
0252: assert (parentScope != null);
0253: /*
0254: * WEIRD: we wedge the global interface scope in between object and all
0255: * of its subclasses; this ensures that interface method names trump all
0256: * (except Object method names)
0257: */
0258: if (parentScope == objectScope) {
0259: parentScope = interfaceScope;
0260: }
0261: myScope = new JsScope(parentScope, "class "
0262: + x.getShortName());
0263: }
0264: classScopes.put(x, myScope);
0265:
0266: push(myScope);
0267: return true;
0268: }
0269:
0270: @Override
0271: public boolean visit(JInterfaceType x, Context ctx) {
0272: // interfaces have no name at run time
0273: push(interfaceScope);
0274: return true;
0275: }
0276:
0277: @Override
0278: public boolean visit(JMethod x, Context ctx) {
0279: // my polymorphic name
0280: String name = x.getName();
0281: if (!x.isStatic()) {
0282: if (polymorphicNames.get(x) == null) {
0283: String mangleName = mangleNameForPoly(x);
0284: JsName polyName;
0285: if (belongsToSpecialObfuscatedType(x)) {
0286: polyName = interfaceScope
0287: .declareName(mangleNameSpecialObfuscate(x));
0288: polyName.setObfuscatable(false);
0289: } else {
0290: polyName = interfaceScope.declareName(
0291: mangleName, name);
0292: }
0293: polymorphicNames.put(x, polyName);
0294: }
0295: }
0296:
0297: if (x.isAbstract()) {
0298: // just push a dummy scope that we can pop in endVisit
0299: push(null);
0300: return false;
0301: }
0302:
0303: // my global name
0304: JsName globalName;
0305: if (x.getEnclosingType() == null) {
0306: globalName = topScope.declareName(name);
0307: } else {
0308: String mangleName = mangleNameForGlobal(x);
0309: globalName = topScope.declareName(mangleName, name);
0310: }
0311: names.put(x, globalName);
0312:
0313: JsFunction jsFunction;
0314: if (x.isNative()) {
0315: // set the global name of the JSNI peer
0316: JsniMethodBody body = (JsniMethodBody) x.getBody();
0317: jsFunction = body.getFunc();
0318: jsFunction.setName(globalName);
0319: } else {
0320: // create a new peer JsFunction
0321: jsFunction = new JsFunction(topScope, globalName, true);
0322: methodBodyMap.put(x.getBody(), jsFunction);
0323: }
0324: push(jsFunction.getScope());
0325: return true;
0326: }
0327:
0328: @Override
0329: public boolean visit(JTryStatement x, Context ctx) {
0330: accept(x.getTryBlock());
0331:
0332: List<JLocalRef> catchArgs = x.getCatchArgs();
0333: List<JBlock> catchBlocks = x.getCatchBlocks();
0334: for (int i = 0, c = catchArgs.size(); i < c; ++i) {
0335: JLocalRef arg = catchArgs.get(i);
0336: JBlock catchBlock = catchBlocks.get(i);
0337: JsCatch jsCatch = new JsCatch(peek(), arg.getTarget()
0338: .getName());
0339: JsParameter jsParam = jsCatch.getParameter();
0340: names.put(arg.getTarget(), jsParam.getName());
0341: catchMap.put(catchBlock, jsCatch);
0342:
0343: push(jsCatch.getScope());
0344: accept(catchBlock);
0345: pop();
0346: }
0347:
0348: // TODO: normalize this so it's never null?
0349: if (x.getFinallyBlock() != null) {
0350: accept(x.getFinallyBlock());
0351: }
0352: return false;
0353: }
0354:
0355: private JsScope peek() {
0356: return scopeStack.peek();
0357: }
0358:
0359: private void pop() {
0360: scopeStack.pop();
0361: }
0362:
0363: private void push(JsScope scope) {
0364: scopeStack.push(scope);
0365: }
0366: }
0367:
0368: private class GenerateJavaScriptVisitor extends
0369: GenerateJavaScriptLiterals {
0370:
0371: private final Set<JClassType> alreadyRan = new HashSet<JClassType>();
0372:
0373: private JMethod currentMethod = null;
0374:
0375: private final JsName globalTemp = topScope.declareName("_");
0376:
0377: private final JsName prototype = objectScope
0378: .declareName("prototype");
0379:
0380: private Map<JClassType, JsFunction> clinitMap = new HashMap<JClassType, JsFunction>();
0381:
0382: {
0383: globalTemp.setObfuscatable(false);
0384: prototype.setObfuscatable(false);
0385: }
0386:
0387: public GenerateJavaScriptVisitor() {
0388: super (jsProgram);
0389: }
0390:
0391: @Override
0392: public void endVisit(JAbsentArrayDimension x, Context ctx) {
0393: throw new InternalCompilerException("Should not get here.");
0394: }
0395:
0396: @Override
0397: public void endVisit(JArrayRef x, Context ctx) {
0398: JsArrayAccess jsArrayAccess = new JsArrayAccess();
0399: jsArrayAccess.setIndexExpr((JsExpression) pop());
0400: jsArrayAccess.setArrayExpr((JsExpression) pop());
0401: push(jsArrayAccess);
0402: }
0403:
0404: @Override
0405: public void endVisit(JAssertStatement x, Context ctx) {
0406: throw new InternalCompilerException("Should not get here.");
0407: }
0408:
0409: @Override
0410: public void endVisit(JBinaryOperation x, Context ctx) {
0411: JsExpression rhs = (JsExpression) pop(); // rhs
0412: JsExpression lhs = (JsExpression) pop(); // lhs
0413: JsBinaryOperator myOp = JavaToJsOperatorMap.get(x.getOp());
0414:
0415: /*
0416: * Use === and !== on reference types, or else you can get wrong answers
0417: * when Object.toString() == 'some string'.
0418: */
0419: if (myOp == JsBinaryOperator.EQ
0420: && x.getLhs().getType() instanceof JReferenceType
0421: && x.getRhs().getType() instanceof JReferenceType) {
0422: myOp = JsBinaryOperator.REF_EQ;
0423: } else if (myOp == JsBinaryOperator.NEQ
0424: && x.getLhs().getType() instanceof JReferenceType
0425: && x.getRhs().getType() instanceof JReferenceType) {
0426: myOp = JsBinaryOperator.REF_NEQ;
0427: }
0428:
0429: push(new JsBinaryOperation(myOp, lhs, rhs));
0430: }
0431:
0432: @Override
0433: public void endVisit(JBlock x, Context ctx) {
0434: JsBlock jsBlock = new JsBlock();
0435: List<JsStatement> stmts = jsBlock.getStatements();
0436: popList(stmts, x.statements.size()); // stmts
0437: Iterator<JsStatement> iterator = stmts.iterator();
0438: while (iterator.hasNext()) {
0439: JsStatement stmt = iterator.next();
0440: if (stmt == jsProgram.getEmptyStmt()) {
0441: iterator.remove();
0442: }
0443: }
0444: push(jsBlock);
0445: }
0446:
0447: @Override
0448: public void endVisit(JBreakStatement x, Context ctx) {
0449: JsNameRef labelRef = null;
0450: if (x.getLabel() != null) {
0451: JsLabel label = (JsLabel) pop(); // label
0452: labelRef = label.getName().makeRef();
0453: }
0454: push(new JsBreak(labelRef));
0455: }
0456:
0457: @Override
0458: public void endVisit(JCaseStatement x, Context ctx) {
0459: if (x.getExpr() == null) {
0460: push(new JsDefault());
0461: } else {
0462: JsCase jsCase = new JsCase();
0463: jsCase.setCaseExpr((JsExpression) pop()); // expr
0464: push(jsCase);
0465: }
0466: }
0467:
0468: @Override
0469: public void endVisit(JCastOperation x, Context ctx) {
0470: throw new InternalCompilerException("Should not get here.");
0471: }
0472:
0473: @Override
0474: public void endVisit(JClassLiteral x, Context ctx) {
0475: JsExpression classObjectAllocation = pop(); // classObjectAllocation
0476:
0477: // My seed function name
0478: String nameString = x.getRefType().getJavahSignatureName()
0479: + "_classlit";
0480: JsName classLit = topScope.declareName(nameString);
0481: classLits.put(x.getRefType(), classLit);
0482: classObjects.put(classLit, classObjectAllocation);
0483: push(classLit.makeRef());
0484: }
0485:
0486: @Override
0487: public void endVisit(JClassSeed x, Context ctx) {
0488: push(names.get(x.getRefType()).makeRef());
0489: }
0490:
0491: @Override
0492: public void endVisit(JClassType x, Context ctx) {
0493: if (alreadyRan.contains(x)) {
0494: return;
0495: }
0496:
0497: alreadyRan.add(x);
0498:
0499: List<JsFunction> jsFuncs = popList(x.methods.size()); // methods
0500: List<JsNode> jsFields = popList(x.fields.size()); // fields
0501:
0502: if (typeOracle.hasClinit(x)) {
0503: JsFunction super Clinit = clinitMap.get(x.extnds);
0504: JsFunction myClinit = jsFuncs.get(0);
0505: handleClinit(myClinit, super Clinit);
0506: clinitMap.put(x, myClinit);
0507: } else {
0508: jsFuncs.set(0, null);
0509: }
0510:
0511: List<JsStatement> globalStmts = jsProgram.getGlobalBlock()
0512: .getStatements();
0513:
0514: // declare all methods into the global scope
0515: for (int i = 0; i < jsFuncs.size(); ++i) {
0516: JsFunction func = jsFuncs.get(i);
0517: if (func != null) {
0518: globalStmts.add(func.makeStmt());
0519: }
0520: }
0521:
0522: if (typeOracle.isInstantiatedType(x)) {
0523: generateClassSetup(x, globalStmts);
0524: }
0525:
0526: // setup fields
0527: JsVars vars = new JsVars();
0528: for (int i = 0; i < jsFields.size(); ++i) {
0529: JsNode node = jsFields.get(i);
0530: if (node instanceof JsVar) {
0531: vars.add((JsVar) node);
0532: } else {
0533: assert (node instanceof JsStatement);
0534: JsStatement stmt = (JsStatement) jsFields.get(i);
0535: globalStmts.add(stmt);
0536: }
0537: }
0538: if (!vars.isEmpty()) {
0539: globalStmts.add(vars);
0540: }
0541: }
0542:
0543: @Override
0544: public void endVisit(JConditional x, Context ctx) {
0545: JsExpression elseExpr = (JsExpression) pop(); // elseExpr
0546: JsExpression thenExpr = (JsExpression) pop(); // thenExpr
0547: JsExpression ifTest = (JsExpression) pop(); // ifTest
0548: push(new JsConditional(ifTest, thenExpr, elseExpr));
0549: }
0550:
0551: @Override
0552: public void endVisit(JContinueStatement x, Context ctx) {
0553: JsNameRef labelRef = null;
0554: if (x.getLabel() != null) {
0555: JsLabel label = (JsLabel) pop(); // label
0556: labelRef = label.getName().makeRef();
0557: }
0558: push(new JsContinue(labelRef));
0559: }
0560:
0561: @Override
0562: public void endVisit(JDoStatement x, Context ctx) {
0563: JsDoWhile stmt = new JsDoWhile();
0564: if (x.getBody() != null) {
0565: stmt.setBody((JsStatement) pop()); // body
0566: } else {
0567: stmt.setBody(jsProgram.getEmptyStmt());
0568: }
0569: stmt.setCondition((JsExpression) pop()); // testExpr
0570: push(stmt);
0571: }
0572:
0573: @Override
0574: public void endVisit(JExpressionStatement x, Context ctx) {
0575: JsExpression expr = (JsExpression) pop(); // expr
0576: push(expr.makeStmt());
0577: }
0578:
0579: @Override
0580: public void endVisit(JField x, Context ctx) {
0581: // if we need an initial value, create an assignment
0582: if (x.constInitializer != null) {
0583: // setup the constant value
0584: accept(x.constInitializer);
0585: } else if (x == program.getIndexedField("Cast.typeIdArray")) {
0586: // magic: setup the type id table
0587: push(generateTypeTable());
0588: } else if (!x.hasInitializer()) {
0589: // setup a default value
0590: accept(x.getType().getDefaultValue());
0591: } else {
0592: // the variable is setup during clinit, no need to initialize here
0593: push(null);
0594: }
0595: JsExpression rhs = (JsExpression) pop();
0596: JsName name = names.get(x);
0597:
0598: if (x.isStatic()) {
0599: // setup a var for the static
0600: JsVar var = new JsVar(name);
0601: var.setInitExpr(rhs);
0602: push(var);
0603: } else {
0604: // for non-statics, only setup an assignment if needed
0605: if (rhs != null) {
0606: JsNameRef fieldRef = name.makeRef();
0607: fieldRef.setQualifier(globalTemp.makeRef());
0608: JsExpression asg = createAssignment(fieldRef, rhs);
0609: push(new JsExprStmt(asg));
0610: } else {
0611: push(null);
0612: }
0613: }
0614: }
0615:
0616: @Override
0617: public void endVisit(JFieldRef x, Context ctx) {
0618: JField field = x.getField();
0619: JsName jsFieldName = names.get(field);
0620: JsNameRef nameRef = jsFieldName.makeRef();
0621: JsExpression curExpr = nameRef;
0622:
0623: /*
0624: * Note: the comma expressions here would cause an illegal tree state if
0625: * the result expression ended up on the lhs of an assignment. A hack in
0626: * in endVisit(JBinaryOperation) rectifies the situation.
0627: */
0628:
0629: // See if we need a clinit
0630: JsInvocation jsInvocation = maybeCreateClinitCall(field);
0631: if (jsInvocation != null) {
0632: curExpr = createCommaExpression(jsInvocation, curExpr);
0633: }
0634:
0635: if (x.getInstance() != null) {
0636: JsExpression qualifier = (JsExpression) pop();
0637: if (field.isStatic()) {
0638: // unnecessary qualifier, create a comma expression
0639: curExpr = createCommaExpression(qualifier, curExpr);
0640: } else {
0641: // necessary qualifier, qualify the name ref
0642: nameRef.setQualifier(qualifier);
0643: }
0644: }
0645:
0646: push(curExpr);
0647: }
0648:
0649: @Override
0650: public void endVisit(JForStatement x, Context ctx) {
0651: JsFor jsFor = new JsFor();
0652:
0653: // body
0654: if (x.getBody() != null) {
0655: jsFor.setBody((JsStatement) pop());
0656: } else {
0657: jsFor.setBody(jsProgram.getEmptyStmt());
0658: }
0659:
0660: // increments
0661: {
0662: JsExpression incrExpr = null;
0663: List<JsExprStmt> exprStmts = popList(x.getIncrements()
0664: .size());
0665: for (int i = 0; i < exprStmts.size(); ++i) {
0666: JsExprStmt exprStmt = exprStmts.get(i);
0667: incrExpr = createCommaExpression(incrExpr, exprStmt
0668: .getExpression());
0669: }
0670: jsFor.setIncrExpr(incrExpr);
0671: }
0672:
0673: // condition
0674: if (x.getTestExpr() != null) {
0675: jsFor.setCondition((JsExpression) pop());
0676: }
0677:
0678: // initializers
0679: JsExpression initExpr = null;
0680: List<JsExprStmt> initStmts = popList(x.getInitializers()
0681: .size());
0682: for (int i = 0; i < initStmts.size(); ++i) {
0683: JsExprStmt initStmt = initStmts.get(i);
0684: if (initStmt != null) {
0685: initExpr = createCommaExpression(initExpr, initStmt
0686: .getExpression());
0687: }
0688: }
0689: jsFor.setInitExpr(initExpr);
0690:
0691: push(jsFor);
0692: }
0693:
0694: @Override
0695: public void endVisit(JIfStatement x, Context ctx) {
0696: JsIf stmt = new JsIf();
0697:
0698: if (x.getElseStmt() != null) {
0699: stmt.setElseStmt((JsStatement) pop()); // elseStmt
0700: }
0701:
0702: if (x.getThenStmt() != null) {
0703: stmt.setThenStmt((JsStatement) pop()); // thenStmt
0704: } else {
0705: stmt.setThenStmt(jsProgram.getEmptyStmt());
0706: }
0707:
0708: stmt.setIfExpr((JsExpression) pop()); // ifExpr
0709: push(stmt);
0710: }
0711:
0712: @Override
0713: public void endVisit(JInstanceOf x, Context ctx) {
0714: throw new InternalCompilerException("Should not get here.");
0715: }
0716:
0717: @Override
0718: public void endVisit(JInterfaceType x, Context ctx) {
0719: List<JsFunction> jsFuncs = popList(x.methods.size()); // methods
0720: List<JsVar> jsFields = popList(x.fields.size()); // fields
0721: List<JsStatement> globalStmts = jsProgram.getGlobalBlock()
0722: .getStatements();
0723:
0724: if (typeOracle.hasClinit(x)) {
0725: JsFunction clinitFunc = jsFuncs.get(0);
0726: handleClinit(clinitFunc, null);
0727: globalStmts.add(clinitFunc.makeStmt());
0728: }
0729:
0730: // setup fields
0731: JsVars vars = new JsVars();
0732: for (int i = 0; i < jsFields.size(); ++i) {
0733: vars.add(jsFields.get(i));
0734: }
0735: if (!vars.isEmpty()) {
0736: globalStmts.add(vars);
0737: }
0738: }
0739:
0740: @Override
0741: public void endVisit(JLabel x, Context ctx) {
0742: push(new JsLabel(names.get(x)));
0743: }
0744:
0745: @Override
0746: public void endVisit(JLabeledStatement x, Context ctx) {
0747: JsStatement body = (JsStatement) pop(); // body
0748: JsLabel label = (JsLabel) pop(); // label
0749: label.setStmt(body);
0750: push(label);
0751: }
0752:
0753: @Override
0754: public void endVisit(JLocal x, Context ctx) {
0755: push(names.get(x).makeRef());
0756: }
0757:
0758: @Override
0759: public void endVisit(JLocalDeclarationStatement x, Context ctx) {
0760:
0761: if (x.getInitializer() == null) {
0762: pop(); // localRef
0763: /*
0764: * local decls can only appear in blocks, so it's okay to push null
0765: * instead of an empty statement
0766: */
0767: push(null);
0768: return;
0769: }
0770:
0771: JsExpression initializer = (JsExpression) pop(); // initializer
0772: JsNameRef localRef = (JsNameRef) pop(); // localRef
0773:
0774: JsBinaryOperation binOp = new JsBinaryOperation(
0775: JsBinaryOperator.ASG, localRef, initializer);
0776:
0777: push(binOp.makeStmt());
0778: }
0779:
0780: @Override
0781: public void endVisit(JLocalRef x, Context ctx) {
0782: push(names.get(x.getTarget()).makeRef());
0783: }
0784:
0785: @Override
0786: public void endVisit(JMethod x, Context ctx) {
0787: if (x.isAbstract()) {
0788: push(null);
0789: return;
0790: }
0791:
0792: JsFunction jsFunc = (JsFunction) pop(); // body
0793: List<JsParameter> params = popList(x.params.size()); // params
0794:
0795: if (!x.isNative()) {
0796: // Setup params on the generated function. A native method already got
0797: // its jsParams set in BuildTypeMap.
0798: // TODO: Do we really need to do that in BuildTypeMap?
0799: List<JsParameter> jsParams = jsFunc.getParameters();
0800: for (int i = 0; i < params.size(); ++i) {
0801: JsParameter param = params.get(i);
0802: jsParams.add(param);
0803: }
0804: }
0805:
0806: JsInvocation jsInvocation = maybeCreateClinitCall(x);
0807: if (jsInvocation != null) {
0808: jsFunc.getBody().getStatements().add(0,
0809: jsInvocation.makeStmt());
0810: }
0811:
0812: push(jsFunc);
0813: currentMethod = null;
0814: }
0815:
0816: @Override
0817: public void endVisit(JMethodBody x, Context ctx) {
0818:
0819: JsBlock body = (JsBlock) pop();
0820: List<JsNameRef> locals = popList(x.locals.size()); // locals
0821:
0822: JsFunction jsFunc = methodBodyMap.get(x);
0823: jsFunc.setBody(body); // body
0824:
0825: /*
0826: * Emit a statement to declare the method's complete set of local
0827: * variables. JavaScript doesn't have the same concept of lexical scoping
0828: * as Java, so it's okay to just predeclare all local vars at the top of
0829: * the function, which saves us having to use the "var" keyword over and
0830: * over.
0831: *
0832: * Note: it's fine to use the same JS ident to represent two different
0833: * Java locals of the same name since they could never conflict with each
0834: * other in Java. We use the alreadySeen set to make sure we don't declare
0835: * the same-named local var twice.
0836: */
0837: JsVars vars = new JsVars();
0838: Set<String> alreadySeen = new HashSet<String>();
0839: for (int i = 0; i < locals.size(); ++i) {
0840: JsName name = names.get(x.locals.get(i));
0841: String ident = name.getIdent();
0842: if (!alreadySeen.contains(ident)) {
0843: alreadySeen.add(ident);
0844: vars.add(new JsVar(name));
0845: }
0846: }
0847:
0848: if (!vars.isEmpty()) {
0849: jsFunc.getBody().getStatements().add(0, vars);
0850: }
0851:
0852: push(jsFunc);
0853: }
0854:
0855: @Override
0856: public void endVisit(JMethodCall x, Context ctx) {
0857: JMethod method = x.getTarget();
0858: JsInvocation jsInvocation = new JsInvocation();
0859:
0860: popList(jsInvocation.getArguments(), x.getArgs().size()); // args
0861:
0862: JsNameRef qualifier;
0863: JsExpression unnecessaryQualifier = null;
0864: if (method.isStatic()) {
0865: if (x.getInstance() != null) {
0866: unnecessaryQualifier = (JsExpression) pop(); // instance
0867: }
0868: qualifier = names.get(method).makeRef();
0869: } else {
0870: if (x.isStaticDispatchOnly()) {
0871: /*
0872: * Dispatch statically (odd case). This happens when a call that must
0873: * be static is targeting an instance method that could not be
0874: * transformed into a static. For example, making a super call into a
0875: * native method currently causes this, because we cannot currently
0876: * staticify native methods.
0877: *
0878: * Have to use a "call" construct.
0879: */
0880: JsName callName = objectScope.declareName("call");
0881: callName.setObfuscatable(false);
0882: qualifier = callName.makeRef();
0883: qualifier.setQualifier(names.get(method).makeRef());
0884: jsInvocation.getArguments().add(0,
0885: (JsExpression) pop()); // instance
0886: } else {
0887: // Dispatch polymorphically (normal case).
0888: qualifier = polymorphicNames.get(method).makeRef();
0889: qualifier.setQualifier((JsExpression) pop()); // instance
0890: }
0891: }
0892: jsInvocation.setQualifier(qualifier);
0893: push(createCommaExpression(unnecessaryQualifier,
0894: jsInvocation));
0895: }
0896:
0897: @Override
0898: public void endVisit(JMultiExpression x, Context ctx) {
0899: List<JsExpression> exprs = popList(x.exprs.size());
0900: JsExpression cur = null;
0901: for (int i = 0; i < exprs.size(); ++i) {
0902: JsExpression next = exprs.get(i);
0903: cur = createCommaExpression(cur, next);
0904: }
0905: push(cur);
0906: }
0907:
0908: @Override
0909: public void endVisit(JNewArray x, Context ctx) {
0910: throw new InternalCompilerException("Should not get here.");
0911: }
0912:
0913: @Override
0914: public void endVisit(JNewInstance x, Context ctx) {
0915: JsNew newOp = new JsNew();
0916: JsNameRef nameRef = names.get(x.getType()).makeRef();
0917: newOp.setConstructorExpression(nameRef);
0918: push(newOp);
0919: }
0920:
0921: @Override
0922: public void endVisit(JParameter x, Context ctx) {
0923: push(new JsParameter(names.get(x)));
0924: }
0925:
0926: @Override
0927: public void endVisit(JParameterRef x, Context ctx) {
0928: push(names.get(x.getTarget()).makeRef());
0929: }
0930:
0931: @Override
0932: public void endVisit(JPostfixOperation x, Context ctx) {
0933: JsUnaryOperation op = new JsPostfixOperation(
0934: JavaToJsOperatorMap.get(x.getOp()),
0935: ((JsExpression) pop())); // arg
0936: push(op);
0937: }
0938:
0939: @Override
0940: public void endVisit(JPrefixOperation x, Context ctx) {
0941: JsUnaryOperation op = new JsPrefixOperation(
0942: JavaToJsOperatorMap.get(x.getOp()),
0943: ((JsExpression) pop())); // arg
0944: push(op);
0945: }
0946:
0947: @Override
0948: public void endVisit(JProgram x, Context ctx) {
0949: List<JsStatement> globalStmts = jsProgram.getGlobalBlock()
0950: .getStatements();
0951:
0952: // types don't push
0953:
0954: // Generate entry methods
0955: List<JsFunction> entryFuncs = popList(x.entryMethods.size()); // entryMethods
0956: for (int i = 0; i < entryFuncs.size(); ++i) {
0957: JsFunction func = entryFuncs.get(i);
0958: if (func != null) {
0959: globalStmts.add(func.makeStmt());
0960: }
0961: }
0962:
0963: generateGwtOnLoad(entryFuncs, globalStmts);
0964: generateNullFunc(globalStmts);
0965:
0966: // Add a few things onto the beginning.
0967:
0968: // Reserve the "_" identifier.
0969: JsVars vars = new JsVars();
0970: vars.add(new JsVar(globalTemp));
0971: globalStmts.add(0, vars);
0972:
0973: // Generate class objects.
0974: vars = new JsVars();
0975: generateClassLiterals(vars);
0976: if (!vars.isEmpty()) {
0977: globalStmts.add(vars);
0978: }
0979: }
0980:
0981: @Override
0982: public void endVisit(JReturnStatement x, Context ctx) {
0983: if (x.getExpr() != null) {
0984: push(new JsReturn((JsExpression) pop())); // expr
0985: } else {
0986: push(new JsReturn());
0987: }
0988: }
0989:
0990: @Override
0991: public void endVisit(JsniMethodRef x, Context ctx) {
0992: JMethod method = x.getTarget();
0993: JsNameRef nameRef = names.get(method).makeRef();
0994: push(nameRef);
0995: }
0996:
0997: @Override
0998: public void endVisit(JsonArray x, Context ctx) {
0999: JsArrayLiteral jsArrayLiteral = new JsArrayLiteral();
1000: popList(jsArrayLiteral.getExpressions(), x.exprs.size());
1001: push(jsArrayLiteral);
1002: }
1003:
1004: @Override
1005: public void endVisit(JsonObject x, Context ctx) {
1006: JsObjectLiteral jsObjectLiteral = new JsObjectLiteral();
1007: popList(jsObjectLiteral.getPropertyInitializers(),
1008: x.propInits.size());
1009: push(jsObjectLiteral);
1010: }
1011:
1012: @Override
1013: public void endVisit(JsonPropInit init, Context ctx) {
1014: JsExpression valueExpr = (JsExpression) pop();
1015: JsExpression labelExpr = (JsExpression) pop();
1016: push(new JsPropertyInitializer(labelExpr, valueExpr));
1017: }
1018:
1019: @Override
1020: public void endVisit(JThisRef x, Context ctx) {
1021: push(new JsThisRef());
1022: }
1023:
1024: @Override
1025: public void endVisit(JThrowStatement x, Context ctx) {
1026: push(new JsThrow((JsExpression) pop())); // expr
1027: }
1028:
1029: @Override
1030: public void endVisit(JTryStatement x, Context ctx) {
1031: JsTry jsTry = new JsTry();
1032:
1033: if (x.getFinallyBlock() != null) {
1034: JsBlock finallyBlock = (JsBlock) pop(); // finallyBlock
1035: if (finallyBlock.getStatements().size() > 0) {
1036: jsTry.setFinallyBlock(finallyBlock);
1037: }
1038: }
1039:
1040: int size = x.getCatchArgs().size();
1041: assert (size < 2 && size == x.getCatchBlocks().size());
1042: if (size == 1) {
1043: JsBlock catchBlock = (JsBlock) pop(); // catchBlocks
1044: pop(); // catchArgs
1045: JsCatch jsCatch = catchMap.get(x.getCatchBlocks()
1046: .get(0));
1047: jsCatch.setBody(catchBlock);
1048: jsTry.getCatches().add(jsCatch);
1049: }
1050:
1051: jsTry.setTryBlock((JsBlock) pop()); // tryBlock
1052:
1053: push(jsTry);
1054: }
1055:
1056: @Override
1057: public void endVisit(JWhileStatement x, Context ctx) {
1058: JsWhile stmt = new JsWhile();
1059: if (x.getBody() != null) {
1060: stmt.setBody((JsStatement) pop()); // body
1061: } else {
1062: stmt.setBody(jsProgram.getEmptyStmt());
1063: }
1064: stmt.setCondition((JsExpression) pop()); // testExpr
1065: push(stmt);
1066: }
1067:
1068: @Override
1069: public boolean visit(JClassType x, Context ctx) {
1070: if (alreadyRan.contains(x)) {
1071: return false;
1072: }
1073:
1074: // force super type to generate code first, this is required for prototype
1075: // chaining to work properly
1076: if (x.extnds != null && !alreadyRan.contains(x)) {
1077: accept(x.extnds);
1078: }
1079:
1080: return true;
1081: }
1082:
1083: @Override
1084: public boolean visit(JMethod x, Context ctx) {
1085: if (x.isAbstract()) {
1086: return false;
1087: }
1088: currentMethod = x;
1089: return true;
1090: }
1091:
1092: @Override
1093: public boolean visit(JsniMethodBody x, Context ctx) {
1094: JsFunction jsFunc = x.getFunc();
1095:
1096: // replace all JSNI idents with a real JsName now that we know it
1097: new JsModVisitor() {
1098:
1099: @Override
1100: public void endVisit(JsNameRef x,
1101: JsContext<JsExpression> ctx) {
1102: String ident = x.getIdent();
1103: if (ident.charAt(0) == '@') {
1104: HasEnclosingType node = program.jsniMap
1105: .get(ident);
1106: assert (node != null);
1107: if (node instanceof JField) {
1108: JField field = (JField) node;
1109: JsName jsName = names.get(field);
1110: assert (jsName != null);
1111: x.resolve(jsName);
1112:
1113: // See if we need to add a clinit call to a static field ref
1114: JsInvocation clinitCall = maybeCreateClinitCall(field);
1115: if (clinitCall != null) {
1116: JsExpression commaExpr = createCommaExpression(
1117: clinitCall, x);
1118: ctx.replaceMe(commaExpr);
1119: }
1120: } else {
1121: JMethod method = (JMethod) node;
1122: if (x.getQualifier() == null) {
1123: JsName jsName = names.get(method);
1124: assert (jsName != null);
1125: x.resolve(jsName);
1126: } else {
1127: JsName jsName = polymorphicNames
1128: .get(method);
1129: if (jsName == null) {
1130: // this can occur when JSNI references an instance method on a
1131: // type that was never actually instantiated.
1132: jsName = nullMethodName;
1133: }
1134: x.resolve(jsName);
1135: }
1136: }
1137: }
1138: }
1139: }.accept(jsFunc);
1140:
1141: push(jsFunc);
1142:
1143: // Do NOT visit JsniMethodRefs/JsniFieldRefs.
1144: return false;
1145: }
1146:
1147: @Override
1148: public boolean visit(JSwitchStatement x, Context ctx) {
1149: /*
1150: * What a pain.. JSwitchStatement and JsSwitch are modeled completely
1151: * differently. Here we try to resolve those differences.
1152: */
1153: JsSwitch jsSwitch = new JsSwitch();
1154: accept(x.getExpr());
1155: jsSwitch.setExpr((JsExpression) pop()); // expr
1156:
1157: List<JStatement> bodyStmts = x.getBody().statements;
1158: if (bodyStmts.size() > 0) {
1159: List<JsStatement> curStatements = null;
1160: for (int i = 0; i < bodyStmts.size(); ++i) {
1161: JStatement stmt = bodyStmts.get(i);
1162: accept(stmt);
1163: if (stmt instanceof JCaseStatement) {
1164: // create a new switch member
1165: JsSwitchMember switchMember = (JsSwitchMember) pop(); // stmt
1166: jsSwitch.getCases().add(switchMember);
1167: curStatements = switchMember.getStmts();
1168: } else {
1169: // add to statements for current case
1170: assert (curStatements != null);
1171: JsStatement newStmt = (JsStatement) pop(); // stmt
1172: if (newStmt != null) {
1173: // Empty JLocalDeclarationStatement produces a null
1174: curStatements.add(newStmt);
1175: }
1176: }
1177: }
1178: }
1179:
1180: push(jsSwitch);
1181: return false;
1182: }
1183:
1184: private JsExpression createAssignment(JsExpression lhs,
1185: JsExpression rhs) {
1186: return new JsBinaryOperation(JsBinaryOperator.ASG, lhs, rhs);
1187: }
1188:
1189: private JsExpression createCommaExpression(JsExpression lhs,
1190: JsExpression rhs) {
1191: if (lhs == null) {
1192: return rhs;
1193: } else if (rhs == null) {
1194: return lhs;
1195: }
1196: return new JsBinaryOperation(JsBinaryOperator.COMMA, lhs,
1197: rhs);
1198: }
1199:
1200: private void generateClassLiterals(JsVars vars) {
1201: Set<JType> alreadyGenerated = new HashSet<JType>();
1202: for (Object element : classLits.keySet()) {
1203: JType type = (JType) element;
1204: generateClassLiteralsRecursive(alreadyGenerated, type,
1205: vars);
1206: }
1207: }
1208:
1209: private void generateClassLiteralsRecursive(
1210: Set<JType> alreadyGenerated, JType type, JsVars vars) {
1211: if (alreadyGenerated.contains(type)) {
1212: return;
1213: }
1214: alreadyGenerated.add(type);
1215:
1216: if (type instanceof JClassType
1217: && !(type instanceof JArrayType)) {
1218: /*
1219: * If this type is a regular class or an enum, then ensure that its
1220: * superclass's class literal is generated before its own.
1221: *
1222: * NOTE: JInterfaceTypes can have their JReferenceType.extnds member set
1223: * to its first implemented interface. JArrayTypes always have Object as
1224: * their superclass so there is no need to explicitly set it here.
1225: */
1226: JClassType classType = (JClassType) type;
1227: if (classType.extnds != null) {
1228: generateClassLiteralsRecursive(alreadyGenerated,
1229: classType.extnds, vars);
1230: }
1231: }
1232:
1233: JsName jsName = classLits.get(type);
1234: JsExpression classObjectAlloc = classObjects.get(jsName);
1235: JsVar var = new JsVar(jsName);
1236: var.setInitExpr(classObjectAlloc);
1237: vars.add(var);
1238: }
1239:
1240: private void generateClassSetup(JClassType x,
1241: List<JsStatement> globalStmts) {
1242: generateSeedFuncAndPrototype(x, globalStmts);
1243: generateVTables(x, globalStmts);
1244:
1245: if (x == program.getTypeJavaLangObject()) {
1246: // special: setup a "toString" alias for java.lang.Object.toString()
1247: generateToStringAlias(x, globalStmts);
1248: }
1249:
1250: generateTypeId(x, globalStmts);
1251: }
1252:
1253: private void generateGwtOnLoad(List<JsFunction> entryFuncs,
1254: List<JsStatement> globalStmts) {
1255: /**
1256: * <pre>
1257: * function gwtOnLoad(errFn, modName, modBase){
1258: * $moduleName = modName;
1259: * $moduleBase = modBase;
1260: * if (errFn) {
1261: * try {
1262: * init();
1263: * } catch(e) {
1264: * errFn(modName);
1265: * }
1266: * } else {
1267: * init();
1268: * }
1269: * }
1270: * </pre>
1271: */
1272: JsFunction gwtOnLoad = new JsFunction(topScope);
1273: globalStmts.add(gwtOnLoad.makeStmt());
1274: JsName gwtOnLoadName = topScope.declareName("gwtOnLoad");
1275: gwtOnLoadName.setObfuscatable(false);
1276: gwtOnLoad.setName(gwtOnLoadName);
1277: JsBlock body = new JsBlock();
1278: gwtOnLoad.setBody(body);
1279: JsScope fnScope = gwtOnLoad.getScope();
1280: List<JsParameter> params = gwtOnLoad.getParameters();
1281: JsName errFn = fnScope.declareName("errFn");
1282: JsName modName = fnScope.declareName("modName");
1283: JsName modBase = fnScope.declareName("modBase");
1284: params.add(new JsParameter(errFn));
1285: params.add(new JsParameter(modName));
1286: params.add(new JsParameter(modBase));
1287: JsExpression asg = createAssignment(topScope
1288: .findExistingUnobfuscatableName("$moduleName")
1289: .makeRef(), modName.makeRef());
1290: body.getStatements().add(asg.makeStmt());
1291: asg = createAssignment(topScope
1292: .findExistingUnobfuscatableName("$moduleBase")
1293: .makeRef(), modBase.makeRef());
1294: body.getStatements().add(asg.makeStmt());
1295: JsIf jsIf = new JsIf();
1296: body.getStatements().add(jsIf);
1297: jsIf.setIfExpr(errFn.makeRef());
1298: JsTry jsTry = new JsTry();
1299: jsIf.setThenStmt(jsTry);
1300: JsBlock callBlock = new JsBlock();
1301: jsIf.setElseStmt(callBlock);
1302: jsTry.setTryBlock(callBlock);
1303: for (int i = 0; i < entryFuncs.size(); ++i) {
1304: JsFunction func = entryFuncs.get(i);
1305: if (func != null) {
1306: JsInvocation call = new JsInvocation();
1307: call.setQualifier(func.getName().makeRef());
1308: callBlock.getStatements().add(call.makeStmt());
1309: }
1310: }
1311: JsCatch jsCatch = new JsCatch(fnScope, "e");
1312: jsTry.getCatches().add(jsCatch);
1313: JsBlock catchBlock = new JsBlock();
1314: jsCatch.setBody(catchBlock);
1315: JsInvocation errCall = new JsInvocation();
1316: catchBlock.getStatements().add(errCall.makeStmt());
1317: errCall.setQualifier(errFn.makeRef());
1318: errCall.getArguments().add(modName.makeRef());
1319: }
1320:
1321: private void generateNullFunc(List<JsStatement> globalStatements) {
1322: // handle null method
1323: JsFunction nullFunc = new JsFunction(topScope,
1324: nullMethodName);
1325: nullFunc.setBody(new JsBlock());
1326: globalStatements.add(nullFunc.makeStmt());
1327: }
1328:
1329: private void generateSeedFuncAndPrototype(JClassType x,
1330: List<JsStatement> globalStmts) {
1331: if (x != program.getTypeJavaLangString()) {
1332: JsName seedFuncName = names.get(x);
1333:
1334: // seed function
1335: // function com_example_foo_Foo() { }
1336: JsFunction seedFunc = new JsFunction(topScope,
1337: seedFuncName);
1338: JsBlock body = new JsBlock();
1339: seedFunc.setBody(body);
1340: globalStmts.add(seedFunc.makeStmt());
1341:
1342: // setup prototype, assign to temp
1343: // _ = com_example_foo_Foo.prototype = new com_example_foo_FooSuper();
1344: JsNameRef lhs = prototype.makeRef();
1345: lhs.setQualifier(seedFuncName.makeRef());
1346: JsExpression rhs;
1347: if (x.extnds != null) {
1348: JsNew newExpr = new JsNew();
1349: newExpr.setConstructorExpression(names
1350: .get(x.extnds).makeRef());
1351: rhs = newExpr;
1352: } else {
1353: rhs = new JsObjectLiteral();
1354: }
1355: JsExpression protoAsg = createAssignment(lhs, rhs);
1356: JsExpression tmpAsg = createAssignment(globalTemp
1357: .makeRef(), protoAsg);
1358: globalStmts.add(tmpAsg.makeStmt());
1359: } else {
1360: /*
1361: * MAGIC: java.lang.String is implemented as a JavaScript String
1362: * primitive with a modified prototype.
1363: */
1364: JsNameRef rhs = prototype.makeRef();
1365: rhs.setQualifier(jsProgram.getRootScope().declareName(
1366: "String").makeRef());
1367: JsExpression tmpAsg = createAssignment(globalTemp
1368: .makeRef(), rhs);
1369: globalStmts.add(tmpAsg.makeStmt());
1370: }
1371: }
1372:
1373: private void generateToStringAlias(JClassType x,
1374: List<JsStatement> globalStmts) {
1375: JMethod toStringMeth = program
1376: .getIndexedMethod("Object.toString");
1377: if (x.methods.contains(toStringMeth)) {
1378: // _.toString = function(){return this.java_lang_Object_toString();}
1379:
1380: // lhs
1381: JsName lhsName = objectScope.declareName("toString");
1382: lhsName.setObfuscatable(false);
1383: JsNameRef lhs = lhsName.makeRef();
1384: lhs.setQualifier(globalTemp.makeRef());
1385:
1386: // rhs
1387: JsInvocation call = new JsInvocation();
1388: JsNameRef toStringRef = new JsNameRef(polymorphicNames
1389: .get(toStringMeth));
1390: toStringRef.setQualifier(new JsThisRef());
1391: call.setQualifier(toStringRef);
1392: JsReturn jsReturn = new JsReturn(call);
1393: JsFunction rhs = new JsFunction(topScope);
1394: JsBlock body = new JsBlock();
1395: body.getStatements().add(jsReturn);
1396: rhs.setBody(body);
1397:
1398: // asg
1399: JsExpression asg = createAssignment(lhs, rhs);
1400: globalStmts.add(new JsExprStmt(asg));
1401: }
1402: }
1403:
1404: private void generateTypeId(JClassType x,
1405: List<JsStatement> globalStmts) {
1406: int typeId = program.getTypeId(x);
1407: if (typeId >= 0) {
1408: JField typeIdField = program
1409: .getIndexedField("Object.typeId");
1410: JsName typeIdName = names.get(typeIdField);
1411: if (typeIdName == null) {
1412: // Was pruned; this compilation must have no dynamic casts.
1413: return;
1414: }
1415: JsNameRef fieldRef = typeIdName.makeRef();
1416: fieldRef.setQualifier(globalTemp.makeRef());
1417: JsIntegralLiteral typeIdLit = jsProgram
1418: .getIntegralLiteral(BigInteger.valueOf(typeId));
1419: JsExpression asg = createAssignment(fieldRef, typeIdLit);
1420: globalStmts.add(new JsExprStmt(asg));
1421: }
1422: }
1423:
1424: private JsExpression generateTypeTable() {
1425: JsArrayLiteral arrayLit = new JsArrayLiteral();
1426: for (int i = 0; i < program.getJsonTypeTable().size(); ++i) {
1427: JsonObject jsonObject = program.getJsonTypeTable().get(
1428: i);
1429: accept(jsonObject);
1430: arrayLit.getExpressions().add((JsExpression) pop());
1431: }
1432: return arrayLit;
1433: }
1434:
1435: private void generateVTables(JClassType x,
1436: List<JsStatement> globalStmts) {
1437: for (int i = 0; i < x.methods.size(); ++i) {
1438: JMethod method = x.methods.get(i);
1439: if (!method.isStatic() && !method.isAbstract()) {
1440: JsNameRef lhs = polymorphicNames.get(method)
1441: .makeRef();
1442: lhs.setQualifier(globalTemp.makeRef());
1443: JsNameRef rhs = names.get(method).makeRef();
1444: JsExpression asg = createAssignment(lhs, rhs);
1445: globalStmts.add(new JsExprStmt(asg));
1446: }
1447: }
1448: }
1449:
1450: private void handleClinit(JsFunction clinitFunc,
1451: JsFunction super Clinit) {
1452: clinitFunc.setExecuteOnce(true);
1453: clinitFunc.setImpliedExecute(super Clinit);
1454: List<JsStatement> statements = clinitFunc.getBody()
1455: .getStatements();
1456: // self-assign to the null method immediately (to prevent reentrancy)
1457: JsExpression asg = createAssignment(clinitFunc.getName()
1458: .makeRef(), nullMethodName.makeRef());
1459: statements.add(0, asg.makeStmt());
1460: }
1461:
1462: private JsInvocation maybeCreateClinitCall(JField x) {
1463: if (!x.isStatic()) {
1464: return null;
1465: }
1466:
1467: JReferenceType enclosingType = x.getEnclosingType();
1468: if (!typeOracle.checkClinit(currentMethod
1469: .getEnclosingType(), enclosingType)) {
1470: return null;
1471: }
1472:
1473: JMethod clinitMethod = enclosingType.methods.get(0);
1474: JsInvocation jsInvocation = new JsInvocation();
1475: jsInvocation
1476: .setQualifier(names.get(clinitMethod).makeRef());
1477: return jsInvocation;
1478: }
1479:
1480: private JsInvocation maybeCreateClinitCall(JMethod x) {
1481: if (!crossClassTargets.contains(x)) {
1482: return null;
1483: }
1484: if (!x.isStatic() || program.isStaticImpl(x)) {
1485: return null;
1486: }
1487: JReferenceType enclosingType = x.getEnclosingType();
1488: if (!typeOracle.hasClinit(enclosingType)) {
1489: return null;
1490: }
1491: // avoid recursion sickness
1492: if (x == enclosingType.methods.get(0)) {
1493: return null;
1494: }
1495:
1496: JMethod clinitMethod = enclosingType.methods.get(0);
1497: JsInvocation jsInvocation = new JsInvocation();
1498: jsInvocation
1499: .setQualifier(names.get(clinitMethod).makeRef());
1500: return jsInvocation;
1501: }
1502: }
1503:
1504: private static class JavaToJsOperatorMap {
1505: private static final Map<JBinaryOperator, JsBinaryOperator> bOpMap = new IdentityHashMap<JBinaryOperator, JsBinaryOperator>();
1506: private static final Map<JUnaryOperator, JsUnaryOperator> uOpMap = new IdentityHashMap<JUnaryOperator, JsUnaryOperator>();
1507:
1508: static {
1509: bOpMap.put(JBinaryOperator.MUL, JsBinaryOperator.MUL);
1510: bOpMap.put(JBinaryOperator.DIV, JsBinaryOperator.DIV);
1511: bOpMap.put(JBinaryOperator.MOD, JsBinaryOperator.MOD);
1512: bOpMap.put(JBinaryOperator.ADD, JsBinaryOperator.ADD);
1513: bOpMap.put(JBinaryOperator.SUB, JsBinaryOperator.SUB);
1514: bOpMap.put(JBinaryOperator.SHL, JsBinaryOperator.SHL);
1515: bOpMap.put(JBinaryOperator.SHR, JsBinaryOperator.SHR);
1516: bOpMap.put(JBinaryOperator.SHRU, JsBinaryOperator.SHRU);
1517: bOpMap.put(JBinaryOperator.LT, JsBinaryOperator.LT);
1518: bOpMap.put(JBinaryOperator.LTE, JsBinaryOperator.LTE);
1519: bOpMap.put(JBinaryOperator.GT, JsBinaryOperator.GT);
1520: bOpMap.put(JBinaryOperator.GTE, JsBinaryOperator.GTE);
1521: bOpMap.put(JBinaryOperator.EQ, JsBinaryOperator.EQ);
1522: bOpMap.put(JBinaryOperator.NEQ, JsBinaryOperator.NEQ);
1523: bOpMap.put(JBinaryOperator.BIT_AND,
1524: JsBinaryOperator.BIT_AND);
1525: bOpMap.put(JBinaryOperator.BIT_XOR,
1526: JsBinaryOperator.BIT_XOR);
1527: bOpMap.put(JBinaryOperator.BIT_OR, JsBinaryOperator.BIT_OR);
1528: bOpMap.put(JBinaryOperator.AND, JsBinaryOperator.AND);
1529: bOpMap.put(JBinaryOperator.OR, JsBinaryOperator.OR);
1530: bOpMap.put(JBinaryOperator.ASG, JsBinaryOperator.ASG);
1531: bOpMap.put(JBinaryOperator.ASG_ADD,
1532: JsBinaryOperator.ASG_ADD);
1533: bOpMap.put(JBinaryOperator.ASG_SUB,
1534: JsBinaryOperator.ASG_SUB);
1535: bOpMap.put(JBinaryOperator.ASG_MUL,
1536: JsBinaryOperator.ASG_MUL);
1537: bOpMap.put(JBinaryOperator.ASG_DIV,
1538: JsBinaryOperator.ASG_DIV);
1539: bOpMap.put(JBinaryOperator.ASG_MOD,
1540: JsBinaryOperator.ASG_MOD);
1541: bOpMap.put(JBinaryOperator.ASG_SHL,
1542: JsBinaryOperator.ASG_SHL);
1543: bOpMap.put(JBinaryOperator.ASG_SHR,
1544: JsBinaryOperator.ASG_SHR);
1545: bOpMap.put(JBinaryOperator.ASG_SHRU,
1546: JsBinaryOperator.ASG_SHRU);
1547: bOpMap.put(JBinaryOperator.ASG_BIT_AND,
1548: JsBinaryOperator.ASG_BIT_AND);
1549: bOpMap.put(JBinaryOperator.ASG_BIT_OR,
1550: JsBinaryOperator.ASG_BIT_OR);
1551: bOpMap.put(JBinaryOperator.ASG_BIT_XOR,
1552: JsBinaryOperator.ASG_BIT_XOR);
1553:
1554: uOpMap.put(JUnaryOperator.INC, JsUnaryOperator.INC);
1555: uOpMap.put(JUnaryOperator.DEC, JsUnaryOperator.DEC);
1556: uOpMap.put(JUnaryOperator.NEG, JsUnaryOperator.NEG);
1557: uOpMap.put(JUnaryOperator.NOT, JsUnaryOperator.NOT);
1558: uOpMap.put(JUnaryOperator.BIT_NOT, JsUnaryOperator.BIT_NOT);
1559: }
1560:
1561: public static JsBinaryOperator get(JBinaryOperator op) {
1562: return bOpMap.get(op);
1563: }
1564:
1565: public static JsUnaryOperator get(JUnaryOperator op) {
1566: return uOpMap.get(op);
1567: }
1568: }
1569:
1570: private class RecordCrossClassCalls extends JVisitor {
1571:
1572: private JMethod currentMethod;
1573:
1574: @Override
1575: public void endVisit(JMethod x, Context ctx) {
1576: currentMethod = null;
1577: }
1578:
1579: @Override
1580: public void endVisit(JMethodCall x, Context ctx) {
1581: JReferenceType sourceType = currentMethod
1582: .getEnclosingType();
1583: JReferenceType targetType = x.getTarget()
1584: .getEnclosingType();
1585: if (typeOracle.checkClinit(sourceType, targetType)) {
1586: crossClassTargets.add(x.getTarget());
1587: }
1588: }
1589:
1590: @Override
1591: public void endVisit(JsniMethodRef x, Context ctx) {
1592: endVisit((JMethodCall) x, ctx);
1593: }
1594:
1595: @Override
1596: public boolean visit(JMethod x, Context ctx) {
1597: currentMethod = x;
1598: return true;
1599: }
1600: }
1601:
1602: private class SortVisitor extends JVisitor {
1603:
1604: private final HasNameSort hasNameSort = new HasNameSort();
1605:
1606: @Override
1607: public void endVisit(JClassType x, Context ctx) {
1608: Collections.sort(x.fields, hasNameSort);
1609:
1610: // Sort the methods manually to avoid sorting clinit out of place!
1611: List<JMethod> methods = x.methods;
1612: JMethod a[] = methods.toArray(new JMethod[methods.size()]);
1613: Arrays.sort(a, 1, a.length, hasNameSort);
1614: for (int i = 1; i < a.length; i++) {
1615: methods.set(i, a[i]);
1616: }
1617: }
1618:
1619: @Override
1620: public void endVisit(JInterfaceType x, Context ctx) {
1621: Collections.sort(x.fields, hasNameSort);
1622: Collections.sort(x.methods, hasNameSort);
1623: }
1624:
1625: @Override
1626: public void endVisit(JMethodBody x, Context ctx) {
1627: Collections.sort(x.locals, hasNameSort);
1628: }
1629:
1630: @Override
1631: public void endVisit(JProgram x, Context ctx) {
1632: Collections.sort(x.entryMethods, hasNameSort);
1633: Collections.sort(x.getDeclaredTypes(), hasNameSort);
1634: }
1635: }
1636:
1637: public static void exec(JProgram program, JsProgram jsProgram,
1638: JsOutputOption output) {
1639: GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(
1640: program, jsProgram, output);
1641: generateJavaScriptAST.execImpl();
1642: }
1643:
1644: private final Map<JBlock, JsCatch> catchMap = new IdentityHashMap<JBlock, JsCatch>();
1645:
1646: /**
1647: * Sorted to avoid nondeterministic iteration.
1648: */
1649: private final Map<JType, JsName> classLits = new TreeMap<JType, JsName>(
1650: new HasNameSort());
1651:
1652: private final Map<JsName, JsExpression> classObjects = new IdentityHashMap<JsName, JsExpression>();
1653: private final Map<JClassType, JsScope> classScopes = new IdentityHashMap<JClassType, JsScope>();
1654:
1655: /**
1656: * A list of methods that are called from another class (ie might need to
1657: * clinit).
1658: */
1659: private Set<JMethod> crossClassTargets = new HashSet<JMethod>();
1660:
1661: /**
1662: * Contains JsNames for all interface methods. A special scope is needed so
1663: * that independent classes will obfuscate their interface implementation
1664: * methods the same way.
1665: */
1666: private final JsScope interfaceScope;
1667:
1668: private final JsProgram jsProgram;
1669: private final Map<JAbstractMethodBody, JsFunction> methodBodyMap = new IdentityHashMap<JAbstractMethodBody, JsFunction>();
1670: private final Map<HasName, JsName> names = new IdentityHashMap<HasName, JsName>();
1671: private JsName nullMethodName;
1672:
1673: /**
1674: * Contains JsNames for the Object instance methods, such as equals, hashCode,
1675: * and toString. All other class scopes have this scope as an ultimate parent.
1676: */
1677: private final JsScope objectScope;
1678: private final Map<JMethod, JsName> polymorphicNames = new IdentityHashMap<JMethod, JsName>();
1679: private final JProgram program;
1680:
1681: /**
1682: * All of the fields and polymorphic methods in String.
1683: *
1684: * Because we modify String's prototype, all fields and polymorphic methods on
1685: * String's super types need special handling.
1686: */
1687: private final Map<String, String> specialObfuscatedIdents = new HashMap<String, String>();
1688:
1689: /**
1690: * All of the super types of String.
1691: *
1692: * Because we modify String's prototype, all fields and polymorphic methods on
1693: * String's super types need special handling.
1694: */
1695: private final Set<JReferenceType> specialObfuscatedTypes = new HashSet<JReferenceType>();
1696:
1697: /**
1698: * Contains JsNames for all globals, such as static fields and methods.
1699: */
1700: private final JsScope topScope;
1701:
1702: private final JTypeOracle typeOracle;
1703: private final JsOutputOption output;
1704:
1705: private GenerateJavaScriptAST(JProgram program,
1706: JsProgram jsProgram, JsOutputOption output) {
1707: this .program = program;
1708: typeOracle = program.typeOracle;
1709: this .jsProgram = jsProgram;
1710: topScope = jsProgram.getScope();
1711: objectScope = jsProgram.getObjectScope();
1712: interfaceScope = new JsScope(objectScope, "Interfaces");
1713: this .output = output;
1714:
1715: /*
1716: * Because we modify String's prototype, all fields and polymorphic methods
1717: * on String's super types need special handling.
1718: */
1719: specialObfuscatedTypes
1720: .add(program.getIndexedType("Comparable"));
1721: specialObfuscatedTypes.add(program
1722: .getIndexedType("CharSequence"));
1723: specialObfuscatedTypes.add(program.getTypeJavaLangObject());
1724: specialObfuscatedTypes.add(program.getTypeJavaLangString());
1725: specialObfuscatedTypes.add(program.getIndexedType("Array"));
1726:
1727: // Object polymorphic
1728: specialObfuscatedIdents.put("getClass", "gC");
1729: specialObfuscatedIdents.put("hashCode", "hC");
1730: specialObfuscatedIdents.put("equals", "eQ");
1731: specialObfuscatedIdents.put("toString", "tS");
1732: specialObfuscatedIdents.put("finalize", "fZ");
1733:
1734: // Object fields
1735: specialObfuscatedIdents.put("typeId", "tI");
1736:
1737: // String polymorphic
1738: specialObfuscatedIdents.put("charAt", "cA");
1739: specialObfuscatedIdents.put("compareTo", "cT");
1740: specialObfuscatedIdents.put("length", "lN");
1741: specialObfuscatedIdents.put("subSequence", "sS");
1742:
1743: // Array magic field
1744: specialObfuscatedIdents.put("arrayClass", "aC");
1745: specialObfuscatedIdents.put("queryId", "qI");
1746: }
1747:
1748: boolean belongsToSpecialObfuscatedType(JField x) {
1749: return specialObfuscatedTypes.contains(x.getEnclosingType());
1750: }
1751:
1752: boolean belongsToSpecialObfuscatedType(JMethod x) {
1753: if (specialObfuscatedTypes.contains(x.getEnclosingType())) {
1754: return true;
1755: }
1756: for (Object element : x.overrides) {
1757: JMethod override = (JMethod) element;
1758: if (specialObfuscatedTypes.contains(override
1759: .getEnclosingType())) {
1760: return true;
1761: }
1762: }
1763: return false;
1764: }
1765:
1766: String getNameString(HasName hasName) {
1767: String s = hasName.getName().replaceAll("_", "_1").replace('.',
1768: '_');
1769: return s;
1770: }
1771:
1772: String mangleName(JField x) {
1773: String s = getNameString(x.getEnclosingType()) + '_'
1774: + getNameString(x);
1775: return s;
1776: }
1777:
1778: String mangleNameForGlobal(JMethod x) {
1779: String s = getNameString(x.getEnclosingType()) + '_'
1780: + getNameString(x) + "__";
1781: for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
1782: JType type = x.getOriginalParamTypes().get(i);
1783: s += type.getJavahSignatureName();
1784: }
1785: return s;
1786: }
1787:
1788: String mangleNameForPoly(JMethod x) {
1789: if (x.overrides.isEmpty()) {
1790: return mangleNameForPolyImpl(x);
1791: } else {
1792: for (JMethod override : x.overrides) {
1793: if (override.overrides.isEmpty()) {
1794: return mangleNameForPolyImpl(override);
1795: }
1796: }
1797: }
1798: throw new InternalCompilerException("Cycle in overrides???");
1799: }
1800:
1801: String mangleNameForPolyImpl(JMethod x) {
1802: String s = getNameString(x) + "__";
1803: for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
1804: JType type = x.getOriginalParamTypes().get(i);
1805: s += type.getJavahSignatureName();
1806: }
1807: return s;
1808: }
1809:
1810: String mangleNameSpecialObfuscate(JField x) {
1811: assert (specialObfuscatedIdents.containsKey(x.getName()));
1812: switch (output) {
1813: case OBFUSCATED:
1814: return specialObfuscatedIdents.get(x.getName());
1815: case PRETTY:
1816: return x.getName() + "$";
1817: case DETAILED:
1818: return mangleName(x) + "$";
1819: }
1820: throw new InternalCompilerException("Unknown output mode");
1821: }
1822:
1823: String mangleNameSpecialObfuscate(JMethod x) {
1824: assert (specialObfuscatedIdents.containsKey(x.getName()));
1825: switch (output) {
1826: case OBFUSCATED:
1827: return specialObfuscatedIdents.get(x.getName());
1828: case PRETTY:
1829: return x.getName() + "$";
1830: case DETAILED:
1831: return mangleNameForPoly(x) + "$";
1832: }
1833: throw new InternalCompilerException("Unknown output mode");
1834: }
1835:
1836: private void execImpl() {
1837: SortVisitor sorter = new SortVisitor();
1838: sorter.accept(program);
1839: RecordCrossClassCalls recorder = new RecordCrossClassCalls();
1840: recorder.accept(program);
1841: CreateNamesAndScopesVisitor creator = new CreateNamesAndScopesVisitor();
1842: creator.accept(program);
1843: GenerateJavaScriptVisitor generator = new GenerateJavaScriptVisitor();
1844: generator.accept(program);
1845: }
1846:
1847: }
|