/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.devtools.j2objc.translate; import com.google.common.collect.Sets; import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration; import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.BodyDeclaration; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.ConstructorInvocation; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.ExpressionStatement; import com.google.devtools.j2objc.ast.FieldAccess; import com.google.devtools.j2objc.ast.FunctionDeclaration; import com.google.devtools.j2objc.ast.FunctionInvocation; import com.google.devtools.j2objc.ast.MethodDeclaration; import com.google.devtools.j2objc.ast.MethodInvocation; import com.google.devtools.j2objc.ast.NativeStatement; import com.google.devtools.j2objc.ast.NormalAnnotation; import com.google.devtools.j2objc.ast.QualifiedName; import com.google.devtools.j2objc.ast.ReturnStatement; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.SingleMemberAnnotation; import com.google.devtools.j2objc.ast.SingleVariableDeclaration; import com.google.devtools.j2objc.ast.Statement; import com.google.devtools.j2objc.ast.SuperConstructorInvocation; import com.google.devtools.j2objc.ast.SuperFieldAccess; import com.google.devtools.j2objc.ast.SuperMethodInvocation; import com.google.devtools.j2objc.ast.ThisExpression; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TreeVisitor; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.types.FunctionElement; import com.google.devtools.j2objc.types.GeneratedExecutableElement; import com.google.devtools.j2objc.types.GeneratedVariableElement; import com.google.devtools.j2objc.util.CaptureInfo; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.ErrorUtil; import com.google.devtools.j2objc.util.NameTable; import com.google.devtools.j2objc.util.TypeUtil; import com.google.devtools.j2objc.util.UnicodeUtils; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** * Converts methods that don't need dynamic dispatch to C functions. This optimization * initially just targets private methods, but will be expanded to include final methods * that don't override superclass methods. * * @author Tom Ball */ public class Functionizer extends UnitTreeVisitor { private final CaptureInfo captureInfo; private Set<ExecutableElement> functionizableMethods; public Functionizer(CompilationUnit unit) { super(unit); captureInfo = unit.getEnv().captureInfo(); } @Override public boolean visit(CompilationUnit node) { functionizableMethods = determineFunctionizableMethods(node); return true; } /** * Determines the set of methods to functionize. In addition to a method being * final we must also find an invocation for that method. Static methods, though, * are always functionized since there are no dynamic dispatch issues. */ private Set<ExecutableElement> determineFunctionizableMethods(final CompilationUnit unit) { final Set<ExecutableElement> functionizableDeclarations = Sets.newHashSet(); final Set<ExecutableElement> invocations = Sets.newHashSet(); unit.accept(new TreeVisitor() { @Override public void endVisit(MethodDeclaration node) { if (canFunctionize(node)) { functionizableDeclarations.add(node.getExecutableElement()); } } @Override public void endVisit(MethodInvocation node) { invocations.add(node.getExecutableElement()); } }); return Sets.intersection(functionizableDeclarations, invocations); } @Override public boolean visit(AnnotationTypeDeclaration node) { return false; } @Override public boolean visit(NormalAnnotation node) { return false; } @Override public boolean visit(SingleMemberAnnotation node) { return false; } /** * Determines whether an instance method can be functionized. */ private boolean canFunctionize(MethodDeclaration node) { ExecutableElement m = node.getExecutableElement(); int modifiers = node.getModifiers(); // Never functionize these types of methods. if (Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers) || !node.hasDeclaration() || ElementUtil.isAnnotationMember(m)) { return false; } // Don't functionize equals/hash, since they are often called by collections. String name = ElementUtil.getName(m); if ((name.equals("hashCode") && m.getParameters().isEmpty()) || (name.equals("equals") && m.getParameters().size() == 1)) { return false; } if (!ElementUtil.isPrivate(m) && !ElementUtil.isFinal(m)) { return false; } return !hasSuperMethodInvocation(node); } private static boolean hasSuperMethodInvocation(MethodDeclaration node) { final boolean[] result = new boolean[1]; result[0] = false; node.accept(new TreeVisitor() { @Override public void endVisit(SuperMethodInvocation node) { result[0] = true; } }); return result[0]; } private FunctionElement newFunctionElement(ExecutableElement method) { TypeElement declaringClass = ElementUtil.getDeclaringClass(method); FunctionElement element = new FunctionElement( nameTable.getFullFunctionName(method), method.getReturnType(), declaringClass); if (ElementUtil.isConstructor(method) || !ElementUtil.isStatic(method)) { element.addParameters(declaringClass.asType()); } transferParams(method, element, declaringClass); return element; } private FunctionElement newAllocatingConstructorElement(ExecutableElement method) { TypeElement declaringClass = ElementUtil.getDeclaringClass(method); FunctionElement element = new FunctionElement( nameTable.getReleasingConstructorName(method), nameTable.getAllocatingConstructorName(method), declaringClass.asType(), declaringClass); transferParams(method, element, declaringClass); return element; } private void transferParams( ExecutableElement method, FunctionElement function, TypeElement declaringClass) { if (ElementUtil.isConstructor(method)) { function.addParameters(ElementUtil.asTypes( captureInfo.getImplicitPrefixParams(declaringClass))); } function.addParameters(ElementUtil.asTypes(method.getParameters())); if (ElementUtil.isConstructor(method)) { function.addParameters(ElementUtil.asTypes( captureInfo.getImplicitPostfixParams(declaringClass))); } } @Override public void endVisit(MethodInvocation node) { ExecutableElement element = node.getExecutableElement(); if (!ElementUtil.isStatic(element) && !functionizableMethods.contains(element)) { return; } FunctionInvocation functionInvocation = new FunctionInvocation(newFunctionElement(element), node.getTypeMirror()); List<Expression> args = functionInvocation.getArguments(); TreeUtil.moveList(node.getArguments(), args); if (!ElementUtil.isStatic(element)) { Expression expr = node.getExpression(); if (expr == null) { expr = new ThisExpression(TreeUtil.getEnclosingTypeElement(node).asType()); } args.add(0, TreeUtil.remove(expr)); } node.replaceWith(functionInvocation); } @Override public void endVisit(SuperMethodInvocation node) { ExecutableElement element = node.getExecutableElement(); // Yes, super method invocations can be static. if (!ElementUtil.isStatic(element)) { return; } FunctionInvocation functionInvocation = new FunctionInvocation(newFunctionElement(element), node.getTypeMirror()); TreeUtil.moveList(node.getArguments(), functionInvocation.getArguments()); node.replaceWith(functionInvocation); } @Override public void endVisit(SuperConstructorInvocation node) { ExecutableElement element = node.getExecutableElement(); AbstractTypeDeclaration typeDecl = TreeUtil.getEnclosingType(node); TypeElement type = typeDecl.getTypeElement(); FunctionElement funcElement = newFunctionElement(element); FunctionInvocation invocation = new FunctionInvocation(funcElement, typeUtil.getVoid()); List<Expression> args = invocation.getArguments(); args.add(new ThisExpression(ElementUtil.getDeclaringClass(element).asType())); if (typeDecl instanceof TypeDeclaration) { TypeDeclaration typeDeclaration = (TypeDeclaration) typeDecl; if (captureInfo.needsOuterParam(ElementUtil.getSuperclass(type))) { Expression outerArg = TreeUtil.remove(node.getExpression()); args.add(outerArg != null ? outerArg : typeDeclaration.getSuperOuter().copy()); } TreeUtil.moveList(typeDeclaration.getSuperCaptureArgs(), args); } TreeUtil.moveList(node.getArguments(), args); if (ElementUtil.isEnum(type)) { for (VariableElement param : captureInfo.getImplicitEnumParams()) { args.add(new SimpleName(param)); } } node.replaceWith(new ExpressionStatement(invocation)); assert funcElement.getParameterTypes().size() == args.size(); } @Override public void endVisit(ConstructorInvocation node) { ExecutableElement element = node.getExecutableElement(); TypeElement declaringClass = ElementUtil.getDeclaringClass(element); FunctionElement funcElement = newFunctionElement(element); FunctionInvocation invocation = new FunctionInvocation(funcElement, typeUtil.getVoid()); List<Expression> args = invocation.getArguments(); args.add(new ThisExpression(declaringClass.asType())); for (VariableElement captureParam : captureInfo.getImplicitPrefixParams(declaringClass)) { args.add(new SimpleName(captureParam)); } TreeUtil.moveList(node.getArguments(), args); for (VariableElement captureParam : captureInfo.getImplicitPostfixParams(declaringClass)) { args.add(new SimpleName(captureParam)); } node.replaceWith(new ExpressionStatement(invocation)); assert funcElement.getParameterTypes().size() == args.size(); } @Override public void endVisit(ClassInstanceCreation node) { ExecutableElement element = node.getExecutableElement(); TypeElement type = ElementUtil.getDeclaringClass(element); FunctionElement funcElement = newAllocatingConstructorElement(element); FunctionInvocation invocation = new FunctionInvocation(funcElement, node.getTypeMirror()); invocation.setHasRetainedResult(node.hasRetainedResult() || options.useARC()); List<Expression> args = invocation.getArguments(); Expression outerExpr = node.getExpression(); if (outerExpr != null) { args.add(TreeUtil.remove(outerExpr)); } else if (captureInfo.needsOuterParam(type)) { args.add(new ThisExpression(ElementUtil.getDeclaringClass(type).asType())); } Expression superOuterArg = node.getSuperOuterArg(); if (superOuterArg != null) { args.add(TreeUtil.remove(superOuterArg)); } TreeUtil.moveList(node.getCaptureArgs(), args); TreeUtil.moveList(node.getArguments(), args); node.replaceWith(invocation); assert funcElement.getParameterTypes().size() == args.size(); } @Override public void endVisit(MethodDeclaration node) { ExecutableElement element = node.getExecutableElement(); // Don't functionize certain ObjC methods like dealloc or __annotations, since // they are added by the translator and need to remain in method form. if (!node.hasDeclaration()) { return; } boolean isConstructor = ElementUtil.isConstructor(element); boolean isInstanceMethod = !ElementUtil.isStatic(element) && !isConstructor; boolean isDefaultMethod = ElementUtil.isDefault(element); List<BodyDeclaration> declarationList = TreeUtil.asDeclarationSublist(node); if (!isInstanceMethod || isDefaultMethod || Modifier.isNative(node.getModifiers()) || functionizableMethods.contains(element)) { TypeElement declaringClass = ElementUtil.getDeclaringClass(element); boolean isEnumConstructor = isConstructor && ElementUtil.isEnum(declaringClass); if (isConstructor) { addImplicitParameters(node, declaringClass); } FunctionDeclaration function = makeFunction(node); declarationList.add(function); if (isConstructor && !ElementUtil.isAbstract(declaringClass) && !isEnumConstructor) { declarationList.add(makeAllocatingConstructor(node, false)); declarationList.add(makeAllocatingConstructor(node, true)); } else if (isEnumConstructor && options.useARC()) { // Enums with ARC need the retaining constructor. declarationList.add(makeAllocatingConstructor(node, false)); } // Instance methods must be kept in case they are invoked using "super". boolean keepMethod = isInstanceMethod // Public methods must be kept for the public API. || !(ElementUtil.isPrivateInnerType(declaringClass) || ElementUtil.isPrivate(element)) // Methods must be kept for reflection if enabled. || (translationUtil.needsReflection(declaringClass) && !isEnumConstructor); if (keepMethod) { if (isDefaultMethod) { // For default methods keep only the declaration. Implementing classes will add a shim. node.setBody(null); node.addModifiers(Modifier.ABSTRACT); } else { setFunctionCaller(node, element); } } else { node.remove(); } ErrorUtil.functionizedMethod(); } } private void addImplicitParameters(MethodDeclaration node, TypeElement type) { List<SingleVariableDeclaration> methodParams = node.getParameters().subList(0, 0); for (VariableElement param : captureInfo.getImplicitPrefixParams(type)) { methodParams.add(new SingleVariableDeclaration(param)); } methodParams = node.getParameters(); for (VariableElement param : captureInfo.getImplicitPostfixParams(type)) { methodParams.add(new SingleVariableDeclaration(param)); } } /** * Create an equivalent function declaration for a given method. */ private FunctionDeclaration makeFunction(MethodDeclaration method) { ExecutableElement elem = method.getExecutableElement(); TypeElement declaringClass = ElementUtil.getDeclaringClass(elem); boolean isInstanceMethod = !ElementUtil.isStatic(elem) && !ElementUtil.isConstructor(elem); FunctionDeclaration function = new FunctionDeclaration(nameTable.getFullFunctionName(elem), elem.getReturnType()); function.setJniSignature(signatureGenerator.createJniFunctionSignature(elem)); function.setLineNumber(method.getLineNumber()); if (!ElementUtil.isStatic(elem)) { VariableElement var = GeneratedVariableElement.newParameter( NameTable.SELF_NAME, declaringClass.asType(), null); function.addParameter(new SingleVariableDeclaration(var)); } TreeUtil.copyList(method.getParameters(), function.getParameters()); function.setModifiers(method.getModifiers() & Modifier.STATIC); if (ElementUtil.isPrivate(elem) || (isInstanceMethod && !ElementUtil.isDefault(elem))) { function.addModifiers(Modifier.PRIVATE); } else { function.addModifiers(Modifier.PUBLIC); } if (Modifier.isNative(method.getModifiers())) { function.addModifiers(Modifier.NATIVE); return function; } function.setBody(TreeUtil.remove(method.getBody())); if (ElementUtil.isStatic(elem)) { // Add class initialization invocation, since this may be the first use of this class. String initName = UnicodeUtils.format("%s_initialize", nameTable.getFullName(declaringClass)); TypeMirror voidType = typeUtil.getVoid(); FunctionElement initElement = new FunctionElement(initName, voidType, declaringClass); FunctionInvocation initCall = new FunctionInvocation(initElement, voidType); function.getBody().addStatement(0, new ExpressionStatement(initCall)); } else { FunctionConverter.convert(function); } return function; } /** * Create a wrapper for a constructor that does the object allocation. */ private FunctionDeclaration makeAllocatingConstructor( MethodDeclaration method, boolean releasing) { assert method.isConstructor(); ExecutableElement element = method.getExecutableElement(); TypeElement declaringClass = ElementUtil.getDeclaringClass(element); String name = releasing ? nameTable.getReleasingConstructorName(element) : nameTable.getAllocatingConstructorName(element); FunctionDeclaration function = new FunctionDeclaration(name, declaringClass.asType()); function.setLineNumber(method.getLineNumber()); function.setModifiers(ElementUtil.isPrivate(element) ? Modifier.PRIVATE : Modifier.PUBLIC); function.setReturnsRetained(!releasing); TreeUtil.copyList(method.getParameters(), function.getParameters()); Block body = new Block(); function.setBody(body); StringBuilder sb = new StringBuilder(releasing ? "J2OBJC_CREATE_IMPL(" : "J2OBJC_NEW_IMPL("); sb.append(nameTable.getFullName(declaringClass)); sb.append(", ").append(nameTable.getFunctionName(element)); for (SingleVariableDeclaration param : function.getParameters()) { sb.append(", ").append(nameTable.getVariableQualifiedName(param.getVariableElement())); } sb.append(")"); body.addStatement(new NativeStatement(sb.toString())); return function; } /** * Replace method block statements with single statement that invokes function. */ private void setFunctionCaller(MethodDeclaration method, ExecutableElement methodElement) { TypeMirror returnType = methodElement.getReturnType(); TypeElement declaringClass = ElementUtil.getDeclaringClass(methodElement); Block body = new Block(); method.setBody(body); method.removeModifiers(Modifier.NATIVE); List<Statement> stmts = body.getStatements(); FunctionInvocation invocation = new FunctionInvocation(newFunctionElement(methodElement), returnType); List<Expression> args = invocation.getArguments(); if (!ElementUtil.isStatic(methodElement)) { args.add(new ThisExpression(declaringClass.asType())); } for (SingleVariableDeclaration param : method.getParameters()) { args.add(new SimpleName(param.getVariableElement())); } if (TypeUtil.isVoid(returnType)) { stmts.add(new ExpressionStatement(invocation)); if (ElementUtil.isConstructor(methodElement)) { stmts.add(new ReturnStatement(new ThisExpression(declaringClass.asType()))); } } else { stmts.add(new ReturnStatement(invocation)); } } @Override public void endVisit(TypeDeclaration node) { if (!node.isInterface() && options.disallowInheritedConstructors()) { addDisallowedConstructors(node); } } /** * Declare any inherited constructors that aren't allowed to be accessed in Java * with a NS_UNAVAILABLE macro, so that clang will flag such access from native * code as an error. */ private void addDisallowedConstructors(TypeDeclaration node) { TypeElement typeElement = node.getTypeElement(); TypeElement superClass = ElementUtil.getSuperclass(typeElement); if (ElementUtil.isPrivateInnerType(typeElement) || ElementUtil.isAbstract(typeElement) || superClass == null) { return; } Set<String> constructors = new HashSet<>(); for (ExecutableElement constructor : ElementUtil.getConstructors(typeElement)) { constructors.add(nameTable.getMethodSelector(constructor)); } Map<String, ExecutableElement> inheritedConstructors = new HashMap<>(); // Add super constructors that have unique parameter lists. for (ExecutableElement superC : ElementUtil.getConstructors(superClass)) { if (ElementUtil.isPrivate(superC)) { // Skip private super constructors since they're already unavailable. continue; } String selector = nameTable.getMethodSelector(superC); if (!constructors.contains(selector)) { inheritedConstructors.put(selector, superC); } } for (Map.Entry<String, ExecutableElement> entry : inheritedConstructors.entrySet()) { ExecutableElement oldConstructor = entry.getValue(); GeneratedExecutableElement newConstructor = GeneratedExecutableElement.newConstructorWithSelector( entry.getKey(), typeElement, typeUtil); MethodDeclaration decl = new MethodDeclaration(newConstructor).setUnavailable(true); decl.addModifiers(Modifier.ABSTRACT); int count = 0; for (VariableElement param : oldConstructor.getParameters()) { VariableElement newParam = GeneratedVariableElement.newParameter( "arg" + count++, param.asType(), newConstructor); newConstructor.addParameter(newParam); decl.addParameter(new SingleVariableDeclaration(newParam)); } addImplicitParameters(decl, ElementUtil.getDeclaringClass(oldConstructor)); node.addBodyDeclaration(decl); } } /** * Convert references to "this" in the function to a "self" parameter. */ private static class FunctionConverter extends TreeVisitor { private final VariableElement selfParam; static void convert(FunctionDeclaration function) { function.accept(new FunctionConverter(function.getParameter(0).getVariableElement())); } private FunctionConverter(VariableElement selfParam) { this.selfParam = selfParam; } @Override public boolean visit(FieldAccess node) { node.getExpression().accept(this); return false; } @Override public boolean visit(QualifiedName node) { node.getQualifier().accept(this); return false; } @Override public void endVisit(SimpleName node) { VariableElement var = TreeUtil.getVariableElement(node); if (var != null && var.getKind().isField()) { // Convert name to self->name. node.replaceWith(new QualifiedName(var, node.getTypeMirror(), new SimpleName(selfParam))); } } @Override public boolean visit(SuperFieldAccess node) { // Change super.field expression to self.field. SimpleName qualifier = new SimpleName(selfParam); node.replaceWith(new FieldAccess(node.getVariableElement(), node.getTypeMirror(), qualifier)); return false; } @Override public void endVisit(ThisExpression node) { SimpleName self = new SimpleName(selfParam); node.replaceWith(self); } @Override public void endVisit(SuperMethodInvocation node) { // Super invocations won't work from a function. Setting the receiver // will cause SuperMethodInvocationRewriter to rewrite this invocation. if (node.getReceiver() == null) { node.setReceiver(new SimpleName(selfParam)); } } } }