/* * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html. * * This file is a derivative of code released by the University of * California under the terms listed below. * * WALA JDT Frontend is Copyright (c) 2008 The Regents of the * University of California (Regents). Provided that this notice and * the following two paragraphs are included in any distribution of * Refinement Analysis Tools or its derivative work, Regents agrees * not to assert any of Regents' copyright rights in Refinement * Analysis Tools against recipient for recipient's reproduction, * preparation of derivative works, public display, public * performance, distribution or sublicensing of Refinement Analysis * Tools and derivative works, in source code and object code form. * This agreement not to assert does not confer, by implication, * estoppel, or otherwise any license or rights in any intellectual * property of Regents, including, but not limited to, any patents * of Regents or Regents' employees. * * IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE * AND ITS DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE AND FURTHER DISCLAIMS ANY STATUTORY * WARRANTY OF NON-INFRINGEMENT. THE SOFTWARE AND ACCOMPANYING * DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS * IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, * UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ package com.ibm.wala.cast.java.translator.jdt; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ArrayAccess; import org.eclipse.jdt.core.dom.ArrayCreation; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.AssertStatement; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.BreakStatement; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.CatchClause; import org.eclipse.jdt.core.dom.CharacterLiteral; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ConditionalExpression; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.ContinueStatement; import org.eclipse.jdt.core.dom.DoStatement; import org.eclipse.jdt.core.dom.EmptyStatement; import org.eclipse.jdt.core.dom.EnhancedForStatement; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMemberValuePairBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.Initializer; import org.eclipse.jdt.core.dom.InstanceofExpression; import org.eclipse.jdt.core.dom.LabeledStatement; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NullLiteral; import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.PackageDeclaration; import org.eclipse.jdt.core.dom.ParenthesizedExpression; import org.eclipse.jdt.core.dom.PostfixExpression; import org.eclipse.jdt.core.dom.PrefixExpression; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.SuperFieldAccess; import org.eclipse.jdt.core.dom.SuperMethodInvocation; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.SwitchStatement; import org.eclipse.jdt.core.dom.SynchronizedStatement; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.dom.ThrowStatement; import org.eclipse.jdt.core.dom.TryStatement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeDeclarationStatement; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.VariableDeclarationExpression; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.WhileStatement; import com.ibm.wala.analysis.typeInference.JavaPrimitiveType; import com.ibm.wala.cast.ir.translator.AstTranslator.InternalCAstSymbol; import com.ibm.wala.cast.ir.translator.TranslatorToCAst; import com.ibm.wala.cast.ir.translator.TranslatorToCAst.DoLoopTranslator; import com.ibm.wala.cast.java.loader.JavaSourceLoaderImpl; import com.ibm.wala.cast.java.loader.Util; import com.ibm.wala.cast.java.translator.JavaProcedureEntity; import com.ibm.wala.cast.tree.CAst; import com.ibm.wala.cast.tree.CAstAnnotation; import com.ibm.wala.cast.tree.CAstControlFlowMap; import com.ibm.wala.cast.tree.CAstEntity; import com.ibm.wala.cast.tree.CAstNode; import com.ibm.wala.cast.tree.CAstNodeTypeMap; import com.ibm.wala.cast.tree.CAstQualifier; import com.ibm.wala.cast.tree.CAstSourcePositionMap; import com.ibm.wala.cast.tree.CAstType; import com.ibm.wala.cast.tree.impl.CAstControlFlowRecorder; import com.ibm.wala.cast.tree.impl.CAstImpl; import com.ibm.wala.cast.tree.impl.CAstNodeTypeMapRecorder; import com.ibm.wala.cast.tree.impl.CAstOperator; import com.ibm.wala.cast.tree.impl.CAstSourcePositionRecorder; import com.ibm.wala.cast.tree.impl.CAstSymbolImpl; import com.ibm.wala.cast.util.CAstPrinter; import com.ibm.wala.classLoader.CallSiteReference; import com.ibm.wala.ide.util.JdtPosition; import com.ibm.wala.shrikeBT.IInvokeInstruction; import com.ibm.wala.types.FieldReference; import com.ibm.wala.types.MethodReference; import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.collections.EmptyIterator; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.collections.Pair; import com.ibm.wala.util.debug.Assertions; // TOTEST: // "1/0" surrounded by catch ArithmeticException & RunTimeException (TryCatchContext.getCatchTypes" // another subtype of ArithmeticException surrounded by catch ArithmeticException // binary ops with all kinds of type conversions // simplenames: fields of this, fields of an enclosing class, fields of an enclosing method, static fields, fully qualified fields w/ package stuff // exceptional CFG edges, somehow. call nodes, new nodes, division by zero in binary ops, null pointer in field accesses, etc. // implicit constructors // FIXME 1.4: thing about / ask agout TAGALONG (JDT stuff tagging along in memory cos we keep it). // FIXME 1.4: find LEFTOUT and find out why polyglot has extra code / infrastructure, if it's used and what for, etc. // Java 1.6: // * type parameters/generics: see getArgumentTypes in ProcedureEntity$anon. also anywhere we use ITypeBinding.equals() may be affected // see anywhere in code labeled GENERICS for present "treat as raw types"/"pretend it's 1.4 and casts" solution. // * boxing (YUCK). see resolveBoxing() // * enums (probably in simplename or something. but using resolveConstantExpressionValue() possible) @SuppressWarnings("unchecked") public class JDTJava2CAstTranslator { protected boolean dump = false; protected final CAst fFactory = new CAstImpl(); // /////////////////////////////////////////// // / HANDLINGS OF VARIOUS THINGS // // /////////////////////////////////////////// protected final AST ast; // TAGALONG protected final JDTIdentityMapper fIdentityMapper; // TAGALONG protected final JDTTypeDictionary fTypeDict; protected final JavaSourceLoaderImpl fSourceLoader; protected final ITypeBinding fDivByZeroExcType; protected final ITypeBinding fNullPointerExcType; protected final ITypeBinding fClassCastExcType; protected final ITypeBinding fRuntimeExcType; protected final ITypeBinding NoClassDefFoundError; protected final ITypeBinding ExceptionInInitializerError; protected final ITypeBinding OutOfMemoryError; protected final DoLoopTranslator doLoopTranslator; private final String fullPath; private final IFile sourceFile; private final CompilationUnit cu; // // COMPILATION UNITS & TYPES // public JDTJava2CAstTranslator(JavaSourceLoaderImpl sourceLoader, CompilationUnit astRoot, IFile sourceFile, String fullPath, boolean replicateForDoLoops) { this(sourceLoader, astRoot, sourceFile, fullPath, replicateForDoLoops, false); } public JDTJava2CAstTranslator(JavaSourceLoaderImpl sourceLoader, CompilationUnit astRoot, IFile sourceFile, String fullPath, boolean replicateForDoLoops, boolean dump) { fDivByZeroExcType = FakeExceptionTypeBinding.arithmetic; fNullPointerExcType = FakeExceptionTypeBinding.nullPointer; fClassCastExcType = FakeExceptionTypeBinding.classCast; NoClassDefFoundError = FakeExceptionTypeBinding.noClassDef; ExceptionInInitializerError = FakeExceptionTypeBinding.initException; OutOfMemoryError = FakeExceptionTypeBinding.outOfMemory; this.sourceFile = sourceFile; this.fSourceLoader = sourceLoader; this.cu = astRoot; this.fullPath = fullPath; ast = cu.getAST(); this.doLoopTranslator = new DoLoopTranslator(replicateForDoLoops, fFactory); this.dump = dump; // FIXME: we might need one AST (-> "Object" class) for all files. fIdentityMapper = new JDTIdentityMapper(fSourceLoader.getReference(), ast); fTypeDict = new JDTTypeDictionary(ast, fIdentityMapper); fRuntimeExcType = ast.resolveWellKnownType("java.lang.RuntimeException"); assert fRuntimeExcType != null; } public CAstEntity translateToCAst() { List<CAstEntity> declEntities = new ArrayList<CAstEntity>(); for (Iterator<CAstEntity> iter = cu.types().iterator(); iter.hasNext();) { AbstractTypeDeclaration decl = (AbstractTypeDeclaration) iter.next(); // can be of type AnnotationTypeDeclaration, EnumDeclaration, TypeDeclaration declEntities.add(visit(decl, new RootContext())); } if (dump) { for(CAstEntity d : declEntities) { CAstPrinter.printTo(d, new PrintWriter(System.err)); } } return new CompilationUnitEntity(cu.getPackage(), declEntities); } // // TYPES // protected final class ClassEntity implements CAstEntity { // TAGALONG (not static, will keep reference to ast, fIdentityMapper, etc) private final String fName; private final Collection<CAstQualifier> fQuals; private final Collection<CAstEntity> fEntities; private final ITypeBinding fJdtType; // TAGALONG private final JdtPosition fSourcePosition; private final Set<CAstAnnotation> annotations; public ClassEntity(ITypeBinding jdtType, String name, Collection<CAstQualifier> quals, Collection<CAstEntity> entities, JdtPosition pos, Set<CAstAnnotation> annotations) { fName = name; fQuals = quals; fEntities = entities; fJdtType = jdtType; fSourcePosition = pos; this.annotations = annotations; } @Override public Collection<CAstAnnotation> getAnnotations() { // TODO Auto-generated method stub return null; } @Override public int getKind() { return TYPE_ENTITY; } @Override public String getName() { return fName; // unqualified? } @Override public String getSignature() { return "L" + fName.replace('.', '/') + ";"; } @Override public String[] getArgumentNames() { return new String[0]; } @Override public CAstNode[] getArgumentDefaults() { return new CAstNode[0]; } @Override public int getArgumentCount() { return 0; } @Override public CAstNode getAST() { // This entity has no AST nodes, really. return null; } @Override public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() { return Collections.singletonMap(null, fEntities); } @Override public Iterator getScopedEntities(CAstNode construct) { Assertions.UNREACHABLE("Non-AST-bearing entity (ClassEntity) asked for scoped entities related to a given AST node"); return null; } @Override public CAstControlFlowMap getControlFlow() { // This entity has no AST nodes, really. return null; } @Override public CAstSourcePositionMap getSourceMap() { // This entity has no AST nodes, really. return null; } @Override public CAstSourcePositionMap.Position getPosition() { return fSourcePosition; } @Override public CAstNodeTypeMap getNodeTypeMap() { // This entity has no AST nodes, really. return new CAstNodeTypeMap() { @Override public CAstType getNodeType(CAstNode node) { throw new UnsupportedOperationException(); } @Override public Collection<CAstNode> getMappedNodes() { throw new UnsupportedOperationException(); } }; } @Override public Collection getQualifiers() { return fQuals; } @Override public CAstType getType() { // return new JdtJavaType(fCT, getTypeDict(), fTypeSystem); return fTypeDict.new JdtJavaType(fJdtType); } } private boolean isInterface(AbstractTypeDeclaration decl) { return decl instanceof AnnotationTypeDeclaration || (decl instanceof TypeDeclaration && ((TypeDeclaration)decl).isInterface()); } private CAstEntity visitTypeDecl(AbstractTypeDeclaration n, WalkContext context) { return createClassDeclaration(n, n.bodyDeclarations(), null, n.resolveBinding(), n.getName().getIdentifier(), n.getModifiers(), isInterface(n), n instanceof AnnotationTypeDeclaration, context); } /** * * @param n * @param bodyDecls * @param enumConstants * @param typeBinding * @param name Used in creating default constructor, and passed into new ClassEntity() * @param context * @return */ private CAstEntity createClassDeclaration(ASTNode n, List/* <BodyDeclaration> */bodyDecls, List/* EnumConstantDeclaration */enumConstants, ITypeBinding typeBinding, String name, int modifiers, boolean isInterface, boolean isAnnotation, WalkContext context) { final List<CAstEntity> memberEntities = new ArrayList<CAstEntity>(); // find and collect all initializers (type Initializer) and field initializers (type VariableDeclarationFragment). // instance initializer code will be inserted into each constructors. // all static initializer code will be grouped together in its own entity. ArrayList<ASTNode> inits = new ArrayList<ASTNode>(); ArrayList<ASTNode> staticInits = new ArrayList<ASTNode>(); if (enumConstants != null) { for (Object decl : enumConstants) { EnumConstantDeclaration ecd = (EnumConstantDeclaration) decl; staticInits.add(ecd); // always (implicitly) static,final (actually, no modifiers allowed) } } for (Object decl : bodyDecls) { if (decl instanceof Initializer) { Initializer initializer = (Initializer) decl; boolean isStatic = ((initializer.getModifiers() & Modifier.STATIC) != 0); (isStatic ? staticInits : inits).add(initializer); } else if (decl instanceof FieldDeclaration) { FieldDeclaration fd = (FieldDeclaration) decl; for (Object f : fd.fragments()) { VariableDeclarationFragment frag = (VariableDeclarationFragment) f; if (frag.getInitializer() != null) { boolean isStatic = ((fd.getModifiers() & Modifier.STATIC) != 0); (isStatic ? staticInits : inits).add(frag); } } } } // process entities. initializers will be folded in here. if (enumConstants != null) { for (Object decl : enumConstants) { memberEntities.add(visit((EnumConstantDeclaration) decl, context)); } } for (Object d : bodyDecls) { BodyDeclaration decl = (BodyDeclaration) d; if (decl instanceof FieldDeclaration) { FieldDeclaration fieldDecl = (FieldDeclaration) decl; Collection<CAstQualifier> quals = JDT2CAstUtils.mapModifiersToQualifiers(fieldDecl.getModifiers(), false, false); for (Object f : fieldDecl.fragments()) { VariableDeclarationFragment fieldFrag = (VariableDeclarationFragment) f; IVariableBinding fieldBinding = fieldFrag.resolveBinding(); memberEntities.add(new FieldEntity(fieldFrag.getName().getIdentifier(), fieldBinding.getType(), quals, makePosition(fieldFrag.getStartPosition(), fieldFrag.getStartPosition() + fieldFrag.getLength()), handleAnnotations(fieldBinding))); } } else if (decl instanceof Initializer) { // Initializers are inserted into constructors when making constructors. } else if (decl instanceof MethodDeclaration) { MethodDeclaration metDecl = (MethodDeclaration) decl; if (typeBinding.isEnum() && metDecl.isConstructor()) memberEntities.add(createEnumConstructorWithParameters(metDecl.resolveBinding(), metDecl, context, inits, metDecl)); else { memberEntities.add(visit(metDecl, typeBinding, context, inits)); // /////////////// Java 1.5 "overridden with subtype" thing (covariant return type) /////////// Collection<IMethodBinding> overriddenMets = JDT2CAstUtils.getOverriddenMethod(metDecl.resolveBinding()); if (overriddenMets != null) { for (IMethodBinding overridden : overriddenMets) if (!JDT2CAstUtils.sameErasedSignatureAndReturnType(metDecl.resolveBinding(), overridden)) memberEntities.add(makeSyntheticCovariantRedirect(metDecl, metDecl.resolveBinding(), overridden, context)); } } } else if (decl instanceof AbstractTypeDeclaration) { memberEntities.add(visit((AbstractTypeDeclaration) decl, context)); } else if (decl instanceof AnnotationTypeMemberDeclaration) { // TODO: need to decide what to do with these } else { Assertions.UNREACHABLE("BodyDeclaration not Field, Initializer, or Method"); } } // add default constructor(s) if necessary // most default constructors have no parameters; however, those created by anonymous classes will have parameters // (they just call super with those parameters) for (Object m : typeBinding.getDeclaredMethods()) { IMethodBinding met = (IMethodBinding) m; if (met.isDefaultConstructor()) { if (typeBinding.isEnum()) memberEntities.add(createEnumConstructorWithParameters(met, n, context, inits, null)); else if (met.getParameterTypes().length > 0) memberEntities.add(createDefaultConstructorWithParameters(met, n, context, inits)); else memberEntities.add(createDefaultConstructor(name, typeBinding, context, inits, n)); } } if (typeBinding.isEnum() && !typeBinding.isAnonymous()) doEnumHiddenEntities(typeBinding, staticInits, memberEntities, context); // collect static inits if (!staticInits.isEmpty()) { Map<CAstNode, CAstEntity> childEntities = HashMapFactory.make(); final MethodContext newContext = new MethodContext(context, childEntities); // childEntities is the same one as in the ProcedureEntity. later visit(New), etc. may add to this. CAstNode[] bodyNodes = new CAstNode[staticInits.size()]; for (int i = 0; i < staticInits.size(); i++) bodyNodes[i] = visitFieldInitNode(staticInits.get(i), newContext); CAstNode staticInitAst = makeNode(newContext, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes); memberEntities.add(new ProcedureEntity(staticInitAst, typeBinding, childEntities, newContext, null)); } Collection<CAstQualifier> quals = JDT2CAstUtils.mapModifiersToQualifiers(modifiers, isInterface, isAnnotation); Set<CAstAnnotation> annotations = handleAnnotations(typeBinding); return new ClassEntity(typeBinding, name, quals, memberEntities, makePosition(n), annotations); } private CAstEntity visit(AnonymousClassDeclaration n, WalkContext context) { return createClassDeclaration(n, n.bodyDeclarations(), null, n.resolveBinding(), JDT2CAstUtils.anonTypeName(n.resolveBinding()), 0 /* no modifiers */, false, false, context); } private CAstNode visit(TypeDeclarationStatement n, WalkContext context) { // TODO 1.6: enums of course... AbstractTypeDeclaration decl = n.getDeclaration(); assert decl instanceof TypeDeclaration : "Local enum declaration not yet supported"; CAstEntity classEntity = visitTypeDecl((TypeDeclaration) decl, context); // these statements doin't actually do anything, just define a type final CAstNode lcdNode = makeNode(context, fFactory, n, CAstNode.EMPTY); // so define it! context.addScopedEntity(lcdNode, classEntity); return lcdNode; } // //////////////////////////////// // METHODS // //////////////////////////////// /** * @param n for positioning. * * Make a constructor with parameters that calls super(...) with parameters. Used for anonymous classes with arguments to a * constructor, like new Foo(arg1,arg2) { } */ private CAstEntity createDefaultConstructorWithParameters(IMethodBinding ctor, ASTNode n, WalkContext oldContext, ArrayList<ASTNode> inits) { // PART I: find super ctor to call ITypeBinding newType = ctor.getDeclaringClass(); ITypeBinding superType = newType.getSuperclass(); IMethodBinding superCtor = null; for (IMethodBinding m : superType.getDeclaredMethods()) if (m.isConstructor() && Arrays.equals(m.getParameterTypes(), ctor.getParameterTypes())) superCtor = m; assert superCtor != null : "couldn't find constructor for anonymous class"; // PART II: make ctor with simply "super(a,b,c...)" final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>(); final MethodContext context = new MethodContext(oldContext, memberEntities); MethodDeclaration fakeCtor = ast.newMethodDeclaration(); fakeCtor.setConstructor(true); fakeCtor.setSourceRange(n.getStartPosition(), n.getLength()); fakeCtor.setBody(ast.newBlock()); // PART IIa: make a fake JDT constructor method with the proper number of args // Make fake args that will be passed String[] fakeArguments = new String[superCtor.getParameterTypes().length + 1]; ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(superCtor.getParameterTypes().length); for (int i = 0; i < fakeArguments.length; i++) fakeArguments[i] = (i == 0) ? "this" : ("argument" + i); // TODO: change to invalid name and don't use // singlevariabledeclaration below for (int i = 1; i < fakeArguments.length; i++) { // the name SingleVariableDeclaration svd = ast.newSingleVariableDeclaration(); svd.setName(ast.newSimpleName(fakeArguments[i])); fakeCtor.parameters().add(svd); // the type paramTypes.add(fTypeDict.getCAstTypeFor(ctor.getParameterTypes()[i - 1])); } // PART IIb: create the statements in the constructor // one super() call plus the inits CAstNode[] bodyNodes = new CAstNode[inits.size() + 1]; // make super(...) call // this, call ref, args CAstNode[] children = new CAstNode[fakeArguments.length + 1]; children[0] = makeNode(context, fFactory, n, CAstNode.SUPER); CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(superCtor), IInvokeInstruction.Dispatch.SPECIAL); children[1] = fFactory.makeConstant(callSiteRef); for (int i = 1; i < fakeArguments.length; i++) { CAstNode argName = fFactory.makeConstant(fakeArguments[i]); CAstNode argType = fFactory.makeConstant(paramTypes.get(i-1)); children[i + 1] = makeNode(context, fFactory, n, CAstNode.VAR, argName, argType); } bodyNodes[0] = makeNode(context, fFactory, n, CAstNode.CALL, children); // QUESTION: no handleExceptions? for (int i = 0; i < inits.size(); i++) bodyNodes[i + 1] = visitFieldInitNode(inits.get(i), context); // finally, make the procedure entity CAstNode ast = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes); return new ProcedureEntity(ast, fakeCtor, newType, memberEntities, context, paramTypes, null, null); } private CAstEntity createDefaultConstructor(String className, ITypeBinding classBinding, WalkContext oldContext, ArrayList<ASTNode> inits, ASTNode positioningNode) { MethodDeclaration fakeCtor = ast.newMethodDeclaration(); fakeCtor.setConstructor(true); // fakeCtor.setName(ast.newSimpleName(className)); will crash on anonymous types... fakeCtor.setSourceRange(positioningNode.getStartPosition(), positioningNode.getLength()); fakeCtor.setBody(ast.newBlock()); return visit(fakeCtor, classBinding, oldContext, inits); } private IMethodBinding findDefaultCtor(ITypeBinding superClass) { for (IMethodBinding met : superClass.getDeclaredMethods()) { if (met.isConstructor() && met.getParameterTypes().length == 0) return met; } Assertions.UNREACHABLE("Couldn't find default ctor"); return null; } /** * Setup constructor body. Here we add the initializer code (both initalizer blocks and initializers in field declarations). We * may also need to add an implicit super() call. * * @param n * @param classBinding Used so we can use this with fake MethodDeclaration nodes, as in the case of creating a default * constructor. * @param context * @param inits * @return */ private CAstNode createConstructorBody(MethodDeclaration n, ITypeBinding classBinding, WalkContext context, ArrayList<ASTNode> inits) { // three possibilites: has super(), has this(), has neither. Statement firstStatement = null; if (!n.getBody().statements().isEmpty()) firstStatement = (Statement) n.getBody().statements().get(0); if (firstStatement instanceof SuperConstructorInvocation) { // Split at call to super: // super(); // field initializer code // remainder of ctor body ArrayList<CAstNode> origStatements = createBlock(n.getBody(), context); CAstNode[] bodyNodes = new CAstNode[inits.size() + origStatements.size()]; int idx = 0; bodyNodes[idx++] = origStatements.get(0); for (ASTNode init : inits) bodyNodes[idx++] = visitFieldInitNode(init, context); // visit each in this constructor's context, ensuring // proper handling of exceptions (we can't just reuse the // CAstNodes) for (int i = 1; i < origStatements.size(); i++) bodyNodes[idx++] = origStatements.get(i); return makeNode(context, fFactory, n.getBody(), CAstNode.BLOCK_STMT, bodyNodes); // QUESTION: why no LOCAL_SCOPE? // that's the way it is in // polyglot. } else if (firstStatement instanceof ConstructorInvocation) { return visitNode(n.getBody(), context); // has this(...) call; initializers will be set somewhere else. } else { // add explicit call to default super() // QUESTION their todo: following superClass lookup of default ctor won't work if we // process Object in source... ITypeBinding superType = classBinding.getSuperclass(); // find default constructor. IT is an error to have a constructor // without super() when the default constructor of the superclass does not exist. IMethodBinding defaultSuperCtor = findDefaultCtor(superType); CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(defaultSuperCtor), IInvokeInstruction.Dispatch.SPECIAL); // QUESTION: why isn't first arg this like in visit(ConstructorInvocation) ? // why don't we handle exceptions like in visit(ConstructorInvocation) ? (these two things are same in polyglot // implementation) CAstNode superCall = makeNode(context, fFactory, n.getBody(), CAstNode.CALL, makeNode(context, fFactory, n.getBody(), CAstNode.SUPER), fFactory.makeConstant(callSiteRef)); Object mapper = new Object(); // dummy used for mapping this node in CFG handleThrowsFromCall(defaultSuperCtor, mapper, context); context.cfg().map(mapper, superCall); ArrayList<CAstNode> origStatements = createBlock(n.getBody(), context); CAstNode[] bodyNodes = new CAstNode[inits.size() + origStatements.size() + 1]; // superCall, inits, ctor body int idx = 0; bodyNodes[idx++] = superCall; for (ASTNode init : inits) bodyNodes[idx++] = visitFieldInitNode(init, context); for (int i = 0; i < origStatements.size(); i++) bodyNodes[idx++] = origStatements.get(i); return makeNode(context, fFactory, n.getBody(), CAstNode.BLOCK_STMT, bodyNodes); } } /** * Make a "fake" function (it doesn't exist in source code but it does in bytecode) for covariant return types. * * @param overriding Declaration of the overriding method. * @param overridden Binding of the overridden method, in a a superclass or implemented interface. * @param oldContext * @return */ private CAstEntity makeSyntheticCovariantRedirect(MethodDeclaration overriding, IMethodBinding overridingBinding, IMethodBinding overridden, WalkContext oldContext) { // SuperClass foo(A, B, C...) // SubClass foo(A,B,C...) // // add a method exactly like overridden that calls overriding final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>(); final MethodContext context = new MethodContext(oldContext, memberEntities); CAstNode calltarget; if ((overridingBinding.getModifiers() & Modifier.STATIC) == 0) calltarget = makeNode(context, fFactory, null, CAstNode.SUPER); else calltarget = makeNode(context, fFactory, null, CAstNode.VOID); ITypeBinding paramTypes[] = overridden.getParameterTypes(); ArrayList<CAstNode> arguments = new ArrayList<CAstNode>(); int i = 0; for (Object o : overriding.parameters()) { SingleVariableDeclaration svd = (SingleVariableDeclaration) o; CAstNode varNode = makeNode(context, fFactory, null, CAstNode.VAR, fFactory.makeConstant(svd.getName().getIdentifier())); ITypeBinding fromType = JDT2CAstUtils.getErasedType(paramTypes[i], ast); ITypeBinding toType = JDT2CAstUtils.getErasedType(overridingBinding.getParameterTypes()[i], ast); if (fromType.equals(toType)) { arguments.add(varNode); } else { arguments.add(createCast(null, varNode, fromType, toType, context)); } i++; } CAstNode callnode = createMethodInvocation(null, overridingBinding, calltarget, arguments, context); CAstNode mdast = makeNode(context, fFactory, null, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, null, CAstNode.BLOCK_STMT, makeNode(context, fFactory, null, CAstNode.RETURN, callnode))); // make parameters to new synthetic method // use RETURN TYPE of overridden, everything else from overriding (including parameter names) ArrayList<CAstType> paramCAstTypes = new ArrayList<CAstType>(overridden.getParameterTypes().length); for (ITypeBinding paramType : overridden.getParameterTypes()) paramCAstTypes.add(fTypeDict.getCAstTypeFor(paramType)); return new ProcedureEntity(mdast, overriding, overridingBinding.getDeclaringClass(), memberEntities, context, paramCAstTypes, overridden.getReturnType(), null); } /** * @param inits Instance intializers & field initializers. Only used if method is a constructor, in which case the initializers * will be inserted in. */ private CAstEntity visit(MethodDeclaration n, ITypeBinding classBinding, WalkContext oldContext, ArrayList<ASTNode> inits) { // pass in memberEntities to the context, later visit(New) etc. may add classes final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>(); final MethodContext context = new MethodContext(oldContext, memberEntities); // LEFTOUT: in polyglot there is a // class context in between method and // root CAstNode mdast; if (n.isConstructor()) mdast = createConstructorBody(n, classBinding, context, inits); else if ((n.getModifiers() & Modifier.ABSTRACT) != 0) // abstract mdast = null; else if (n.getBody() == null || n.getBody().statements().size() == 0) // empty mdast = makeNode(context, fFactory, n, CAstNode.RETURN); else mdast = visitNode(n.getBody(), context); // Polyglot comment: Presumably the MethodContext's parent is a ClassContext, // and he has the list of initializers. Hopefully the following // will glue that stuff in the right place in any constructor body. Set<CAstAnnotation> annotations = null; if (n.resolveBinding() != null) { annotations = handleAnnotations(n.resolveBinding()); } return new ProcedureEntity(mdast, n, classBinding, memberEntities, context, annotations); } private Set<CAstAnnotation> handleAnnotations(IBinding binding) { IAnnotationBinding[] annotations = binding.getAnnotations(); if(annotations == null || annotations.length == 0) { return null; } Set<CAstAnnotation> castAnnotations = HashSetFactory.make(); for(IAnnotationBinding annotation : annotations) { ITypeBinding annotationTypeBinding = annotation.getAnnotationType(); final CAstType annotationType = fTypeDict.getCAstTypeFor(annotationTypeBinding); final Map<String,Object> args = HashMapFactory.make(); for(IMemberValuePairBinding mvpb : annotation.getAllMemberValuePairs()) { String name = mvpb.getName(); Object value = mvpb.getValue(); args.put(name, value); } castAnnotations.add(new CAstAnnotation() { @Override public CAstType getType() { return annotationType; } @Override public Map<String, Object> getArguments() { return args; } @Override public String toString() { return annotationType.getName() + args; } }); } return castAnnotations; } protected final class ProcedureEntity implements JavaProcedureEntity { // TAGALONG (make static, access ast) // From Code Body Entity private final Map<CAstNode, Collection<CAstEntity>> fEntities; @Override public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() { return Collections.unmodifiableMap(fEntities); } @Override public Iterator getScopedEntities(CAstNode construct) { if (fEntities.containsKey(construct)) { return (fEntities.get(construct)).iterator(); } else { return EmptyIterator.instance(); } } @Override public String getSignature() { return Util.methodEntityToSelector(this).toString(); } private final CAstNode fAst; MethodDeclaration fDecl; // TAGALONG serious tagalong... private String[] fParameterNames; // INCLUDING this private ArrayList<CAstType> fParameterTypes; private MethodContext fContext; // possibly TAGALONG, maybe not private ITypeBinding fType; // TAGALONG protected ITypeBinding fReturnType; private int fModifiers; private final Set<CAstAnnotation> annotations; // can be method, constructor, "fake" default constructor, or null decl = static initializer /** * For a static initializer, pass a null decl. */ // FIXME: get rid of decl and pass in everything instead of having to do two different things with parameters // regular case private ProcedureEntity(CAstNode mdast, MethodDeclaration decl, ITypeBinding type, Map<CAstNode, CAstEntity> entities, MethodContext context, Set<CAstAnnotation> annotations) { this(mdast, decl, type, entities, context, null, null, decl.getModifiers(), annotations); } // static init private ProcedureEntity(CAstNode mdast, ITypeBinding type, Map<CAstNode, CAstEntity> entities, MethodContext context, Set<CAstAnnotation> annotations) { this(mdast, null, type, entities, context, null, null, 0, annotations); } private ProcedureEntity(CAstNode mdast, MethodDeclaration decl, ITypeBinding type, Map<CAstNode, CAstEntity> entities, MethodContext context, ArrayList<CAstType> parameterTypes, ITypeBinding returnType, Set<CAstAnnotation> annotations) { this(mdast, decl, type, entities, context, parameterTypes, returnType, decl.getModifiers(), annotations); } private ProcedureEntity(CAstNode mdast, MethodDeclaration decl, ITypeBinding type, Map<CAstNode, CAstEntity> entities, MethodContext context, ArrayList<CAstType> parameterTypes, ITypeBinding returnType, int modifiers, Set<CAstAnnotation> annotations) { // TypeSystem system, CodeInstance pd, String[] argumentNames, // } // Map<CAstNode, CAstEntity> entities, MethodContext mc) { fDecl = decl; fAst = mdast; // "procedure decl ast" fContext = context; fType = type; fReturnType = returnType; fModifiers = modifiers; this.annotations = annotations; // from CodeBodyEntity fEntities = new LinkedHashMap<CAstNode, Collection<CAstEntity>>(); for (Iterator keys = entities.keySet().iterator(); keys.hasNext();) { CAstNode key = (CAstNode) keys.next(); fEntities.put(key, Collections.singleton(entities.get(key))); } if (fDecl != null) { int i = 0; // index to start filling up with real params if ((fModifiers & Modifier.STATIC) != 0) { fParameterNames = new String[fDecl.parameters().size()]; i = 0; } else { fParameterNames = new String[fDecl.parameters().size() + 1]; fParameterNames[0] = "this"; i = 1; } if (parameterTypes == null) { fParameterTypes = new ArrayList<CAstType>(fDecl.parameters().size()); for (Object p : fDecl.parameters()) { fParameterNames[i++] = ((SingleVariableDeclaration) p).getName().getIdentifier(); fParameterTypes.add(fTypeDict.getCAstTypeFor(((SingleVariableDeclaration) p).resolveBinding().getType())); } } else { // currently this is only used in making a default constructor with arguments (anonymous classes). // this is because we cannot synthesize bindings. fParameterTypes = parameterTypes; for (Object p : fDecl.parameters()) { fParameterNames[i++] = ((SingleVariableDeclaration) p).getName().getIdentifier(); } } } else { fParameterNames = new String[0]; fParameterTypes = new ArrayList<CAstType>(0); // static initializer } } @Override public Collection<CAstAnnotation> getAnnotations() { return annotations; } @Override public String toString() { return fDecl == null ? "<clinit>" : fDecl.toString(); } @Override public int getKind() { return CAstEntity.FUNCTION_ENTITY; } @Override public String getName() { if (fDecl == null) return MethodReference.clinitName.toString(); else if (fDecl.isConstructor()) return MethodReference.initAtom.toString(); else return fDecl.getName().getIdentifier(); } /** * INCLUDING first parameter 'this' (for non-static methods) */ @Override public String[] getArgumentNames() { return fParameterNames; } @Override public CAstNode[] getArgumentDefaults() { return new CAstNode[0]; } /** * INCLUDING first parameter 'this' (for non-static methods) */ @Override public int getArgumentCount() { return fParameterNames.length; } @Override public CAstNode getAST() { return fAst; } @Override public CAstControlFlowMap getControlFlow() { return fContext.cfg(); } @Override public CAstSourcePositionMap getSourceMap() { return fContext.pos(); } @Override public CAstSourcePositionMap.Position getPosition() { return fDecl==null? getSourceMap().getPosition(fAst): makePosition(fDecl); } @Override public CAstNodeTypeMap getNodeTypeMap() { return fContext.getNodeTypeMap(); } @Override public Collection getQualifiers() { if (fDecl == null) return JDT2CAstUtils.mapModifiersToQualifiers(Modifier.STATIC, false, false); // static init else return JDT2CAstUtils.mapModifiersToQualifiers(fModifiers, false, false); } @Override public CAstType getType() { return new CAstType.Method() { private Collection<CAstType> fExceptionTypes = null; @Override @SuppressWarnings("deprecation") public CAstType getReturnType() { if (fReturnType != null) return fTypeDict.getCAstTypeFor(fReturnType); Type type = fDecl == null ? null : (ast.apiLevel() == 2 ? fDecl.getReturnType() : fDecl.getReturnType2()); if (type == null) return fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("void")); else return fTypeDict.getCAstTypeFor(type.resolveBinding()); } /** * NOT INCLUDING first parameter 'this' (for non-static methods) */ @Override public List getArgumentTypes() { return fParameterTypes; } /** * NOT INCLUDING first parameter 'this' (for non-static methods) */ @Override public int getArgumentCount() { return fDecl == null ? 0 : fParameterTypes.size(); } @Override public String getName() { Assertions.UNREACHABLE("CAstType.FunctionImpl#getName() called???"); return "?"; } @Override public Collection getSupertypes() { Assertions.UNREACHABLE("CAstType.FunctionImpl#getSupertypes() called???"); return null; } @Override public Collection/* <CAstType> */getExceptionTypes() { if (fExceptionTypes == null) { fExceptionTypes = new LinkedHashSet<CAstType>(); if (fDecl != null) for (Object exception : fDecl.thrownExceptions()) fExceptionTypes.add(fTypeDict.getCAstTypeFor(((Name) exception).resolveTypeBinding())); } return fExceptionTypes; } @Override public CAstType getDeclaringType() { return fTypeDict.getCAstTypeFor(fType); } }; } } // //////////////////////////////////// // FIELDS //////////////////////////// // //////////////////////////////////// // FIELDS ADDED DIRECTLY IN visit(TypeDeclaration,WalkContext) private CAstNode visitFieldInitNode(ASTNode node, WalkContext context) { // there are gathered by createClassDeclaration and can only be of two types: if (node instanceof Initializer) { return visitNode(((Initializer) node).getBody(), context); } else if (node instanceof VariableDeclarationFragment) { VariableDeclarationFragment f = (VariableDeclarationFragment) node; // this is guaranteed to have an initializer // Generate CAST node for the initializer (init()) // Type targetType = f.memberInstance().container(); // Type fieldType = f.type().type(); FieldReference fieldRef = fIdentityMapper.getFieldRef(f.resolveBinding()); // We use null to indicate an OBJECT_REF to a static field, as the // FieldReference doesn't // hold enough info to determine this. In this case, (unlike field ref) // we don't have a // target expr to evaluate. boolean isStatic = ((f.resolveBinding().getModifiers() & Modifier.STATIC) != 0); CAstNode thisNode = isStatic ? makeNode(context, fFactory, null, CAstNode.VOID) : makeNode(context, fFactory, f, CAstNode.THIS); CAstNode lhsNode = makeNode(context, fFactory, f, CAstNode.OBJECT_REF, thisNode, fFactory.makeConstant(fieldRef)); Expression init = f.getInitializer(); CAstNode rhsNode = visitNode(init, context); CAstNode assNode = makeNode(context, fFactory, f, CAstNode.ASSIGN, lhsNode, rhsNode); return assNode; // their naming, not mine } else if (node instanceof EnumConstantDeclaration) { return createEnumConstantDeclarationInit((EnumConstantDeclaration) node, context); } else { Assertions.UNREACHABLE("invalid init node gathered by createClassDeclaration"); return null; } } protected final class FieldEntity implements CAstEntity { private final ITypeBinding type; private final String name; private final Collection<CAstQualifier> quals; private final JdtPosition position; private final Set<CAstAnnotation> annotations; private FieldEntity(String name, ITypeBinding type, Collection<CAstQualifier> quals, JdtPosition position, Set<CAstAnnotation> annotations) { super(); this.type = type; this.quals = quals; this.name = name; this.position = position; this.annotations = annotations; } @Override public Collection<CAstAnnotation> getAnnotations() { return annotations; } @Override public int getKind() { return CAstEntity.FIELD_ENTITY; } @Override public String getName() { return name; } @Override public String getSignature() { return name + fIdentityMapper.typeToTypeID(type); } @Override public String[] getArgumentNames() { return new String[0]; } @Override public CAstNode[] getArgumentDefaults() { return new CAstNode[0]; } @Override public int getArgumentCount() { return 0; } @Override public Iterator getScopedEntities(CAstNode construct) { return EmptyIterator.instance(); } @Override public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() { return Collections.emptyMap(); } @Override public CAstNode getAST() { // No AST for a field decl; initializers folded into // constructor processing... return null; } @Override public CAstControlFlowMap getControlFlow() { // No AST for a field decl; initializers folded into // constructor processing... return null; } @Override public CAstSourcePositionMap getSourceMap() { // No AST for a field decl; initializers folded into // constructor processing... return null; } @Override public CAstSourcePositionMap.Position getPosition() { return position; } @Override public CAstNodeTypeMap getNodeTypeMap() { // No AST for a field decl; initializers folded into // constructor processing... return null; } @Override public Collection getQualifiers() { return quals; } @Override public CAstType getType() { return fTypeDict.getCAstTypeFor(type); } } // ///////////////////////////////////// // / NODES ///////////////////////////// // ///////////////////////////////////// /** * Visit all the statements in the block and return an arraylist of the statements. Some statements * (VariableDeclarationStatements) may expand to more than one CAstNode. */ private ArrayList<CAstNode> createBlock(Block n, WalkContext context) { ArrayList<CAstNode> stmtNodes = new ArrayList<CAstNode>(); for (Object s : n.statements()) visitNodeOrNodes((ASTNode) s, context, stmtNodes); return stmtNodes; } private CAstNode visit(Block n, WalkContext context) { ArrayList<CAstNode> stmtNodes = createBlock(n, context); CAstNode stmtNodesArray[] = stmtNodes.toArray(new CAstNode[stmtNodes.size()]); return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, stmtNodesArray)); } private CAstNode visit(VariableDeclarationFragment n, WalkContext context) { int modifiers; if (n.getParent() instanceof VariableDeclarationStatement) modifiers = ((VariableDeclarationStatement) n.getParent()).getModifiers(); else if (n.getParent() instanceof VariableDeclarationExpression) modifiers = ((VariableDeclarationExpression) n.getParent()).getModifiers(); else modifiers = ((FieldDeclaration) n.getParent()).getModifiers(); boolean isFinal = (modifiers & Modifier.FINAL) != 0; ITypeBinding type = n.resolveBinding().getType(); Expression init = n.getInitializer(); CAstNode initNode; String t = type.getBinaryName(); if (init == null) { if (JDT2CAstUtils.isLongOrLess(type)) // doesn't include boolean initNode = fFactory.makeConstant(0); else if (t.equals("D") || t.equals("F")) initNode = fFactory.makeConstant(0.0); else initNode = fFactory.makeConstant(null); } else initNode = visitNode(init, context); Object defaultValue = JDT2CAstUtils.defaultValueForType(type); return makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(n.getName().getIdentifier(), fTypeDict.getCAstTypeFor(type), isFinal, defaultValue)), initNode); } /* * One VariableDeclarationStatement represents more than one CAstNode statement. */ private ArrayList<CAstNode> visit(VariableDeclarationStatement n, WalkContext context) { ArrayList<CAstNode> result = new ArrayList<CAstNode>(); for (Object o : n.fragments()) result.add(visit((VariableDeclarationFragment) o, context)); return result; } private CAstNode visit(ArrayInitializer n, WalkContext context) { ITypeBinding type = n.resolveTypeBinding(); assert type != null : "Could not determine type of ArrayInitializer"; TypeReference newTypeRef = fIdentityMapper.getTypeRef(type); CAstNode[] eltNodes = new CAstNode[n.expressions().size() + 1]; int idx = 0; eltNodes[idx++] = makeNode(context, fFactory, n, CAstNode.NEW, fFactory.makeConstant(newTypeRef), fFactory.makeConstant(n .expressions().size())); for (Iterator iter = n.expressions().iterator(); iter.hasNext(); idx++) { Expression element = (Expression) iter.next(); eltNodes[idx] = visitNode(element, context); if (eltNodes[idx] == null) assert eltNodes[idx] != null : element.toString(); } return makeNode(context, fFactory, n, CAstNode.ARRAY_LITERAL, eltNodes); } private CAstNode visit(ClassInstanceCreation n, WalkContext context) { return createClassInstanceCreation(n, n.arguments(), n.resolveConstructorBinding(), n.getExpression(), n .getAnonymousClassDeclaration(), context); } private CAstNode createClassInstanceCreation(ASTNode nn, List/* Expression or CAstNode */arguments, IMethodBinding ctorBinding, Expression qual, AnonymousClassDeclaration anonDecl, WalkContext context) { // a new instruction is actually two things: a NEW object and a CALL to a constructor CAstNode newNode; CAstNode callNode; final String tmpName = "ctor temp"; // this name is an illegal Java identifier. this var will hold the new object so we can call the constructor on it // GENERICS getMethodDeclaration() ctorBinding = ctorBinding.getMethodDeclaration(); // unlike polyglot, this will // point to a default // constructor in the anon class MethodReference ctorRef = fIdentityMapper.getMethodRef(ctorBinding); // ////////////// PART I: make the NEW expression /////////////////////////////////////////////////////// ITypeBinding newType = ctorBinding.getDeclaringClass(); TypeReference newTypeRef = fIdentityMapper.getTypeRef(newType); // new nodes with an explicit enclosing argument, e.g. "outer.new Inner()". They are mostly treated the same, except // in JavaCAst2IRTranslator.doNewObject CAstNode qualNode = null; if (qual == null && newType.getDeclaringClass() != null && ((newType.getModifiers() & Modifier.STATIC) == 0) && !newType.isLocal()) { // "new X()" expanded into "new this.X()" or "new MyClass.this.X" // check isLocal because anonymous classes and local classes are not included. ITypeBinding plainThisType = JDT2CAstUtils.getDeclaringClassOfNode(nn); // type of "this" ITypeBinding implicitThisType = findClosestEnclosingClassSubclassOf(plainThisType, newType.getDeclaringClass(), ((newType .getModifiers() & Modifier.PRIVATE) != 0)); if (implicitThisType.isEqualTo(plainThisType)) qualNode = makeNode(context, fFactory, nn, CAstNode.THIS); // "new this.X()" else qualNode = makeNode(context, fFactory, nn, CAstNode.THIS, fFactory.makeConstant(fIdentityMapper .getTypeRef(implicitThisType))); // "new Bla.this.X()" } else if (qual != null) qualNode = visitNode(qual, context); if (qualNode != null) newNode = makeNode(context, fFactory, nn, CAstNode.NEW_ENCLOSING, fFactory.makeConstant(newTypeRef), qualNode); else newNode = makeNode(context, fFactory, nn, CAstNode.NEW, fFactory.makeConstant(newTypeRef)); ITypeBinding[] newExceptions = new ITypeBinding[] { NoClassDefFoundError, ExceptionInInitializerError, OutOfMemoryError }; context.cfg().map(newNode, newNode); for (ITypeBinding exp : newExceptions) { for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(exp)) { context.cfg().add(newNode, catchTarget.snd, catchTarget.fst); } } // ANONYMOUS CLASSES // ctor already points to right place, so should type ref, so all we have to do is make the entity if (anonDecl != null) context.addScopedEntity(newNode, visit(anonDecl, context)); // ////////////// PART II: make the CALL expression /////////////////////////////////////////////////////// // setup args & handle exceptions CAstNode[] argNodes = new CAstNode[arguments.size() + 2]; int idx = 0; // args: [ this, callsiteref, <actual args> ] // arg 0: this argNodes[idx++] = makeNode(context, fFactory, nn, CAstNode.VAR, fFactory.makeConstant(tmpName), fFactory.makeConstant(fTypeDict.getCAstTypeFor(newType))); // contains output from newNode (see part III) // arg 1: call site ref (WHY?) int dummyPC = 0; // Just want to wrap the kind of call; the "rear end" won't care about anything else CallSiteReference callSiteRef = CallSiteReference.make(dummyPC, ctorRef, IInvokeInstruction.Dispatch.SPECIAL); argNodes[idx++] = fFactory.makeConstant(callSiteRef); // rest of args for (Iterator iter = arguments.iterator(); iter.hasNext();) { Object arg = iter.next(); argNodes[idx++] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context); } callNode = makeNode(context, fFactory, nn, CAstNode.CALL, argNodes); context.cfg().map(nn, callNode); handleThrowsFromCall(ctorBinding, nn, context); // PART III: make a node with both NEW and CALL // Make a LOCAL_SCOPE with a BLOCK_EXPR node child which does three things: // 1) declare a temporary variable and assign the new object to it (LOCAL_SCOPE is needed to chain this new variable // to this block) // 2) CALL the constructor on the new variable // 3) access this temporary variable. Since the value of the block is the last thing in the block, the resultant // value will be the variable return makeNode(context, fFactory, nn, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, nn, CAstNode.BLOCK_EXPR, makeNode( context, fFactory, nn, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol(tmpName, fTypeDict.getCAstTypeFor(newType), true)), newNode), callNode, makeNode(context, fFactory, nn, CAstNode.VAR, fFactory.makeConstant(tmpName)))); } /** * * @param met * @param mappedAstNode An AST node or object mapped in the CFG: we will call context.cfg().add() on it. Caller must worry about * mapping it with context.cfg().map(). * @param context */ private void handleThrowsFromCall(IMethodBinding met, Object mappedAstNode, WalkContext context) { ITypeBinding[] throwTypes = met.getExceptionTypes(); for (ITypeBinding thrownType : throwTypes) { Collection<Pair<ITypeBinding, Object>> catchTargets = context.getCatchTargets(thrownType); for (Pair<ITypeBinding, Object> catchTarget : catchTargets) context.cfg().add(mappedAstNode, catchTarget.snd, catchTarget.fst); } // can also throw runtime exception for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType)) context.cfg().add(mappedAstNode, catchTarget.snd, catchTarget.fst); } private CAstNode visit(ExpressionStatement n, WalkContext context) { return visitNode(n.getExpression(), context); } private CAstNode visit(SuperMethodInvocation n, WalkContext context) { CAstNode target; if (n.getQualifier() == null) target = makeNode(context, fFactory, n, CAstNode.SUPER); else { TypeReference owningTypeRef = fIdentityMapper.getTypeRef(n.getQualifier().resolveTypeBinding()); target = makeNode(context, fFactory, n, CAstNode.SUPER, fFactory.makeConstant(owningTypeRef)); } // GENERICS getMethodDeclaration() return createMethodInvocation(n, n.resolveMethodBinding().getMethodDeclaration(), target, n.arguments(), context); } // FIXME: implicit this private CAstNode visit(MethodInvocation n, WalkContext context) { // GENERICS getMethodDeclaration() IMethodBinding binding = n.resolveMethodBinding().getMethodDeclaration(); if ((binding.getModifiers() & Modifier.STATIC) != 0) { CAstNode target; // JLS says: evaluate qualifier & throw away unless of course it's just a class name (or null), // in which case we replace the EMPTY with a VOID // of course, "this" has no side effects either. target = visitNode(n.getExpression(), context); if (target.getKind() == CAstNode.EMPTY || target.getKind() == CAstNode.THIS) return createMethodInvocation(n, binding, makeNode(context, fFactory, null, CAstNode.VOID), n.arguments(), context); else return makeNode(context, fFactory, n, CAstNode.BLOCK_EXPR, target, createMethodInvocation(n, binding, makeNode(context, fFactory, null, CAstNode.VOID), n.arguments(), context)); // target is evaluated but thrown away, and only result of method invocation is kept } else { CAstNode target; if (n.getExpression() != null) { target = visitNode(n.getExpression(), context); } else { ITypeBinding typeOfThis = JDT2CAstUtils.getDeclaringClassOfNode(n); boolean methodIsPrivate = (binding.getModifiers() & Modifier.PRIVATE) != 0; // how could it be in the subtype and private? this only happens the supertype is also an // enclosing type. in that case the variable refers to the field in the enclosing instance. // NOTE: method may be defined in MyClass's superclass, but we still want to expand // this into MyClass, so we have to find the enclosing class which defines this function. ITypeBinding implicitThisClass = findClosestEnclosingClassSubclassOf(typeOfThis, binding.getDeclaringClass(), methodIsPrivate); if (typeOfThis.isEqualTo(implicitThisClass)) // "foo = 5" -> "this.foo = 5": expand into THIS + class target = makeNode(context, fFactory, n, CAstNode.THIS); else // "foo = 5" -> "MyClass.this.foo = 5" -- inner class: expand into THIS + class target = makeNode(context, fFactory, n, CAstNode.THIS, fFactory.makeConstant(fIdentityMapper .getTypeRef(implicitThisClass))); // NOTE: method may be defined in MyClass's superclass, but we still want to expand // this into MyClass, so we have to find the enclosing class which defines this function. } CAstNode node = createMethodInvocation(n, binding, target, n.arguments(), context); // TODO: maybe not exactly right... what if it's a capture? we may have to cast it down a little bit. if (binding.getReturnType().isTypeVariable()) { // GENERICS: add a cast ITypeBinding realtype = JDT2CAstUtils.getErasedType(n.resolveMethodBinding().getReturnType(), ast); ITypeBinding fromtype = JDT2CAstUtils.getTypesVariablesBase(binding.getReturnType(), ast); if (!realtype.isEqualTo(fromtype)) return createCast(n, node, fromtype, realtype, context); } return node; } } private CAstNode createMethodInvocation(ASTNode pos, IMethodBinding methodBinding, CAstNode target, List/* Expression or CAstNode */arguments, WalkContext context) { // MethodMethodInstance methodInstance = n.methodInstance(); boolean isStatic = (methodBinding.getModifiers() & Modifier.STATIC) != 0; ITypeBinding methodOwner = methodBinding.getDeclaringClass(); if (!(methodOwner.isInterface() || methodOwner.isClass() || methodOwner.isEnum())) { assert false : "owner " + methodOwner + " of " + methodBinding + " is not a class"; } // POPULATE PARAMETERS // this (or void for static), method reference, rest of args int nFormals = methodBinding.getParameterTypes().length; CAstNode[] children = new CAstNode[2 + nFormals]; // this (or void for static) assert target != null : "no receiver for " + methodBinding; children[0] = target; // method reference // unlike polyglot, expression will never be super here. this is handled in SuperMethodInvocation. IInvokeInstruction.IDispatch dispatchType; if (isStatic) dispatchType = IInvokeInstruction.Dispatch.STATIC; else if (methodOwner.isInterface()) dispatchType = IInvokeInstruction.Dispatch.INTERFACE; else if ((methodBinding.getModifiers() & Modifier.PRIVATE) != 0 || target.getKind() == CAstNode.SUPER) // only one possibility, not a virtual call (I guess?) dispatchType = IInvokeInstruction.Dispatch.SPECIAL; else dispatchType = IInvokeInstruction.Dispatch.VIRTUAL; // pass 0 for dummyPC: Just want to wrap the kind of call; the "rear end" won't care about anything else... CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(methodBinding), dispatchType); children[1] = fFactory.makeConstant(callSiteRef); populateArguments(children, methodBinding, arguments, context); Object fakeCfgMap = new Object(); handleThrowsFromCall(methodBinding, fakeCfgMap, context); CAstNode result = makeNode(context, fFactory, pos, CAstNode.CALL, children); context.cfg().map(fakeCfgMap, result); return result; } /** * Populate children, starting at index 2, for the invocation of methodBinding. If varargs are used this function will collapse * the proper arguments into an array. * * If the number of actuals equals the number of formals and the function is varargs, we have to check the type of the last * argument to see if we should "box" it in an array. If the arguments[arguments.length-1] is not an Expression, we cannot get the * type, so we do not box it. (Making covariant varargs functions require this behavior) * * @param children * @param methodBinding * @param arguments * @param context */ private void populateArguments(CAstNode[] children, IMethodBinding methodBinding, List/* CAstNode or Expression */arguments, WalkContext context) { int nFormals = methodBinding.getParameterTypes().length; assert children.length == nFormals + 2; int nActuals = arguments.size(); ITypeBinding lastArgType = null; if (nActuals > 0 && arguments.get(nActuals - 1) instanceof Expression) lastArgType = ((Expression) arguments.get(nActuals - 1)).resolveTypeBinding(); // if the # of actuals equals the # of formals, AND the function is varargs, we have to check // to see if the lastArgType is subtype compatible with the type of last parameter (which will be an array). // If it is, we pass this array in directly. Otherwise this it is wrapped in an array init. // Example: 'void foo(int... x)' can be run via 'foo(5)' or 'foo(new int[] { 5, 6 })' -- both have one argument so we must check // the type if (nActuals == nFormals && (!methodBinding.isVarargs() || lastArgType == null || lastArgType .isSubTypeCompatible(methodBinding.getParameterTypes()[nFormals - 1]))) { int i = 2; for (Object arg : arguments) children[i++] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context); } else { assert nActuals >= (nFormals - 1) && methodBinding.isVarargs() : "Invalid number of parameters for constructor call"; for (int i = 0; i < nFormals - 1; i++) { Object arg = arguments.get(i); children[i + 2] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context); } CAstNode subargs[] = new CAstNode[nActuals - nFormals + 2]; // nodes for args and one extra for NEW expression TypeReference newTypeRef = fIdentityMapper.getTypeRef(methodBinding.getParameterTypes()[nFormals - 1]); subargs[0] = makeNode(context, fFactory, null, CAstNode.NEW, fFactory.makeConstant(newTypeRef), fFactory .makeConstant(subargs.length - 1)); for (int j = 1; j < subargs.length; j++) { Object arg = arguments.get(j + nFormals - 2); subargs[j] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context); } children[nFormals + 1] = makeNode(context, fFactory, (ASTNode) null, CAstNode.ARRAY_LITERAL, subargs); } } private CAstNode visit(ReturnStatement r, WalkContext context) { Expression retExpr = r.getExpression(); if (retExpr == null) return makeNode(context, fFactory, r, CAstNode.RETURN); else return makeNode(context, fFactory, r, CAstNode.RETURN, visitNode(retExpr, context)); } private CAstNode visit(Assignment n, WalkContext context) { if (n.getOperator() == Assignment.Operator.ASSIGN) return makeNode(context, fFactory, n, CAstNode.ASSIGN, visitNode(n.getLeftHandSide(), new AssignmentContext(context)), visitNode(n.getRightHandSide(), context)); else { CAstNode left = visitNode(n.getLeftHandSide(), context); // GENERICs lvalue for pre op hack if (left.getKind() == CAstNode.CAST) { return doFunkyGenericAssignPreOpHack(n, context); } // +=, %=, &=, etc. CAstNode result = makeNode(context, fFactory, n, CAstNode.ASSIGN_PRE_OP, left, visitNode(n.getRightHandSide(), context), JDT2CAstUtils.mapAssignOperator(n.getOperator())); // integer division by zero if (JDT2CAstUtils.isLongOrLess(n.resolveTypeBinding()) && (n.getOperator() == Assignment.Operator.DIVIDE_ASSIGN || n.getOperator() == Assignment.Operator.REMAINDER_ASSIGN)) { Collection<Pair<ITypeBinding, Object>> excTargets = context.getCatchTargets(fDivByZeroExcType); if (!excTargets.isEmpty()) { for (Pair<ITypeBinding, Object> catchPair : excTargets) context.cfg().add(result, catchPair.snd, fDivByZeroExcType); } else { context.cfg().add(result, CAstControlFlowMap.EXCEPTION_TO_EXIT, fDivByZeroExcType); } } return result; } } /** * Consider the case: * * <pre> * String real_oneheyya = (((returnObjectWithSideEffects().y))+="hey")+"ya" * </pre> * * where field 'y' is parameterized to type string. then += is not defined for type 'object'. This function is a hack that expands * the code into an assignment and binary operation. * * @param leftCast this is the left cast in the original expression. We throw most of it away, although we use the "Cast from" and * "cast to" * @param left * @param context * @return */ private CAstNode doFunkyGenericAssignPreOpHack(Assignment assign, WalkContext context) { Expression left = assign.getLeftHandSide(); Expression right = assign.getRightHandSide(); // consider the case: // String real_oneheyya = (((returnObjectWithSideEffects().y))+="hey")+"ya"; // this is going to be a MAJOR pain... // where field 'y' is parameterized to type string. then += is not defined for type 'object'. we want to transform // it kind of like this, except we have to define temp. // String real_oneheyya = (String)((temp=cg2WithSideEffects()).y = (String)temp.y + "hey")+"ya"; // ---------------------------------------------------------------- // // we are responsible for underlined portion // CAST(LOCAL SCOPE(BLOCK EXPR(DECL STMT(temp, // left.target),ASSIGN(OBJECT_REF(temp,y),BINARY_EXPR(CAST(OBJECT_REF(Temp,y)),RIGHT))))) // yeah, I know, it's cheating, LOCAL SCOPE / DECL STMT inside an expression ... will it work? while (left instanceof ParenthesizedExpression) left = ((ParenthesizedExpression) left).getExpression(); assert left instanceof FieldAccess : "Cast in assign pre-op but no field access?!"; FieldAccess field = (FieldAccess) left; InfixExpression.Operator infixop = JDT2CAstUtils.mapAssignOperatorToInfixOperator(assign.getOperator()); // DECL_STMT: temp = ...; final String tmpName = "temp generic preop hack"; // illegal Java identifier CAstNode exprNode = visitNode(field.getExpression(), context); CAstNode tmpDeclNode = makeNode(context, fFactory, left, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol( tmpName, fTypeDict.getCAstTypeFor(field.getExpression().resolveTypeBinding()), true)), exprNode); // need two object refndoes "temp.y" CAstNode obref1 = createFieldAccess(makeNode(context, fFactory, left, CAstNode.VAR, fFactory.makeConstant(tmpName), fFactory.makeConstant(fTypeDict.getCAstTypeFor(field.resolveFieldBinding().getType()))), field .getName().getIdentifier(), field.resolveFieldBinding(), left, new AssignmentContext(context)); CAstNode obref2 = createFieldAccess(makeNode(context, fFactory, left, CAstNode.VAR, fFactory.makeConstant(tmpName), fFactory.makeConstant(fTypeDict.getCAstTypeFor(field.resolveFieldBinding().getType()))), field .getName().getIdentifier(), field.resolveFieldBinding(), left, context); ITypeBinding realtype = JDT2CAstUtils.getErasedType(field.resolveFieldBinding().getType(), ast); ITypeBinding fromtype = JDT2CAstUtils .getTypesVariablesBase(field.resolveFieldBinding().getVariableDeclaration().getType(), ast); CAstNode castedObref = obref2;// createCast(left, obref2, fromtype, realtype, context); // put it all together // CAST(LOCAL SCOPE(BLOCK EXPR(DECL STMT(temp, // left.target),ASSIGN(OBJECT_REF(temp,y),BINARY_EXPR(CAST(OBJECT_REF(Temp,y)),RIGHT))))) CAstNode result = makeNode(context, fFactory, assign, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, assign, CAstNode.BLOCK_EXPR, tmpDeclNode, makeNode(context, fFactory, assign, CAstNode.ASSIGN, obref1, createInfixExpression( infixop, realtype, left.getStartPosition(), left.getLength(), castedObref, right, context)))); return createCast(assign, result, fromtype, realtype, context); } private CAstNode visit(ParenthesizedExpression n, WalkContext context) { return visitNode(n.getExpression(), context); } private CAstNode visit(BooleanLiteral n, WalkContext context) { return fFactory.makeConstant(n.booleanValue()); } private CAstNode visit(CharacterLiteral n, WalkContext context) { return fFactory.makeConstant(n.charValue()); } private CAstNode visit(NullLiteral n, WalkContext context) { return fFactory.makeConstant(null); } private CAstNode visit(StringLiteral n, WalkContext context) { return fFactory.makeConstant(n.getLiteralValue()); } private CAstNode visit(TypeLiteral n, WalkContext context) { String typeName = fIdentityMapper.typeToTypeID(n.resolveTypeBinding()); return makeNode(context, fFactory, n, CAstNode.TYPE_LITERAL_EXPR, fFactory.makeConstant(typeName)); } private CAstNode visit(NumberLiteral n, WalkContext context) { return fFactory.makeConstant(n.resolveConstantExpressionValue()); } /** * SimpleName can be a field access, local, or class name (do nothing case) * * @param n * @param context * @return */ private CAstNode visit(SimpleName n, WalkContext context) { // class name, handled above in either method invocation, qualified name, or qualified this if (n.resolveBinding() instanceof ITypeBinding) return makeNode(context, fFactory, null, CAstNode.EMPTY); assert n.resolveBinding() instanceof IVariableBinding : "SimpleName's binding is not a variable or a type binding!"; IVariableBinding binding = (IVariableBinding) n.resolveBinding(); binding = binding.getVariableDeclaration(); // ignore weird generic stuff // TODO: enum constants if (binding.isField()) { // enum constants ... // implicit field access -- implicit this or class CAstNode targetNode; if ((binding.getModifiers() & Modifier.STATIC) != 0) { // "foo = 5" -> "MyClass.foo = 5" or "SomeEnclosingClass.foo" = 5 targetNode = makeNode(context, fFactory, null, CAstNode.EMPTY); // we will get type from binding. no side // effects in evaluating a class name, so NOP // here. } else { ITypeBinding typeOfThis = JDT2CAstUtils.getDeclaringClassOfNode(n); boolean fieldIsPrivate = (binding.getModifiers() & Modifier.PRIVATE) != 0; // how could it be in the subtype and private? this only happens the supertype is also an // enclosing type. in that case the variable refers to the field in the enclosing instance. // NOTE: method may be defined in MyClass's superclass, but we still want to expand // this into MyClass, so we have to find the enclosing class which defines this function. ITypeBinding implicitThisClass = findClosestEnclosingClassSubclassOf(typeOfThis, binding.getDeclaringClass(), fieldIsPrivate); if (typeOfThis.isEqualTo(implicitThisClass)) // "foo = 5" -> "this.foo = 5": expand into THIS + class targetNode = makeNode(context, fFactory, n, CAstNode.THIS); else // "foo = 5" -> "MyClass.this.foo = 5" -- inner class: expand into THIS + class targetNode = makeNode(context, fFactory, n, CAstNode.THIS, fFactory.makeConstant(fIdentityMapper .getTypeRef(implicitThisClass))); // NOTE: method may be defined in MyClass's superclass, but we still want to expand // this into MyClass, so we have to find the enclosing class which defines this function. // fFactory.makeConstant(owningTypeRef)); } return createFieldAccess(targetNode, n.getIdentifier(), binding, n, context); } else { // local CAstType t = fTypeDict.getCAstTypeFor(((IVariableBinding)n.resolveBinding()).getType()); return makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(n.getIdentifier()), fFactory.makeConstant(t)); } } /** * Sees if a field defined in owningTypeRef is contained & accessible to a type of typeOfThis. That is, if owningTypeRef == * typeOfThis or typeOfThis is a subtype and isPrivate is false. If this is not that case, looks in the enclosing class of * typeOfThis and tries again, and its enclosing class, ... * * Essentially if we have a field/method referenced only by name and we know its type (owningTypeRef), this function will return * owningTypeRef or the subtype that the field is accessed thru, for expanding "f = 5" into "TheClass.this.f = 5". * * @param typeOfThis * @param owningTypeRef * @param isPrivate * @return */ private ITypeBinding findClosestEnclosingClassSubclassOf(ITypeBinding typeOfThis, ITypeBinding owningType, boolean isPrivate) { // GENERICS // if (owningType.isParameterizedType()) // owningType = owningType.getTypeDeclaration(); // if (typeOfThis.isParameterizedType()) // typeOfThis = typeOfThis.getTypeDeclaration(); // // typeOfThis.getTypeDeclaration() owningType = owningType.getErasure(); ITypeBinding current = typeOfThis; while (current != null) { current = current.getErasure(); // Walk the hierarchy rather than using isSubTypeCompatible to handle // generics -- we need to perform erasure of super types boolean isInSubtype = false;//current.isSubTypeCompatible(owningType); ITypeBinding supertp = current; while (supertp != null){ supertp = supertp.getErasure(); // Use isSubTypeCompatible even though we are manually walking type hierarchy -- // that way interfaces are handled without us having to do it manually. if (supertp.isSubTypeCompatible(owningType)){ isInSubtype = true; break; } supertp = supertp.getSuperclass(); } // how could it be in the subtype and private? this only happens the supertype is also an // enclosing type. in that case the variable refers to the field in the enclosing instance. if (current.isEqualTo(owningType) || (isInSubtype && !isPrivate)) return current; current = current.getDeclaringClass(); } Assertions.UNREACHABLE("Couldn't find field in class or enclosing class or superclasses of these"); return null; } /** * Process a field access. Semantics differ for static and instance fields. Fields can throw null pointer exceptions so we must * connect proper exceptional edges in the CFG. * * @param n * @param context * @return */ private CAstNode visit(FieldAccess n, WalkContext context) { CAstNode targetNode = visitNode(n.getExpression(), context); return createFieldAccess(targetNode, n.getName().getIdentifier(), n.resolveFieldBinding(), n, context); } /** * Used by visit(FieldAccess) and visit(SimpleName) -- implicit "this" / static field access. things from 'this' cannot throw an * exception. maybe handle this in here as a special case? i don't know... or check if targetNode is THIS, that should even work * for this.x = 5 and (this).x = 5 * * @param target Used to evaluate the field access. In the case of static field accesses, this is included in the first part of a * block -- thus it is evaluated for any side effects but thrown away. * @param fieldName Name of the field. * @param fieldBinding This is used to determine the field's class. * @param positioningNode Used only for making a JdtPosition. * @param context * @return */ private CAstNode createFieldAccess(CAstNode targetNode, String fieldName, IVariableBinding possiblyParameterizedBinding, ASTNode positioningNode, WalkContext context) { IVariableBinding fieldBinding = possiblyParameterizedBinding.getVariableDeclaration(); ITypeBinding targetType = fieldBinding.getDeclaringClass(); if (targetType == null) { // array assert fieldName.equals("length") : "null targetType but not aray length access"; return makeNode(context, fFactory, positioningNode, CAstNode.ARRAY_LENGTH, targetNode); } assert fieldBinding.isField() : "Field binding is not a field?!"; // we can probably safely delete this // check // translate JDT field ref to WALA field ref FieldReference fieldRef = fIdentityMapper.getFieldRef(fieldBinding); if ((fieldBinding.getModifiers() & Modifier.STATIC) != 0) { // JLS says: evaluate the target of the field ref and throw it away. // Hence the following block expr, whose 2 children are the target // evaluation // followed by the OBJECT_REF with a null target child (which the // "back-end" // CAst -> IR translator interprets as a static ref). // TODO: enum constants // don't worry about generics (can't declare static fields with type variables) if (fieldBinding.getConstantValue() != null) { return makeNode(context, fFactory, positioningNode, CAstNode.BLOCK_EXPR, targetNode, fFactory.makeConstant(fieldBinding .getConstantValue())); } else { return makeNode(context, fFactory, positioningNode, CAstNode.BLOCK_EXPR, targetNode, makeNode(context, fFactory, positioningNode, CAstNode.OBJECT_REF, makeNode(context, fFactory, null, CAstNode.VOID), fFactory.makeConstant(fieldRef))); } } else { CAstNode refNode = makeNode(context, fFactory, positioningNode, CAstNode.OBJECT_REF, targetNode, fFactory .makeConstant(fieldRef)); if (targetNode.getKind() != CAstNode.THIS) { // this.x will never throw a null pointer exception, because this // can never be null Collection excTargets = context.getCatchTargets(fNullPointerExcType); if (!excTargets.isEmpty()) { // connect NPE exception edge to relevant catch targets // (presumably only one) for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) { Pair catchPair = (Pair) iterator.next(); context.cfg().add(refNode, catchPair.snd, fNullPointerExcType); } } else { // connect exception edge to exit context.cfg().add(refNode, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNullPointerExcType); } context.cfg().map(refNode, refNode); } if (fieldBinding.getConstantValue() != null) { // don't have to worry about generics, a constant of generic type can only be null return makeNode(context, fFactory, positioningNode, CAstNode.BLOCK_EXPR, refNode, // evaluating 'refNode' can have side effects, so we must still evaluate it! fFactory.makeConstant(fieldBinding.getConstantValue())); } else { if (fieldBinding.getType().isTypeVariable() && !context.needLValue()) { // GENERICS: add a cast ITypeBinding realtype = JDT2CAstUtils.getErasedType(possiblyParameterizedBinding.getType(), ast); ITypeBinding fromtype = JDT2CAstUtils.getTypesVariablesBase(fieldBinding.getType(), ast); if (!realtype.isEqualTo(fromtype)) return createCast(positioningNode, refNode, fromtype, realtype, context); } return refNode; } } } private CAstNode visit(ThisExpression n, WalkContext context) { if (n.getQualifier() != null) { ITypeBinding owningType = n.getQualifier().resolveTypeBinding(); TypeReference owningTypeRef = fIdentityMapper.getTypeRef(owningType); return makeNode(context, fFactory, n, CAstNode.THIS, fFactory.makeConstant(owningTypeRef)); } else return makeNode(context, fFactory, n, CAstNode.THIS); } /** * QualifiedNames may be: 1) static of non-static field accesses -- we handle this case here 2) type names used in the context of: * a) field access (QualifiedName) b) method invocation c) qualifier of "this" in these cases we get the binding info in each of * these three functions and use them there, thus we return an EMPTY (no-op) here. 3) package names used in the context of a * QualifiedName class we return a EMPTY (no-op) here. * * @param n * @param context * @return */ private CAstNode visit(QualifiedName n, WalkContext context) { // "package.Class" is a QualifiedName, but also is "Class.staticField" // only handle if it's a "Class.staticField" ("Field" in polyglot AST) if (n.resolveBinding() instanceof IVariableBinding) { IVariableBinding binding = (IVariableBinding) n.resolveBinding(); assert binding.isField() : "Non-field variable QualifiedName!"; // if field access is static, visitNode(n.getQualifier()) will come back here // and we will return an EMPTY node return createFieldAccess(visitNode(n.getQualifier(), context), n.getName().getIdentifier(), binding, n, context); } else return makeNode(context, fFactory, null, CAstNode.EMPTY); // type name, handled in surrounding context } private CAstNode visit(InfixExpression n, WalkContext context) { Expression left = n.getLeftOperand(); ITypeBinding leftType = left.resolveTypeBinding(); int leftStartPosition = left.getStartPosition(); CAstNode leftNode = visitNode(left, context); int leftLength = n.getLeftOperand().getLength(); CAstNode result = createInfixExpression(n.getOperator(), leftType, leftStartPosition, leftLength, leftNode, n.getRightOperand(), context); if (n.hasExtendedOperands()) { // keep on adding operands on the right side leftLength = n.getRightOperand().getStartPosition() + n.getRightOperand().getLength() - leftStartPosition; for (Object o : n.extendedOperands()) { Expression operand = (Expression) o; result = createInfixExpression(n.getOperator(), leftType, leftStartPosition, leftLength, result, operand, context); if (leftType.isPrimitive() && operand.resolveTypeBinding().isPrimitive()) leftType = JDT2CAstUtils.promoteTypes(leftType, operand.resolveTypeBinding(), ast); // TODO: boxing else leftType = operand.resolveTypeBinding(); // leftStartPosition doesn't change, beginning is always the first operand leftLength = operand.getStartPosition() + operand.getLength() - leftStartPosition; } } return result; } private CAstNode createInfixExpression(InfixExpression.Operator op, ITypeBinding leftType, int leftStartPosition, int leftLength, CAstNode leftNode, Expression right, WalkContext context) { CAstNode rightNode = visitNode(right, context); int start = leftStartPosition; int end = right.getStartPosition() + right.getLength(); JdtPosition pos = makePosition(start, end); JdtPosition rightPos = makePosition(leftStartPosition, leftStartPosition + leftLength); JdtPosition leftPos = makePosition(right.getStartPosition(), right.getStartPosition() + right.getLength()); if (op == InfixExpression.Operator.CONDITIONAL_AND) { return makeNode(context, fFactory, pos, CAstNode.IF_EXPR, leftNode, rightNode, fFactory.makeConstant(false)); } else if (op == InfixExpression.Operator.CONDITIONAL_OR) { return makeNode(context, fFactory, pos, CAstNode.IF_EXPR, leftNode, fFactory.makeConstant(true), rightNode); } else { ITypeBinding rightType = right.resolveTypeBinding(); if (leftType.isPrimitive() && rightType.isPrimitive()) { // TODO: boxing ITypeBinding result = JDT2CAstUtils.promoteTypes(leftType, rightType, ast); // cast to proper type if (!result.isEqualTo(leftType)) leftNode = makeNode(context, fFactory, leftPos, CAstNode.CAST, fFactory.makeConstant(fTypeDict.getCAstTypeFor(result)), leftNode, fFactory.makeConstant(fTypeDict.getCAstTypeFor(leftType))); if (!result.isEqualTo(rightType)) rightNode = makeNode(context, fFactory, rightPos, CAstNode.CAST, fFactory.makeConstant(fTypeDict.getCAstTypeFor(result)), rightNode, fFactory.makeConstant(fTypeDict.getCAstTypeFor(rightType))); CAstNode opNode = makeNode(context, fFactory, pos, CAstNode.BINARY_EXPR, JDT2CAstUtils.mapBinaryOpcode(op), leftNode, rightNode); // divide by zero exception implicitly thrown if (JDT2CAstUtils.isLongOrLess(leftType) && JDT2CAstUtils.isLongOrLess(rightType) && (JDT2CAstUtils.mapBinaryOpcode(op) == CAstOperator.OP_DIV || JDT2CAstUtils.mapBinaryOpcode(op) == CAstOperator.OP_MOD)) { Collection excTargets = context.getCatchTargets(fDivByZeroExcType); if (!excTargets.isEmpty()) { for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) { Pair catchPair = (Pair) iterator.next(); context.cfg().add(op, catchPair.snd, fDivByZeroExcType); } } else { context.cfg().add(op, CAstControlFlowMap.EXCEPTION_TO_EXIT, fDivByZeroExcType); } } return opNode; } else { return makeNode(context, fFactory, pos, CAstNode.BINARY_EXPR, JDT2CAstUtils.mapBinaryOpcode(op), leftNode, rightNode); } } } private CAstNode visit(ConstructorInvocation n, WalkContext context) { // GENERICS getMethodDeclaration() return createConstructorInvocation(n.resolveConstructorBinding().getMethodDeclaration(), n.arguments(), n, context, false); } private CAstNode visit(SuperConstructorInvocation n, WalkContext context) { // FIXME: use expression?! polyglot doesn't handle it and it seems to be a very rare case. // class E { class X {} } // class Y extends E.X { Y(E e) { e.super(); } } // GENERICS getMethodDeclaration() return createConstructorInvocation(n.resolveConstructorBinding().getMethodDeclaration(), n.arguments(), n, context, true); } private CAstNode visit(SuperFieldAccess n, WalkContext context) { CAstNode targetNode; if (n.getQualifier() == null) targetNode = makeNode(context, fFactory, n, CAstNode.SUPER); else { TypeReference owningTypeRef = fIdentityMapper.getTypeRef(n.getQualifier().resolveTypeBinding()); targetNode = makeNode(context, fFactory, n, CAstNode.SUPER, fFactory.makeConstant(owningTypeRef)); } return createFieldAccess(targetNode, n.getName().getIdentifier(), n.resolveFieldBinding(), n, context); } /** * callerNode: used for positioning and also in CFG (handleThrowsFrom Call) */ private CAstNode createConstructorInvocation(IMethodBinding ctorBinding, List/* <Expression> */arguments, ASTNode callerNode, WalkContext context, boolean isSuper) { ITypeBinding ctorType = ctorBinding.getDeclaringClass(); assert ctorType.isClass(); // dummy PC = 0 -- Just want to wrap the kind of call; the "rear end" // won't care about anything else... CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(ctorBinding), IInvokeInstruction.Dispatch.SPECIAL); int nFormals = ctorBinding.getParameterTypes().length; CAstNode[] children = new CAstNode[2 + nFormals]; // this, call site ref, args CAstNode targetNode = makeNode(context, fFactory, callerNode, isSuper ? CAstNode.SUPER : CAstNode.THIS); children[0] = targetNode; children[1] = fFactory.makeConstant(callSiteRef); populateArguments(children, ctorBinding, arguments, context); handleThrowsFromCall(ctorBinding, callerNode, context); CAstNode result = makeNode(context, fFactory, callerNode, CAstNode.CALL, children); context.cfg().map(context, result); return result; } private CAstNode visit(IfStatement n, WalkContext context) { return makeNode(context, fFactory, n, CAstNode.IF_STMT, visitNode(n.getExpression(), context), visitNode(n.getThenStatement(), context), visitNode(n.getElseStatement(), context)); } private CAstNode visit(InstanceofExpression n, WalkContext context) { return makeNode(context, fFactory, n, CAstNode.INSTANCEOF, fFactory.makeConstant(fTypeDict.getCAstTypeFor(n.getRightOperand() .resolveBinding())), visitNode(n.getLeftOperand(), context)); } private CAstNode visit(CastExpression n, WalkContext context) { Expression arg = n.getExpression(); ITypeBinding castedFrom = arg.resolveTypeBinding(); ITypeBinding castedTo = n.getType().resolveBinding(); return createCast(n, visitNode(arg, context), castedFrom, castedTo, context); } private CAstNode createCast(ASTNode pos, CAstNode argNode, ITypeBinding castedFrom, ITypeBinding castedTo, WalkContext context) { Object cfgMapDummy = new Object(); // safer as 'pos' may be used for another purpose (i.e., this could be an implicit cast) // null can go into anything (e.g. in "((Foobar) null)" null can be assumed to be of type Foobar already) if (castedFrom.isNullType()) castedFrom = castedTo; CAstNode ast = makeNode(context, fFactory, pos, CAstNode.CAST, fFactory.makeConstant(fTypeDict.getCAstTypeFor(castedTo)), argNode, fFactory.makeConstant(fTypeDict.getCAstTypeFor(castedFrom))); Collection excTargets = context.getCatchTargets(fClassCastExcType); if (!excTargets.isEmpty()) { // connect ClassCastException exception edge to relevant catch targets // (presumably only one) for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) { Pair catchPair = (Pair) iterator.next(); context.cfg().add(cfgMapDummy, catchPair.snd, fClassCastExcType); } } else { // connect exception edge to exit context.cfg().add(cfgMapDummy, CAstControlFlowMap.EXCEPTION_TO_EXIT, fClassCastExcType); } context.cfg().map(cfgMapDummy, ast); return ast; } private CAstNode visit(ConditionalExpression n, WalkContext context) { return makeNode(context, fFactory, n, CAstNode.IF_EXPR, visitNode(n.getExpression(), context), visitNode(n.getThenExpression(), context), visitNode(n.getElseExpression(), context)); } private CAstNode visit(PostfixExpression n, WalkContext context) { CAstOperator op = (n.getOperator() == PostfixExpression.Operator.DECREMENT) ? CAstOperator.OP_SUB : CAstOperator.OP_ADD; return makeNode(context, fFactory, n, CAstNode.ASSIGN_POST_OP, visitNode(n.getOperand(), context), fFactory.makeConstant(1), op); } private CAstNode visit(PrefixExpression n, WalkContext context) { PrefixExpression.Operator op = n.getOperator(); if (op == PrefixExpression.Operator.DECREMENT || op == PrefixExpression.Operator.INCREMENT) { CAstOperator castOp = (n.getOperator() == PrefixExpression.Operator.DECREMENT) ? CAstOperator.OP_SUB : CAstOperator.OP_ADD; return makeNode(context, fFactory, n, CAstNode.ASSIGN_PRE_OP, visitNode(n.getOperand(), context), fFactory.makeConstant(1), castOp); } else if (op == PrefixExpression.Operator.PLUS) { return visitNode(n.getOperand(), context); // drop useless unary plus operator } else if (op == PrefixExpression.Operator.MINUS) { CAstNode zero; if (JDT2CAstUtils.isLongOrLess(n.getOperand().resolveTypeBinding())) zero = fFactory.makeConstant(0L); else zero = fFactory.makeConstant(0.0); return makeNode(context, fFactory, n, CAstNode.BINARY_EXPR, CAstOperator.OP_SUB, zero, visitNode(n.getOperand(), context)); } else { // ! and ~ CAstOperator castOp = (n.getOperator() == PrefixExpression.Operator.NOT) ? CAstOperator.OP_NOT : CAstOperator.OP_BITNOT; return makeNode(context, fFactory, n, CAstNode.UNARY_EXPR, castOp, visitNode(n.getOperand(), context)); } } private CAstNode visit(EmptyStatement n, WalkContext context) { CAstNode result = makeNode(context, fFactory, n, CAstNode.EMPTY); context.cfg().map(n, result); // why is this necessary? for break / continue targets? (they use an empty statement) return result; } private CAstNode visit(AssertStatement n, WalkContext context) { return makeNode(context, fFactory, n, CAstNode.ASSERT, visitNode(n.getExpression(), context)); } // //////////////// // LOOPS -- special handling of for and continue // //////////////// private CAstNode visit(LabeledStatement n, WalkContext context) { // find the first non-block statement ant set-up the label map (useful for breaking many fors) ASTNode stmt = n.getBody(); while (stmt instanceof Block) stmt = (ASTNode) ((Block) stmt).statements().iterator().next(); if (n.getParent() != null) // if not a synthetic node from a break/continue -- don't pollute namespace with label, we get it thru the context context.getLabelMap().put(stmt, n.getLabel().getIdentifier()); CAstNode result; if (!(n.getBody() instanceof EmptyStatement)) { ASTNode breakTarget = makeBreakOrContinueTarget(n, n.getLabel().getIdentifier()); CAstNode breakNode = visitNode(breakTarget, context); WalkContext child = new BreakContext(context, n.getLabel().getIdentifier(), breakTarget); result = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.LABEL_STMT, fFactory .makeConstant(n.getLabel().getIdentifier()), visitNode(n.getBody(), child)), breakNode); } else { result = makeNode(context, fFactory, n, CAstNode.LABEL_STMT, fFactory.makeConstant(n.getLabel().getIdentifier()), visitNode(n .getBody(), context)); } context.cfg().map(n, result); if (n.getParent() != null) // if not a synthetic node from a break/continue -- don't pollute namespace with label, we get it thru the context context.getLabelMap().remove(stmt); return result; } /** * Make a fake labeled node with no body, as an anchor to go to */ private ASTNode makeBreakOrContinueTarget(ASTNode loop, String name) { LabeledStatement labeled = ast.newLabeledStatement(); labeled.setBody(ast.newEmptyStatement()); labeled.setSourceRange(loop.getStartPosition(), loop.getLength()); labeled.setLabel(ast.newSimpleName(name)); // we don't have to worry about namespace conflicts as it is only // definedwithin return labeled; } private CAstNode visit(BreakStatement n, WalkContext context) { String label = n.getLabel() == null ? null : n.getLabel().getIdentifier(); ASTNode target = context.getBreakFor(label); assert target != null; CAstNode result = makeNode(context, fFactory, n, CAstNode.GOTO); context.cfg().map(n, result); context.cfg().add(n, target, null); return result; } private CAstNode visit(ContinueStatement n, WalkContext context) { String label = n.getLabel() == null ? null : n.getLabel().getIdentifier(); ASTNode target = context.getContinueFor(label); assert target != null; CAstNode result = makeNode(context, fFactory, n, CAstNode.GOTO); context.cfg().map(n, result); context.cfg().add(n, target, null); return result; } private CAstNode visit(WhileStatement n, WalkContext context) { Expression cond = n.getExpression(); Statement body = n.getBody(); ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition()); CAstNode breakNode = visitNode(breakTarget, context); ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition()); CAstNode continueNode = visitNode(continueTarget, context); String loopLabel = (String) context.getLabelMap().get(n); LoopContext lc = new LoopContext(context, loopLabel, breakTarget, continueTarget); /* * The following loop is created sligtly differently than in jscore. It doesn't have a specific target for continue. */ return makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.LOOP, visitNode(cond, context), makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, visitNode(body, lc), continueNode)), breakNode); } private CAstNode getSwitchCaseConstant(SwitchCase n, WalkContext context) { // TODO: enums Expression expr = n.getExpression(); Object constant = (expr == null) ? new Integer(0) : expr.resolveConstantExpressionValue(); // default case label of // "0" (what polyglot // does). we also set // SWITCH_DEFAULT // somewhere else // polyglot converts all labels to longs. why? who knows... if (constant instanceof Character) constant = new Long(((Character) constant).charValue()); else if (constant instanceof Byte) constant = new Long(((Byte) constant).longValue()); else if (constant instanceof Integer) constant = new Long(((Integer) constant).longValue()); else if (constant instanceof Short) constant = new Long(((Short) constant).longValue()); if (constant != null) { return fFactory.makeConstant(constant); } else if (expr instanceof SimpleName) { // enum constant return visit((SimpleName) expr, context); } else { Assertions.UNREACHABLE("null constant for non-enum switch case!"); return null; } } private CAstNode visit(SwitchCase n, WalkContext context) { CAstNode label = makeNode(context, fFactory, n, CAstNode.LABEL_STMT, getSwitchCaseConstant(n, context)); context.cfg().map(n, label); return label; } private CAstNode visit(SwitchStatement n, WalkContext context) { ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition()); CAstNode breakAst = visitNode(breakTarget, context); String loopLabel = (String) context.getLabelMap().get(n); // set by labeled statement (if there is one before this // switch statement) WalkContext childContext = new BreakContext(context, loopLabel, breakTarget); Expression cond = n.getExpression(); List/* <Statement> */cases = n.statements(); // First compute the control flow edges for the various case labels for (int i = 0; i < cases.size(); i++) { Statement se = (Statement) cases.get(i); if (se instanceof SwitchCase) { SwitchCase c = (SwitchCase) se; if (c.isDefault()) context.cfg().add(n, c, CAstControlFlowMap.SWITCH_DEFAULT); else context.cfg().add(n, c, getSwitchCaseConstant(c, context)); // if we don't do this, we may not get a constant but a // block expression or something else } } ArrayList<CAstNode> caseNodes = new ArrayList<CAstNode>(); // polyglot bundles all statements in between two statements into a block. // this is temporary place to hold current bundle of nodes. ArrayList<CAstNode> currentBlock = new ArrayList<CAstNode>(); // Now produce the CAst representation for each case for (Object o : cases) { Statement s = (Statement) o; if (s instanceof SwitchCase) { if (!currentBlock.isEmpty()) { // bundle up statements before this case CAstNode stmtNodes[] = currentBlock.toArray(new CAstNode[currentBlock.size()]); // make position from start of first statement to end of last statement JdtPosition positionOfAll = makePosition(childContext.pos().getPosition(stmtNodes[0]).getFirstOffset(), childContext .pos().getPosition(stmtNodes[stmtNodes.length - 1]).getLastOffset()); caseNodes.add(makeNode(childContext, fFactory, positionOfAll, CAstNode.BLOCK_STMT, stmtNodes)); currentBlock.clear(); } caseNodes.add(visitNode(s, childContext)); } else { visitNodeOrNodes(s, childContext, currentBlock); } } if (!currentBlock.isEmpty()) { // bundle up statements before this case CAstNode stmtNodes[] = currentBlock.toArray(new CAstNode[currentBlock.size()]); // make position from start of first statement to end of last statement JdtPosition positionOfAll = makePosition(childContext.pos().getPosition(stmtNodes[0]).getFirstOffset(), childContext.pos() .getPosition(stmtNodes[stmtNodes.length - 1]).getLastOffset()); caseNodes.add(makeNode(childContext, fFactory, positionOfAll, CAstNode.BLOCK_STMT, stmtNodes)); } // Now produce the switch stmt itself CAstNode switchAst = makeNode(context, fFactory, n, CAstNode.SWITCH, visitNode(cond, context), makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, caseNodes.toArray(new CAstNode[caseNodes.size()]))); context.cfg().map(n, switchAst); // Finally, wrap the entire switch in a block so that we have a // well-defined place to 'break' to. return makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, switchAst, breakAst); } private CAstNode visit(DoStatement n, WalkContext context) { String loopLabel = context.getLabelMap().get(n); // set by visit(LabeledStatement) String token = loopLabel==null? "at_" + n.getStartPosition(): loopLabel; ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel_" + token); CAstNode breakNode = visitNode(breakTarget, context); ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel_" + token); CAstNode continueNode = visitNode(continueTarget, context); CAstNode loopTest = visitNode(n.getExpression(), context); WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget); CAstNode loopBody = visitNode(n.getBody(), loopContext); return doLoopTranslator.translateDoLoop(loopTest, loopBody, continueNode, breakNode, context); } /** * Expands the form: for ( [final] Type var: iterable ) { ... } Into something equivalent to: for ( Iterator iter = * iterable.iter(); iter.hasNext(); ) { [final] Type var = (Type) iter.next(); ... } Or, in the case of an array: for ( int idx = * 0; i < iterable.length; i++ ) { [final] Type var = iterable[idx]; ... } Except that the expression "iterable" is only evaluate * once (or is it?) * * @param n * @param context * @return */ private CAstNode visit(EnhancedForStatement n, WalkContext context) { if (n.getExpression().resolveTypeBinding().isArray()) return makeArrayEnhancedForLoop(n, context); else return makeIteratorEnhancedForLoop(n, context); } private CAstNode makeIteratorEnhancedForLoop(EnhancedForStatement n, WalkContext context) { // case 1: iterator CAstNode exprNode = visitNode(n.getExpression(), context); SingleVariableDeclaration svd = n.getParameter(); Statement body = n.getBody(); // expand into: // typical for loop: // { [inits]; while (cond) { [body]; [label continueTarget]; iters } [label breakTarget] // BLOCK(BLOCK(init1,init2,...),LOOP(cond,BLOCK(bodyblock,continuetarget,BLOCK(iter1,iter2,...))),breaktarget // in our case: // the only init is "Iterator iter = iterable.iter()" // cond is "iter.hasNext()" // bodyblock should be prepended with "[final] Type var = iter.next()" (put in the block that body belongs to) // iter is null // continuetarget and breaktarget are the same as in a regular for loop // BLOCK(iterassign,LOOP(cond,BLOCK(paramassign,bodyblock,continuetarget)),breaktarget) final String tmpName = "iter tmp"; // this is an illegal Java identifier, we will use this variable to hold the "invisible" // iterator /*-------- make "iter = iterator.iter()" ---------*/ // make a fake method ref MethodReference iterMethodRef = fIdentityMapper.fakeMethodRefNoArgs("Ljava/lang/Iterable;.iterator()Ljava/util/Iterator<TT;>;", "Ljava/lang/Iterable", "iterator", "Ljava/util/Iterator"); CAstNode iterCallSiteRef = fFactory.makeConstant(CallSiteReference .make(0, iterMethodRef, IInvokeInstruction.Dispatch.INTERFACE)); // Iterable.iter() throws no exceptions. CAstNode iterCallNode = makeNode(context, fFactory, n, CAstNode.CALL, exprNode, iterCallSiteRef); // handle runtimeexception Object o1 = new Object(); // dummy object used for mapping / exceptions for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType)) context.cfg().add(o1, catchTarget.snd, catchTarget.fst); context.cfg().map(o1, iterCallNode); // TODO: this might not work, lots of calls in this one statement. CAstNode iterAssignNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol( tmpName, fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("int")), true)), iterCallNode); // MATCHUP: wrap in a block iterAssignNode = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iterAssignNode); // TODO: TOTEST: using this and Iterable.hasNext() explicitly in same file. /*---------- cond: iter.hasNext(); -----------*/ MethodReference hasNextMethodRef = fIdentityMapper.fakeMethodRefNoArgs("Ljava/util/Iterator;.hasNext()Z", "Ljava/util/Iterator", "hasNext", "Z"); CAstNode iterVar = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName)); CAstNode hasNextCallSiteRef = fFactory.makeConstant(CallSiteReference.make(0, hasNextMethodRef, IInvokeInstruction.Dispatch.INTERFACE)); // throws no exceptions. CAstNode hasNextCallNode = makeNode(context, fFactory, n, CAstNode.CALL, iterVar, hasNextCallSiteRef); // handle runtimeexception Object o2 = new Object(); // dummy object used for mapping / exceptions for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType)) context.cfg().add(o2, catchTarget.snd, catchTarget.fst); context.cfg().map(o2, hasNextCallNode); // TODO: this might not work, lots of calls in this one statement. /*---------- paramassign: var = (Type) iter.next() ---------*/ MethodReference nextMethodRef = fIdentityMapper.fakeMethodRefNoArgs("Ljava/util/Iterator;.next()TE;", "Ljava/util/Iterator", "next", "Ljava/lang/Object"); CAstNode nextCallSiteRef = fFactory.makeConstant(CallSiteReference .make(0, nextMethodRef, IInvokeInstruction.Dispatch.INTERFACE)); // throws no exceptions. CAstNode iterVar2 = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName)); CAstNode nextCallNode = makeNode(context, fFactory, n, CAstNode.CALL, iterVar2, nextCallSiteRef); for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType)) context.cfg().add(svd, catchTarget.snd, catchTarget.fst); context.cfg().map(svd, nextCallNode); // TODO: another cfg edge associated with svd! is this okay? prolly not... associate it with the cast, somehow... CAstNode castedNode = createCast(svd, nextCallNode, ast.resolveWellKnownType("java.lang.Object"), svd.resolveBinding() .getType(), context); Object defaultValue = JDT2CAstUtils.defaultValueForType(svd.resolveBinding().getType()); CAstNode nextAssignNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(svd .getName().getIdentifier(), fTypeDict.getCAstTypeFor(svd.resolveBinding().getType()), (svd.getModifiers() & Modifier.FINAL) != 0, defaultValue)), castedNode); /*----------- put it all together ----------*/ ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition()); ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition()); String loopLabel = context.getLabelMap().get(n); WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget); // LOCAL_SCOPE(BLOCK(iterassign,LOOP(cond,BLOCK(BLOCK(paramassign,bodyblock),continuetarget,BLOCK())),breaktarget)) return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iterAssignNode, makeNode(context, fFactory, n, CAstNode.LOOP, hasNextCallNode, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, nextAssignNode, visitNode(body, loopContext))), visitNode(continueTarget, context), makeNode(context, fFactory, n, CAstNode.BLOCK_STMT))), visitNode(breakTarget, context))); } private CAstNode makeArrayEnhancedForLoop(EnhancedForStatement n, WalkContext context) { // ********* BEFORE: // for ( String x: doSomething() ) { ... } // ********* AFTER: // { // String tmparray[] = doSomething(); // for ( int tmpindex = 0; i < tmparray.length; tmpindex++ ) { // String x = tmparray[tmpindex]; // ... // } // } // simplest: // LOCAL_SCOPE(BLOCK(arrayDecl,indexDecl,LOOP(cond,BLOCK(nextAssign,bodyblock,continuetarget,iter)),breaktarget)) // match up exactly: // LOCAL_SCOPE(BLOCK(arrayDecl,LOCAL_SCOPE(BLOCK(BLOCK(indexDecl),LOOP(cond,BLOCK(LOCAL_SCOPE(BLOCK(nextAssign,bodyblock)),continuetarget,BLOCK(iter))),breaktarget)))) /*------ arrayDecl --------- String tmparray[] = doSomething() ------*/ final String tmpArrayName = "for temp array"; // illegal java identifier CAstNode exprNode = visitNode(n.getExpression(), context); CAstNode arrayDeclNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol( tmpArrayName, fTypeDict.getCAstTypeFor(n.getExpression().resolveTypeBinding()), true)), exprNode); /*------ indexDecl --------- int tmpindex = 0 ------*/ final String tmpIndexName = "for temp index"; CAstNode indexDeclNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol( tmpIndexName, fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("int")), true)), fFactory.makeConstant(new Integer(0))); /*------ cond ------------- tmpindex < tmparray.length ------*/ CAstNode tmpArrayLengthNode = makeNode(context, fFactory, n, CAstNode.ARRAY_LENGTH, makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpArrayName), fFactory.makeConstant(fTypeDict.getCAstTypeFor(n.getExpression().resolveTypeBinding())))); CAstNode condNode = makeNode(context, fFactory, n, CAstNode.BINARY_EXPR, CAstOperator.OP_LT, makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpIndexName), fFactory.makeConstant(JavaPrimitiveType.INT)), tmpArrayLengthNode); /*------ tmpIndexInc -------- tmpindex++ ------*/ CAstNode tmpArrayIncNode = makeNode(context, fFactory, n, CAstNode.ASSIGN_POST_OP, makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpIndexName)), fFactory.makeConstant(1), CAstOperator.OP_ADD); /*------ tmpArrayAccess ----- String x = tmparray[tmpindex] ------*/ CAstNode tmpArrayAccessNode = makeNode(context, fFactory, n, CAstNode.ARRAY_REF, makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpArrayName)), fFactory.makeConstant(fIdentityMapper.getTypeRef(n.getExpression() .resolveTypeBinding().getComponentType())), makeNode(context, fFactory, n, CAstNode.VAR, fFactory .makeConstant(tmpIndexName))); SingleVariableDeclaration svd = n.getParameter(); Object defaultValue = JDT2CAstUtils.defaultValueForType(svd.resolveBinding().getType()); CAstNode nextAssignNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(svd .getName().getIdentifier(), fTypeDict.getCAstTypeFor(n.getExpression() .resolveTypeBinding().getComponentType()), (svd.getModifiers() & Modifier.FINAL) != 0, defaultValue)), tmpArrayAccessNode); // LOCAL_SCOPE(BLOCK(arrayDecl,LOCAL_SCOPE(BLOCK(BLOCK(indexDecl),LOOP(cond,BLOCK(LOCAL_SCOPE(BLOCK(nextAssign,bodyblock)),continuetarget,BLOCK(iter))),breaktarget)))) // more complicated than it has to be, but it matches up exactly with the Java expansion above. ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition()); ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition()); String loopLabel = (String) context.getLabelMap().get(n); WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget); return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, arrayDeclNode, makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, indexDeclNode), makeNode(context, fFactory, n, CAstNode.LOOP, condNode, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, nextAssignNode, visitNode(n.getBody(), loopContext))), visitNode(continueTarget, context), makeNode( context, fFactory, n, CAstNode.BLOCK_STMT, tmpArrayIncNode))), visitNode(breakTarget, context))))); } private CAstNode visit(ForStatement n, WalkContext context) { ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition()); ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition()); String loopLabel = (String) context.getLabelMap().get(n); WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget); ArrayList<CAstNode> inits = new ArrayList<CAstNode>(); for (int i = 0; i < n.initializers().size(); i++) { ASTNode init = (ASTNode) n.initializers().get(i); if (init instanceof VariableDeclarationExpression) { for (Object o : ((VariableDeclarationExpression) init).fragments()) inits.add(visitNode((ASTNode) o, context)); } else inits.add(visitNode(init, context)); } CAstNode[] iters = new CAstNode[n.updaters().size()]; for (int i = 0; i < iters.length; i++) iters[i] = visitNode((ASTNode) n.updaters().get(i), context); CAstNode initsBlock = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, inits.toArray(new CAstNode[inits.size()])); CAstNode itersBlock = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iters); // { [inits]; while (cond) { [body]; [label continueTarget]; iters } [label breakTarget] return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, initsBlock, makeNode(context, fFactory, n, CAstNode.LOOP, visitNode(n.getExpression(), context), makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, visitNode(n.getBody(), loopContext), visitNode(continueTarget, context), itersBlock)), visitNode( breakTarget, context))); } private CAstNode visit(TryStatement n, WalkContext context) { List/* <CatchClause> */catchBlocks = n.catchClauses(); Block finallyBlock = n.getFinally(); Block tryBlock = n.getBody(); // try/finally if (catchBlocks.isEmpty()) { return makeNode(context, fFactory, n, CAstNode.UNWIND, visitNode(tryBlock, context), visitNode(finallyBlock, context)); // try/catch/[finally] } else { TryCatchContext tc = new TryCatchContext(context, n); CAstNode tryNode = visitNode(tryBlock, tc); for (Iterator iter = catchBlocks.iterator(); iter.hasNext();) { tryNode = makeNode(context, fFactory, n, CAstNode.TRY, tryNode, visitNode((CatchClause) iter.next(), context)); } // try/catch if (finallyBlock == null) { return tryNode; // try/catch/finally } else { return makeNode(context, fFactory, n, CAstNode.UNWIND, tryNode, visitNode(finallyBlock, context)); } } } private CAstNode visit(CatchClause n, WalkContext context) { Block body = n.getBody(); SingleVariableDeclaration formal = n.getException(); CAstNode excDecl = makeNode(context, fFactory, n, CAstNode.CATCH, fFactory.makeConstant(formal.getName().getIdentifier()), visitNode(body, context)); CAstNode localScope = makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, excDecl); context.cfg().map(n, excDecl); context.getNodeTypeMap().add(excDecl, fTypeDict.getCAstTypeFor(n.getException().resolveBinding().getType())); return localScope; } private CAstNode visit(ThrowStatement n, WalkContext context) { CAstNode result = makeNode(context, fFactory, n, CAstNode.THROW, visitNode(n.getExpression(), context)); ITypeBinding label = n.getExpression().resolveTypeBinding(); context.cfg().map(n, result); Collection/* <Pair<Type,Node>> */catchNodes = context.getCatchTargets(label); for (Iterator iter = catchNodes.iterator(); iter.hasNext();) { Pair/* <Type,Node> */catchNode = (Pair/* <Type,Node> */) iter.next(); context.cfg().add(n, catchNode.snd, catchNode.fst); } return result; } private void hookUpNPETargets(ASTNode n, WalkContext wc) { Collection excTargets = wc.getCatchTargets(fNullPointerExcType); if (!excTargets.isEmpty()) { // connect NPE exception edge to relevant catch targets // (presumably only one) for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) { Pair catchPair = (Pair) iterator.next(); wc.cfg().add(n, catchPair.snd, fNullPointerExcType); } } else { // connect exception edge to exit wc.cfg().add(n, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNullPointerExcType); } } // // ARRAYS // private CAstNode visit(ArrayAccess n, WalkContext context) { TypeReference eltTypeRef = fIdentityMapper.getTypeRef(n.resolveTypeBinding()); CAstNode cast = makeNode(context, fFactory, n, CAstNode.ARRAY_REF, visitNode(n.getArray(), context), fFactory.makeConstant(eltTypeRef), visitNode(n.getIndex(), context)); hookUpNPETargets(n, context); context.cfg().map(n, cast); return cast; } // FIXME: inner classes here, probably too... private CAstNode visit(ArrayCreation n, WalkContext context) { ITypeBinding newType = n.resolveTypeBinding(); ArrayInitializer ai = n.getInitializer(); assert newType.isArray(); if (ai != null) { return visitNode(ai, context); } else { TypeReference arrayTypeRef = fIdentityMapper.getTypeRef(newType); List/* <Expression> */dims = n.dimensions(); CAstNode[] args = new CAstNode[dims.size() + 1]; int idx = 0; args[idx++] = fFactory.makeConstant(arrayTypeRef); for (Iterator iter = dims.iterator(); iter.hasNext();) { Expression dimExpr = (Expression) iter.next(); args[idx++] = visitNode(dimExpr, context); } return makeNode(context, fFactory, n, CAstNode.NEW, args); } } private CAstNode visit(SynchronizedStatement n, WalkContext context) { CAstNode exprNode = visitNode(n.getExpression(), context); String exprName = fFactory.makeUnique(); CAstNode declStmt = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory .makeConstant(new CAstSymbolImpl(exprName, fTypeDict.getCAstTypeFor(n.getExpression().resolveTypeBinding()), true)), exprNode); CAstNode monitorEnterNode = makeNode(context, fFactory, n, CAstNode.MONITOR_ENTER, makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(exprName))); context.cfg().map(monitorEnterNode, monitorEnterNode); for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fNullPointerExcType)) context.cfg().add(monitorEnterNode, catchTarget.snd, catchTarget.fst); CAstNode bodyNodes = visitNode(n.getBody(), context); CAstNode monitorExitNode = makeNode(context, fFactory, n, CAstNode.MONITOR_EXIT, makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(exprName))); context.cfg().map(monitorExitNode, monitorExitNode); for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fNullPointerExcType)) context.cfg().add(monitorExitNode, catchTarget.snd, catchTarget.fst); CAstNode tryBody = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, monitorEnterNode, bodyNodes); CAstNode bigBody = makeNode(context, fFactory, n, CAstNode.UNWIND, tryBody, monitorExitNode); return makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, declStmt, bigBody); } // /////////////////////////////////////////// // / THE GIANT SWITCH STATEMENT ( BORING ) /// // /////////////////////////////////////////// /** * Giant switch statement. * * @param n * @return */ private CAstEntity visit(AbstractTypeDeclaration n, WalkContext context) { // handling of compilationunit in translate() if (n instanceof TypeDeclaration) { return visitTypeDecl(n, context); } else if (n instanceof EnumDeclaration) { return visit((EnumDeclaration) n, context); } else if (n instanceof AnnotationTypeDeclaration) { return visitTypeDecl(n, context); } else { Assertions.UNREACHABLE("Unhandled type declaration type"); return null; } } /** * Giant switch statement, part deux * * @param body * @param context * @return */ private CAstNode visitNode(ASTNode n, WalkContext context) { if (n == null) return makeNode(context, fFactory, null, CAstNode.EMPTY); if (n instanceof ArrayAccess) { return visit((ArrayAccess) n, context); } else if (n instanceof ArrayCreation) { return visit((ArrayCreation) n, context); } else if (n instanceof ArrayInitializer) { return visit((ArrayInitializer) n, context); } else if (n instanceof AssertStatement) { return visit((AssertStatement) n, context); } else if (n instanceof Assignment) { return visit((Assignment) n, context); } else if (n instanceof Block) { return visit((Block) n, context); } else if (n instanceof BooleanLiteral) { return visit((BooleanLiteral) n, context); } else if (n instanceof BreakStatement) { return visit((BreakStatement) n, context); } else if (n instanceof CastExpression) { return visit((CastExpression) n, context); } else if (n instanceof CatchClause) { return visit((CatchClause) n, context); } else if (n instanceof CharacterLiteral) { return visit((CharacterLiteral) n, context); } else if (n instanceof ClassInstanceCreation) { return visit((ClassInstanceCreation) n, context); } else if (n instanceof ConditionalExpression) { return visit((ConditionalExpression) n, context); } else if (n instanceof ConstructorInvocation) { return visit((ConstructorInvocation) n, context); } else if (n instanceof ContinueStatement) { return visit((ContinueStatement) n, context); } else if (n instanceof DoStatement) { return visit((DoStatement) n, context); } else if (n instanceof EmptyStatement) { return visit((EmptyStatement) n, context); } else if (n instanceof EnhancedForStatement) { return visit((EnhancedForStatement) n, context); } else if (n instanceof ExpressionStatement) { return visit((ExpressionStatement) n, context); } else if (n instanceof FieldAccess) { return visit((FieldAccess) n, context); } else if (n instanceof ForStatement) { return visit((ForStatement) n, context); } else if (n instanceof IfStatement) { return visit((IfStatement) n, context); } else if (n instanceof InfixExpression) { return visit((InfixExpression) n, context); } else if (n instanceof InstanceofExpression) { return visit((InstanceofExpression) n, context); } else if (n instanceof LabeledStatement) { return visit((LabeledStatement) n, context); } else if (n instanceof MethodInvocation) { return visit((MethodInvocation) n, context); } else if (n instanceof NumberLiteral) { return visit((NumberLiteral) n, context); } else if (n instanceof NullLiteral) { return visit((NullLiteral) n, context); } else if (n instanceof ParenthesizedExpression) { return visit((ParenthesizedExpression) n, context); } else if (n instanceof PostfixExpression) { return visit((PostfixExpression) n, context); } else if (n instanceof PrefixExpression) { return visit((PrefixExpression) n, context); } else if (n instanceof QualifiedName) { return visit((QualifiedName) n, context); } else if (n instanceof ReturnStatement) { return visit((ReturnStatement) n, context); } else if (n instanceof SimpleName) { return visit((SimpleName) n, context); } else if (n instanceof StringLiteral) { return visit((StringLiteral) n, context); } else if (n instanceof SuperConstructorInvocation) { return visit((SuperConstructorInvocation) n, context); } else if (n instanceof SuperFieldAccess) { return visit((SuperFieldAccess) n, context); } else if (n instanceof SuperMethodInvocation) { return visit((SuperMethodInvocation) n, context); } else if (n instanceof SynchronizedStatement) { return visit((SynchronizedStatement) n, context); } else if (n instanceof SwitchStatement) { return visit((SwitchStatement) n, context); } else if (n instanceof SwitchCase) { return visit((SwitchCase) n, context); } else if (n instanceof ThisExpression) { return visit((ThisExpression) n, context); } else if (n instanceof TypeLiteral) { return visit((TypeLiteral) n, context); } else if (n instanceof ThrowStatement) { return visit((ThrowStatement) n, context); } else if (n instanceof TryStatement) { return visit((TryStatement) n, context); } else if (n instanceof TypeDeclarationStatement) { return visit((TypeDeclarationStatement) n, context); } else if (n instanceof VariableDeclarationFragment) { return visit((VariableDeclarationFragment) n, context); } else if (n instanceof WhileStatement) { return visit((WhileStatement) n, context); } // VariableDeclarationStatement handled as special case (returns multiple statements) Assertions.UNREACHABLE("Unhandled JDT node type " + n.getClass().getCanonicalName()); return null; } private void visitNodeOrNodes(ASTNode n, WalkContext context, Collection<CAstNode> coll) { if (n instanceof VariableDeclarationStatement) coll.addAll(visit((VariableDeclarationStatement) n, context)); else coll.add(visitNode(n, context)); } // ///////////////////////////////////////// // SPECIALIZED CASTENTITYs AND CASTNODEs // // ///////////////////////////////////////// protected static final class CompilationUnitEntity implements CAstEntity { private final String fName; private final Collection<CAstEntity> fTopLevelDecls; public CompilationUnitEntity(PackageDeclaration packageDeclaration, List<CAstEntity> topLevelDecls) { fName = (packageDeclaration == null) ? "" : packageDeclaration.getName().getFullyQualifiedName().replace('.', '/'); fTopLevelDecls = topLevelDecls; } @Override public Collection<CAstAnnotation> getAnnotations() { return null; } @Override public int getKind() { return FILE_ENTITY; } @Override public String getName() { return fName; } @Override public String getSignature() { Assertions.UNREACHABLE(); return null; } @Override public String[] getArgumentNames() { return new String[0]; } @Override public CAstNode[] getArgumentDefaults() { return new CAstNode[0]; } @Override public int getArgumentCount() { return 0; } @Override public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() { return Collections.singletonMap(null, fTopLevelDecls); } @Override public Iterator getScopedEntities(CAstNode construct) { Assertions.UNREACHABLE("CompilationUnitEntity asked for AST-related entities, but it has no AST."); return null; } @Override public CAstNode getAST() { return null; } @Override public CAstControlFlowMap getControlFlow() { Assertions.UNREACHABLE("CompilationUnitEntity.getControlFlow()"); return null; } @Override public CAstSourcePositionMap getSourceMap() { Assertions.UNREACHABLE("CompilationUnitEntity.getSourceMap()"); return null; } @Override public CAstSourcePositionMap.Position getPosition() { return null; } @Override public CAstNodeTypeMap getNodeTypeMap() { Assertions.UNREACHABLE("CompilationUnitEntity.getNodeTypeMap()"); return null; } @Override public Collection getQualifiers() { return Collections.EMPTY_LIST; } @Override public CAstType getType() { Assertions.UNREACHABLE("CompilationUnitEntity.getType()"); return null; } } // ///////////////////////////// // WALK CONTEXTS // WHY???? // //////////////////////////////// /** * Contains things needed by in the visit() of some nodes to process the nodes. For example, pos() contains the source position * mapping which each node registers */ public static interface WalkContext extends TranslatorToCAst.WalkContext<WalkContext, ASTNode> { public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type); public Map<ASTNode, String> getLabelMap(); public boolean needLValue(); } /** * Default context functions. When one context doesn't handle something, it the next one up does. For example, there is only one * source pos. mapping per MethodContext, so loop contexts delegate it up. */ public static class DelegatingContext extends TranslatorToCAst.DelegatingContext<WalkContext, ASTNode> implements WalkContext { public DelegatingContext(WalkContext parent) { super(parent); } @Override public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type) { return parent.getCatchTargets(type); } @Override public Map<ASTNode, String> getLabelMap() { return parent.getLabelMap(); } @Override public boolean needLValue() { return parent.needLValue(); } } /* * Root context. Doesn't do anything. */ public static class RootContext extends TranslatorToCAst.RootContext<WalkContext, ASTNode> implements WalkContext { @Override public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type) { Assertions.UNREACHABLE("RootContext.getCatchTargets()"); return null; } @Override public Map<ASTNode, String> getLabelMap() { Assertions.UNREACHABLE("RootContext.getLabelMap()"); return null; } @Override public boolean needLValue() { Assertions.UNREACHABLE("Rootcontext.needLValue()"); return false; } } private class AssignmentContext extends DelegatingContext { protected AssignmentContext(WalkContext parent) { super(parent); } @Override public boolean needLValue() { return true; } } private static class TryCatchContext extends DelegatingContext { Collection<Pair<ITypeBinding, Object>> fCatchNodes = new ArrayList<Pair<ITypeBinding, Object>>(); TryCatchContext(WalkContext parent, TryStatement tryNode) { super(parent); for (Iterator catchIter = tryNode.catchClauses().iterator(); catchIter.hasNext();) { CatchClause c = (CatchClause) catchIter.next(); Pair<ITypeBinding, Object> p = Pair.make(c.getException().resolveBinding().getType(), (Object) c); fCatchNodes.add(p); } } @Override public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding label) { // Look for all matching targets for this thrown type: // if supertpe match, then return only matches at this catch // if subtype match, then matches here and parent matches Collection<Pair<ITypeBinding, Object>> catchNodes = new ArrayList<Pair<ITypeBinding, Object>>(); for (Iterator<Pair<ITypeBinding, Object>> iter = fCatchNodes.iterator(); iter.hasNext();) { Pair<ITypeBinding, Object> p = (Pair<ITypeBinding, Object>) iter.next(); ITypeBinding catchType = (ITypeBinding) p.fst; // catchType here should NEVER be FakeExceptionTypeBinary, because these can only be thrown (not caught) by // "1/0", implicit null pointer exceptions, etc. assert !(catchNodes instanceof FakeExceptionTypeBinding) : "catchNodes instanceof FakeExceptionTypeBinary!"; if (label.isSubTypeCompatible(catchType) || label.isEqualTo(catchType)) { catchNodes.add(p); return catchNodes; // _might_ get caught } else if (catchType.isSubTypeCompatible(label)) { catchNodes.add(p); continue; } } catchNodes.addAll(parent.getCatchTargets(label)); return catchNodes; } } private class BreakContext extends DelegatingContext { protected final String label; private final ASTNode breakTo; BreakContext(WalkContext parent, String label, ASTNode breakTo) { super(parent); this.label = label; this.breakTo = breakTo; } @Override public ASTNode getBreakFor(String label) { return (label == null || label.equals(this.label)) ? breakTo : super.getBreakFor(label); } } private class LoopContext extends BreakContext { private final ASTNode continueTo; protected LoopContext(WalkContext parent, String label, ASTNode breakTo, ASTNode continueTo) { super(parent, label, breakTo); this.continueTo = continueTo; } @Override public ASTNode getContinueFor(String label) { return (label == null || label.equals(this.label)) ? continueTo : super.getContinueFor(label); } } public class MethodContext extends DelegatingContext { private final Map<CAstNode, CAstEntity> fEntities; private final Map<ASTNode, String> labelMap = HashMapFactory.make(2); public MethodContext(WalkContext parent, Map<CAstNode, CAstEntity> entities) { // constructor did take: pd.procedureInstance(), memberEntities, context super(parent); fEntities = entities; } @Override public Map<ASTNode, String> getLabelMap() { return labelMap; // labels are kept within a method. } final CAstSourcePositionRecorder fSourceMap = new CAstSourcePositionRecorder(); final CAstControlFlowRecorder fCFG = new CAstControlFlowRecorder(fSourceMap); final CAstNodeTypeMapRecorder fNodeTypeMap = new CAstNodeTypeMapRecorder(); @Override public CAstControlFlowRecorder cfg() { return fCFG; } @Override public void addScopedEntity(CAstNode node, CAstEntity entity) { fEntities.put(node, entity); } @Override public CAstSourcePositionRecorder pos() { return fSourceMap; } @Override public CAstNodeTypeMapRecorder getNodeTypeMap() { return fNodeTypeMap; } @Override public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding label) { // TAGALONG (need fRuntimeExcType) // Why do we seemingly catch a RuntimeException in every method? this won't catch the RuntimeException above where // it is supposed to be caught? Collection<Pair<ITypeBinding, Object>> result = Collections.singleton(Pair.<ITypeBinding, Object> make(fRuntimeExcType, CAstControlFlowMap.EXCEPTION_TO_EXIT)); return result; } @Override public boolean needLValue() { return false; } } // //////////////////////////////////// // MAKE NODE VARIATIONS & POSITIONS (BORING) // maybe moved to different file. // makeNode() simply calls Ast.makeNode() and sets the position for the node // //////////////////////////////////// protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind) { CAstNode cn = Ast.makeNode(kind); setPos(wc, cn, n); return cn; } protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c[]) { CAstNode cn = Ast.makeNode(kind, c); setPos(wc, cn, n); return cn; } protected CAstNode makeNode(WalkContext wc, CAst Ast, JdtPosition pos, int kind, CAstNode c[]) { CAstNode cn = Ast.makeNode(kind, c); wc.pos().setPosition(cn, pos); return cn; } protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c1, CAstNode c2) { CAstNode cn = Ast.makeNode(kind, c1, c2); setPos(wc, cn, n); return cn; } protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c) { CAstNode cn = Ast.makeNode(kind, c); setPos(wc, cn, n); return cn; } protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c1, CAstNode c2, CAstNode c3) { CAstNode cn = Ast.makeNode(kind, c1, c2, c3); setPos(wc, cn, n); return cn; } protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c1, CAstNode c2, CAstNode c3, CAstNode c4) { CAstNode cn = Ast.makeNode(kind, c1, c2, c3, c4); setPos(wc, cn, n); return cn; } protected CAstNode makeNode(WalkContext wc, CAst Ast, JdtPosition pos, int kind, CAstNode c1, CAstNode c2, CAstNode c3) { CAstNode cn = Ast.makeNode(kind, c1, c2, c3); wc.pos().setPosition(cn, pos); return cn; } protected void setPos(WalkContext wc, CAstNode cn, ASTNode jdtNode) { if (jdtNode != null) wc.pos().setPosition(cn, makePosition(jdtNode)); } public JdtPosition makePosition(ASTNode n) { return makePosition(n.getStartPosition(), n.getStartPosition() + n.getLength()); } public JdtPosition makePosition(int start, int end) { return new JdtPosition(start, end, cu.getLineNumber(start), cu.getLineNumber(end), sourceFile, fullPath); } // ///////////////////////////////////////////////////////////////// // // ENUM TRANSFORMATION ////////////////////////////////////////// // ///////////////////////////////////////////////////////////////// private static final ArrayList<CAstQualifier> enumQuals = new ArrayList<CAstQualifier>(3); static { enumQuals.add(CAstQualifier.PUBLIC); enumQuals.add(CAstQualifier.STATIC); enumQuals.add(CAstQualifier.FINAL); } /** * Only called from createClassDeclaration. * * @param decl * @param context * @return */ private CAstEntity visit(EnumConstantDeclaration decl, WalkContext context) { return new FieldEntity(decl.getName().getIdentifier(), decl.resolveVariable().getType(), enumQuals, makePosition(decl .getStartPosition(), decl.getStartPosition() + decl.getLength()), null); } /** * Called only from visitFieldInitNode(node,context) */ private CAstNode createEnumConstantDeclarationInit(EnumConstantDeclaration node, WalkContext context) { String hiddenVariableName = (String) node.getProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclName"); if (hiddenVariableName == null) { FieldReference fieldRef = fIdentityMapper.getFieldRef(node.resolveVariable()); // We use null to indicate an OBJECT_REF to a static field CAstNode lhsNode = makeNode(context, fFactory, node, CAstNode.OBJECT_REF, makeNode(context, fFactory, null, CAstNode.VOID), fFactory.makeConstant(fieldRef)); // CONSTRUCT ARGUMENTS & "new MyEnum(...)" statement ArrayList<Object> arguments = new ArrayList<Object>(); arguments.add(fFactory.makeConstant(node.getName().getIdentifier())); // name of constant arguments.add(fFactory.makeConstant(node.resolveVariable().getVariableId())); // id arguments.addAll(node.arguments()); CAstNode rhsNode = createClassInstanceCreation(node, arguments, node.resolveConstructorBinding(), null, node .getAnonymousClassDeclaration(), context); CAstNode assNode = makeNode(context, fFactory, node, CAstNode.ASSIGN, lhsNode, rhsNode); return assNode; // their naming, not mine } else { // String[] x = (new Direction[] { // NORTH, EAST, SOUTH, WEST, $VALUES, $VALUES$ // }); return null; } } private CAstEntity createEnumValueOfMethod(ITypeBinding enumType, WalkContext oldContext) { IMethodBinding met = null, superMet = null; // find our valueOf(String) for (IMethodBinding m : enumType.getDeclaredMethods()) if (m.getName().equals("valueOf") && m.getParameterTypes().length == 1 && m.getParameterTypes()[0].isEqualTo(ast.resolveWellKnownType("java.lang.String"))) met = m; // find Enum.valueOf(Class, String) for (IMethodBinding m : enumType.getSuperclass().getTypeDeclaration().getDeclaredMethods()) if (m.getName().equals("valueOf") && m.getParameterTypes().length == 2) superMet = m; assert met != null && superMet != null : "Couldn't find enum values() function in JDT bindings!"; Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>(); final MethodContext context = new MethodContext(oldContext, memberEntities); MethodDeclaration fakeMet = ast.newMethodDeclaration(); fakeMet.setName(ast.newSimpleName("valueOf")); fakeMet.setSourceRange(-1, 0); fakeMet.setBody(ast.newBlock()); SingleVariableDeclaration stringS = ast.newSingleVariableDeclaration(); stringS.setName(ast.newSimpleName("s")); fakeMet.parameters().add(stringS); // TODO: probably uses reflection so isn't very useful for analyses. Is there something more useful we could put in here? // return (MyEnum)Enum.valueOf(MyEnum.class, s); // cast(call(type_literal, var))) CAstNode typeLit = makeNode(context, fFactory, fakeMet, CAstNode.TYPE_LITERAL_EXPR, fFactory.makeConstant(fIdentityMapper .typeToTypeID(enumType))); CAstNode stringSvar = makeNode(context, fFactory, fakeMet, CAstNode.VAR, fFactory.makeConstant("s"), fFactory.makeConstant(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String")))); ArrayList<Object> args = new ArrayList<Object>(); args.add(typeLit); args.add(stringSvar); CAstNode call = createMethodInvocation(fakeMet, superMet, makeNode(context, fFactory, fakeMet, CAstNode.VOID), args, context); CAstNode cast = createCast(fakeMet, call, enumType, superMet.getReturnType(), context); CAstNode bodyNode = makeNode(context, fFactory, fakeMet, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, fakeMet, CAstNode.BLOCK_STMT, makeNode(context, fFactory, fakeMet, CAstNode.RETURN, cast))); ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(1); paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String"))); return new ProcedureEntity(bodyNode, fakeMet, enumType, memberEntities, context, paramTypes, enumType, met.getModifiers(), handleAnnotations(met)); } private CAstEntity createEnumValuesMethod(ITypeBinding enumType, ArrayList<IVariableBinding> constants, WalkContext oldContext) { IMethodBinding met = null; for (IMethodBinding m : enumType.getDeclaredMethods()) if (m.getName().equals("values") && m.getParameterTypes().length == 0) met = m; assert met != null : "Couldn't find enum values() function in JDT bindings!"; Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>(); final MethodContext context = new MethodContext(oldContext, memberEntities); MethodDeclaration fakeMet = ast.newMethodDeclaration(); fakeMet.setName(ast.newSimpleName("values")); fakeMet.setSourceRange(-1, 0); fakeMet.setBody(ast.newBlock()); // make enum constant values array: new MyEnum() { MYENUMCST1, MYENUMCST2, ... } CAstNode[] eltNodes = new CAstNode[constants.size() + 1]; int idx = 0; TypeReference arrayTypeRef = fIdentityMapper.getTypeRef(enumType.createArrayType(1)); eltNodes[idx++] = makeNode(context, fFactory, fakeMet, CAstNode.NEW, fFactory.makeConstant(arrayTypeRef), fFactory .makeConstant(constants.size())); for (IVariableBinding cst : constants) eltNodes[idx++] = createFieldAccess(makeNode(context, fFactory, fakeMet, CAstNode.VOID), cst.getName(), cst, fakeMet, context); CAstNode bodyNode = makeNode(context, fFactory, fakeMet, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, fakeMet, CAstNode.BLOCK_STMT, makeNode(context, fFactory, fakeMet, CAstNode.RETURN, makeNode(context, fFactory, fakeMet, CAstNode.ARRAY_LITERAL, eltNodes)))); ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(0); return new ProcedureEntity(bodyNode, fakeMet, enumType, memberEntities, context, paramTypes, enumType.createArrayType(1), met .getModifiers(), handleAnnotations(enumType)); } private void doEnumHiddenEntities(ITypeBinding typeBinding, ArrayList<ASTNode> staticInits, List<CAstEntity> memberEntities, WalkContext context) { // PART I: create a $VALUES field // collect constants // ArrayList<String> constants = new ArrayList<String>(); // for ( ASTNode n: staticInits ) // if ( n instanceof EnumConstantDeclaration ) // constants.add(((EnumConstantDeclaration)n).getName().getIdentifier()); // figure out a suitable untaken name // String hiddenFieldName = "hidden values field"; // illegal name // // public static final MyEnum[] $VALUES; // memberEntities.add(new FieldEntity(hiddenFieldName, // typeBinding.createArrayType(1), enumQuals, // makePosition(-1,-1))); // // EnumConstantDeclaration fakeValuesDecl = ast.newEnumConstantDeclaration(); // // pass along values that we will use in createEnumConstantDeclarationInit() in creating static initializer // fakeValuesDecl.setProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclName", hiddenFieldName); // fakeValuesDecl.setProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclConstants", constants); // staticInits.add(fakeValuesDecl); ArrayList<IVariableBinding> constants = new ArrayList<IVariableBinding>(); for (IVariableBinding var : typeBinding.getDeclaredFields()) if (var.isEnumConstant()) constants.add(var); // constants are unsorted by default Collections.sort(constants, new Comparator<IVariableBinding>() { @Override public int compare(IVariableBinding arg0, IVariableBinding arg1) { return arg0.getVariableId() - arg1.getVariableId(); } }); // PART II: create values() memberEntities.add(createEnumValuesMethod(typeBinding, constants, context)); // PART III: create valueOf() memberEntities.add(createEnumValueOfMethod(typeBinding, context)); } private CAstEntity visit(EnumDeclaration n, WalkContext context) { // JDT contains correct type info / class / subclass info for the enum return createClassDeclaration(n, n.bodyDeclarations(), n.enumConstants(), n.resolveBinding(), n.getName().getIdentifier(), n .resolveBinding().getModifiers(), false, false, context); } /** * @param n for positioning. */ private CAstEntity createEnumConstructorWithParameters(IMethodBinding ctor, ASTNode n, WalkContext oldContext, ArrayList<ASTNode> inits, MethodDeclaration nonDefaultCtor) { // PART I: find super ctor to call ITypeBinding newType = ctor.getDeclaringClass(); ITypeBinding javalangenumType = newType.getSuperclass(); IMethodBinding superCtor = null; if (newType.isEnum()) { for (IMethodBinding met : javalangenumType.getDeclaredMethods()) if (met.isConstructor()) { superCtor = met; break; } } assert superCtor != null : "enum"; // PART II: make ctor with simply "super(a,b,c...)" // TODO: extra CAstNodes final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>(); final MethodContext context = new MethodContext(oldContext, memberEntities); MethodDeclaration fakeCtor = ast.newMethodDeclaration(); fakeCtor.setConstructor(true); fakeCtor.setSourceRange(n.getStartPosition(), n.getLength()); fakeCtor.setBody(ast.newBlock()); // PART IIa: make a fake JDT constructor method with the proper number of args // Make fake args that will be passed String[] fakeArguments = new String[3 + ctor.getParameterTypes().length]; if (nonDefaultCtor == null) { for (int i = 3; i < fakeArguments.length; i++) fakeArguments[i] = "__wala_jdtcast_argument" + i; // this is in the case of an anonymous class with parameters, eg NORTH in // the following example: public enum A { NORTH("south") { ...} A(String // s){} } } else { for (int i = 3; i < fakeArguments.length; i++) fakeArguments[i] = ((SingleVariableDeclaration) nonDefaultCtor.parameters().get(i - 3)).getName().getIdentifier(); } ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(superCtor.getParameterTypes().length); fakeArguments[0] = "this"; fakeArguments[1] = "__wala_jdtcast_argument1"; // TODO FIXME: change to invalid name in the case that nonDefaultCtor != null fakeArguments[2] = "__wala_jdtcast_argument2"; // otherwise there will be conflicts if we name our variable // __wala_jdtcast_argument1!!! for (int i = 1; i < fakeArguments.length; i++) { // the name SingleVariableDeclaration svd = ast.newSingleVariableDeclaration(); svd.setName(ast.newSimpleName(fakeArguments[i])); fakeCtor.parameters().add(svd); // the type if (i == 1) paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String"))); else if (i == 2) paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("int"))); else paramTypes.add(fTypeDict.getCAstTypeFor(ctor.getParameterTypes()[i - 3])); } // PART IIb: create the statements in the constructor // one super() call plus the inits CAstNode[] bodyNodes; if (nonDefaultCtor == null) bodyNodes = new CAstNode[inits.size() + 1]; else bodyNodes = new CAstNode[inits.size() + 2]; // make super(...) call // this, call ref, args CAstNode[] children; if (ctor.isDefaultConstructor()) children = new CAstNode[4 + ctor.getParameterTypes().length]; // anonymous class' implicit constructors call constructors with // more than standard two enum args else children = new CAstNode[4]; // explicit constructor children[0] = makeNode(context, fFactory, n, CAstNode.SUPER); CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(superCtor), IInvokeInstruction.Dispatch.SPECIAL); children[1] = fFactory.makeConstant(callSiteRef); children[2] = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[1]), fFactory.makeConstant(paramTypes.get(0))); children[3] = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[2]), fFactory.makeConstant(paramTypes.get(1))); if (ctor.isDefaultConstructor()) for (int i = 0; i < ctor.getParameterTypes().length; i++) children[i + 4] = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[i + 3]), fFactory.makeConstant(paramTypes.get(i+2))); bodyNodes[0] = makeNode(context, fFactory, n, CAstNode.CALL, children); // QUESTION: no handleExceptions? for (int i = 0; i < inits.size(); i++) bodyNodes[i + 1] = visitFieldInitNode(inits.get(i), context); if (nonDefaultCtor != null) bodyNodes[bodyNodes.length - 1] = visitNode(nonDefaultCtor.getBody(), context); // finally, make the procedure entity CAstNode ast = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes); return new ProcedureEntity(ast, fakeCtor, newType, memberEntities, context, paramTypes, null, handleAnnotations(ctor)); } }