blob: 169de6434ca21cfe86617ace28f3f90e8d7bdef0 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.android.jack.ir.impl;
import com.google.common.annotations.VisibleForTesting;
import com.android.jack.Jack;
import com.android.jack.ir.StringInterner;
import com.android.jack.ir.ast.JAnnotationMethod;
import com.android.jack.ir.ast.JClass;
import com.android.jack.ir.ast.JConstructor;
import com.android.jack.ir.ast.JDefinedClass;
import com.android.jack.ir.ast.JDefinedClassOrInterface;
import com.android.jack.ir.ast.JDefinedEnum;
import com.android.jack.ir.ast.JEnumField;
import com.android.jack.ir.ast.JField;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodId;
import com.android.jack.ir.ast.JMethodIdWide;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JParameter;
import com.android.jack.ir.ast.JRetentionPolicy;
import com.android.jack.ir.ast.JType;
import com.android.jack.ir.ast.JTypeLookupException;
import com.android.jack.ir.ast.JVariable;
import com.android.jack.ir.ast.MethodKind;
import com.android.jack.ir.ast.MissingJTypeLookupException;
import com.android.jack.ir.ast.marker.GenericSignature;
import com.android.jack.ir.ast.marker.ThrownExceptionMarker;
import com.android.jack.ir.formatter.TypePackageAndMethodFormatter;
import com.android.jack.ir.sourceinfo.SourceInfo;
import com.android.jack.ir.sourceinfo.SourceInfoFactory;
import com.android.jack.lookup.JLookup;
import com.android.jack.lookup.JNodeLookup;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.IntersectionTypeBinding18;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
/**
* Creates unresolved references to types, fields, and methods.
*/
public class ReferenceMapper {
@Nonnull
private final List<String> argNames = new ArrayList<String>();
@Nonnull
private final Map<SignatureKey, JField> fields = new HashMap<SignatureKey, JField>();
@Nonnull
private final Map<SignatureKey, JMethod> methods = new HashMap<SignatureKey, JMethod>();
@Nonnull
private static final StringInterner stringInterner = StringInterner.get();
@Nonnull
private final TypePackageAndMethodFormatter lookupFormater;
@CheckForNull
private JDefinedClass javaLangString;
@Nonnull
private final JNodeLookup lookup;
@Nonnull
private final LookupEnvironment lookupEnvironment;
@Nonnull
private final SourceInfoFactory sourceInfoFactory;
public ReferenceMapper(@Nonnull JNodeLookup lookup,
@Nonnull LookupEnvironment lookupEnvironment, @Nonnull SourceInfoFactory sourceInfoFactory) {
this.lookup = lookup;
this.lookupEnvironment = lookupEnvironment;
this.sourceInfoFactory = sourceInfoFactory;
this.lookupFormater = Jack.getLookupFormatter();
}
@Nonnull
public LookupEnvironment getLookupEnvironment() {
return lookupEnvironment;
}
@Nonnull
public SourceInfoFactory getSourceInfoFactory() {
return sourceInfoFactory;
}
@Nonnull
public JLookup getLookup() {
return lookup;
}
@Nonnull
public JField get(@Nonnull FieldBinding binding) throws JTypeLookupException {
binding = binding.original();
SignatureKey key = new SignatureKey(binding);
JField field = fields.get(key);
if (field == null) {
// Call createField on FieldBinding having a declaring class that is not a SourceTypeBinding
// will result in NPE. If field is not already cached and the declaring class is a
// SourceTypeBinding, no need to search the field since it does not exists, thus create
// it automatically. In other cases, for instance with BinaryTypeBinding, fields can be
// created by the Jayce file loader, and the cache will not yet be filled, thus search
// the field and fill the cache.
if (binding.declaringClass instanceof SourceTypeBinding) {
field = createField(binding);
} else {
JDefinedClassOrInterface enclosingType =
(JDefinedClassOrInterface) get(binding.declaringClass);
field = findField(binding, enclosingType);
assert field != null;
}
cacheField(key, field);
}
return field;
}
@Nonnull
public JMethod get(@Nonnull MethodBinding binding) throws JTypeLookupException {
binding = binding.original();
SignatureKey key = new SignatureKey(binding);
JMethod method = methods.get(key);
if (method == null) {
if (binding.declaringClass instanceof SourceTypeBinding) {
method = createMethod(binding);
} else {
JDefinedClassOrInterface enclosingType =
(JDefinedClassOrInterface) get(binding.declaringClass);
method = findMethod(binding, enclosingType);
if (method == null) {
// because synthetic bindings are not automatically loaded but may be requested by
// createSyntheticMethodFromBinding
assert binding instanceof SyntheticMethodBinding;
method = createMethod(binding);
}
}
cacheMethod(key, method);
}
return method;
}
@Nonnull
public List<JType> getBounds(@Nonnull IntersectionTypeBinding18 binding)
throws JTypeLookupException {
List<JType> bounds = new ArrayList<JType>(binding.intersectingTypes.length);
for (ReferenceBinding refBinding : binding.intersectingTypes) {
bounds.add(get(refBinding));
}
return bounds;
}
@Nonnull
public JType get(@Nonnull TypeBinding binding) throws JTypeLookupException {
binding = binding.erasure();
assert !(binding instanceof IntersectionTypeBinding18);
return get(new String(binding.signature()));
}
@Nonnull
static String intern(@Nonnull char[] cs) {
return intern(String.valueOf(cs));
}
@Nonnull
static String intern(@Nonnull String s) {
return stringInterner.intern(s);
}
@Nonnull
public JType get(@Nonnull String signature) throws JTypeLookupException {
return lookup.getType(signature);
}
void setField(@Nonnull FieldBinding binding, @Nonnull JField field) {
cacheField(new SignatureKey(binding), field);
}
@Nonnull
private JMethod createMethod(@Nonnull MethodBinding b) throws JTypeLookupException {
AbstractMethodDeclaration declaration = getDeclaration(b);
CudInfo cuInfo;
SourceInfo info;
if (declaration != null) {
cuInfo = new CudInfo(declaration.scope.referenceCompilationUnit());
b = declaration.binding;
info = makeSourceInfo(cuInfo, declaration, sourceInfoFactory);
} else {
cuInfo = null;
info = SourceInfo.UNKNOWN;
}
ReferenceBinding declaringClass = (ReferenceBinding) b.declaringClass.erasure();
Set<String> alreadyNamedVariables = new HashSet<String>();
JDefinedClassOrInterface enclosingType = (JDefinedClassOrInterface) get(declaringClass);
JMethod method;
boolean isNested = JackIrBuilder.isNested(declaringClass);
int flags = b.getAccessFlags();
// No need to add the extra 'default' modifier into Jack
flags = flags & ~ExtraCompilerModifiers.AccDefaultMethod;
if (b.isDeprecated()) {
flags |= JModifier.DEPRECATED;
}
if (b.isConstructor()) {
method = new JConstructor(info, (JDefinedClass) enclosingType, flags);
if (declaringClass.isEnum()) {
// Enums have hidden arguments for name and value
createParameter(info, method, "enum$name",
lookupEnvironment.getType(TypeConstants.JAVA_LANG_STRING),
JModifier.FINAL | JModifier.SYNTHETIC);
createParameter(info, method, "enum$ordinal", TypeBinding.INT,
JModifier.FINAL | JModifier.SYNTHETIC);
}
// add synthetic args for outer this
if (isNested) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
createParameters(nestedBinding.enclosingInstances, info, method, alreadyNamedVariables);
}
} else {
JType returnType = get(b.returnType);
if (declaringClass.isAnnotationType()) {
method =
new JAnnotationMethod(info,
new JMethodId(
new JMethodIdWide(intern(b.selector), MethodKind.INSTANCE_VIRTUAL), returnType),
enclosingType,
ReferenceMapper.removeSynchronizedOnBridge(flags));
} else {
method = new JMethod(info, new JMethodId(new JMethodIdWide(intern(b.selector),
ReferenceMapper.getMethodKind(flags)), returnType),
enclosingType,
ReferenceMapper.removeSynchronizedOnBridge(flags));
}
}
// User args.
if (declaration != null) {
assert cuInfo != null;
createParameters(method, declaration, cuInfo);
} else {
mapParameters(info, method, b, 0);
}
if (b.isConstructor()) {
if (isNested) {
// add synthetic args for locals
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
// add synthetic args for outer this and locals
createParameters(nestedBinding.outerLocalVariables, info, method, alreadyNamedVariables);
}
}
mapExceptions(method, b);
if (b.isSynthetic()) {
method.setSynthetic();
}
enclosingType.addMethod(method);
char[] genSignature = b.genericSignature();
if (genSignature != null) {
method.addMarker(new GenericSignature(intern(genSignature)));
}
method.updateParents(enclosingType);
return method;
}
@CheckForNull
private AbstractMethodDeclaration getDeclaration(@Nonnull MethodBinding b) {
// Lambda method does not have declaration but they are not SyntheticMethodBinding, thus use
// also b.isSynthetic(). isSynthetic method can not always be used because some
// SyntheticMethodBinding does not have synthetic modifier, it is the case for valueOf of
// enumeration
if (b instanceof SyntheticMethodBinding || b.isSynthetic()) {
return null;
}
AbstractMethodDeclaration declaration = b.sourceMethod();
if (declaration == null) { // happens at least for clone() of arrays, see UpdatedMethodBinding
SourceTypeBinding sourceType = (SourceTypeBinding) b.declaringClass;
for (AbstractMethodDeclaration candidate : sourceType.scope.referenceContext.methods) {
if (CharOperation.equals(candidate.selector, b.selector)
&& CharOperation.equals(candidate.binding.signature(), b.signature())) {
declaration = candidate;
break;
}
}
assert declaration != null;
}
return declaration;
}
private void createParameters(@CheckForNull SyntheticArgumentBinding[] sab,
@Nonnull SourceInfo info, @Nonnull JMethod method,
@Nonnull Set<String> alreadyNamedVariables) {
if (sab != null) {
for (int i = 0; i < sab.length; ++i) {
SyntheticArgumentBinding arg = sab[i];
String argName = String.valueOf(arg.name);
if (alreadyNamedVariables.contains(argName)) {
argName += "_" + i;
}
createParameter(info, method, argName, arg.type, getModifier(arg));
alreadyNamedVariables.add(argName);
}
}
}
private void createParameters(@Nonnull JMethod method, @Nonnull AbstractMethodDeclaration x,
@Nonnull CudInfo cuInfo) throws JTypeLookupException {
if (x.arguments != null) {
for (Argument argument : x.arguments) {
SourceInfo info = makeSourceInfo(cuInfo, argument, sourceInfoFactory);
LocalVariableBinding binding = argument.binding;
createParameter(info, method, intern(binding.name), binding.type, getModifier(binding));
}
}
}
private int getModifier(@Nonnull LocalVariableBinding lvBinding) {
return lvBinding.isFinal() ? JModifier.FINAL : JModifier.DEFAULT;
}
@Nonnull
public JParameter createParameter(@Nonnull SourceInfo info, @Nonnull JMethod method,
@Nonnull String name, @Nonnull TypeBinding typeBinding, int modifier) {
return createParameter(info, method, name, typeBinding, modifier, method.getParams().size());
}
@Nonnull
public JParameter createParameter(@Nonnull SourceInfo info, @Nonnull JMethod method,
@Nonnull String name, @Nonnull TypeBinding typeBinding, int modifier, int paramIndex)
throws JTypeLookupException {
JType type = get(typeBinding);
JParameter param =
new JParameter(info, name, type, modifier, method);
method.getParams().add(paramIndex, param);
method.getMethodIdWide().getParamTypes().add(paramIndex, type);
addGenericSignatureMarker(typeBinding, param);
assert method.getParams().size() == method.getMethodIdWide().getParamTypes().size();
return param;
}
public void addGenericSignatureMarker(@Nonnull TypeBinding typeBinding,
@Nonnull JVariable variable) {
char[] genericTypeSignature = typeBinding.genericTypeSignature();
if (genericTypeSignature != null) {
char[] signature = typeBinding.signature();
char[] genericSignature = typeBinding.genericTypeSignature();
// Check if the generic signature really contains generic types i.e. is different from the
// non-generic signature
if (!CharOperation.equals(signature, genericSignature)) {
variable.addMarker(new GenericSignature(intern(genericSignature)));
}
}
}
@Nonnull
private JField createField(@Nonnull FieldBinding binding) throws JTypeLookupException {
FieldDeclaration sourceField = binding.sourceField();
CudInfo cuInfo =
new CudInfo(((SourceTypeBinding) binding.declaringClass).scope.referenceCompilationUnit());
SourceInfo info = makeSourceInfo(cuInfo, sourceField, sourceInfoFactory);
JType type = get(binding.type);
JDefinedClassOrInterface enclosingType = (JDefinedClassOrInterface) get(binding.declaringClass);
JField field;
if (sourceField.initialization != null
&& sourceField.initialization instanceof AllocationExpression
&& ((AllocationExpression) sourceField.initialization).enumConstant != null) {
field =
new JEnumField(info, intern(binding.name), binding.original().id,
(JDefinedEnum) enclosingType, (JDefinedClass) type);
} else {
int flags = binding.getAccessFlags();
if (binding.isDeprecated()) {
flags |= JModifier.DEPRECATED;
}
if (isCompileTimeConstant(binding)) {
flags |= JModifier.COMPILE_TIME_CONSTANT;
}
field = new JField(info, intern(binding.name), enclosingType, type, flags);
}
enclosingType.addField(field);
char [] genSignature = binding.genericSignature();
if (genSignature != null) {
field.addMarker(new GenericSignature(intern(genSignature)));
}
field.updateParents(enclosingType);
return field;
}
@Nonnull
static JRetentionPolicy getRetentionPolicy(long tagBits) {
JRetentionPolicy result;
long annotBits = tagBits & TagBits.AnnotationRetentionMASK;
if ((annotBits ^ TagBits.AnnotationSourceRetention) == 0) {
result = JRetentionPolicy.SOURCE;
} else if ((annotBits ^ TagBits.AnnotationRuntimeRetention) == 0) {
result = JRetentionPolicy.RUNTIME;
} else {
result = JRetentionPolicy.CLASS;
}
return result;
}
private void ensureArgNames(int required) {
for (int i = argNames.size(); i <= required; ++i) {
argNames.add(intern("arg" + i));
}
}
private void mapExceptions(JMethod method, MethodBinding binding) throws JTypeLookupException {
ReferenceBinding[] thrownExceptions = binding.thrownExceptions;
int length = thrownExceptions.length;
if (length != 0) {
List<JClass> thrownException = new ArrayList<JClass>(length);
for (ReferenceBinding thrownBinding : thrownExceptions) {
JDefinedClass type = (JDefinedClass) get(thrownBinding);
thrownException.add(type);
}
method.addMarker(new ThrownExceptionMarker(thrownException));
}
}
private int mapParameters(SourceInfo info, JMethod method, MethodBinding binding, int argPosition)
throws JTypeLookupException {
if (binding.parameters != null) {
ensureArgNames(argPosition + binding.parameters.length);
for (TypeBinding argType : binding.parameters) {
createParameter(info, method, argNames.get(argPosition++), argType, JModifier.DEFAULT);
}
}
return argPosition;
}
@Nonnull
private static String getTypeConstantPoolName(@Nonnull String typeName) {
assert typeName.charAt(0) == 'L' : typeName + " is not well formed.";
assert typeName.charAt(typeName.length() - 1) == ';' : typeName + " is not well formed.";
return typeName.substring(1, typeName.length() - 1);
}
@Nonnull
public static ReferenceBinding getEcjType(@Nonnull String typeName,
@Nonnull LookupEnvironment lookupEnvironment) throws JTypeLookupException {
String typeNameWithDot = getTypeConstantPoolName(typeName);
char[][] compoundName = CharOperation.splitOn('/', typeNameWithDot.toCharArray());
ReferenceBinding refBinding = lookupEnvironment.getType(compoundName);
if (refBinding instanceof ProblemReferenceBinding) {
ProblemReferenceBinding problem = (ProblemReferenceBinding) refBinding;
ReferenceBinding closestMatch = problem.closestReferenceMatch();
if (closestMatch != null && typeName.equals(new String(closestMatch.signature()))) {
assert closestMatch.isNestedType();
refBinding = closestMatch;
} else {
refBinding = null;
}
}
if (refBinding == null) {
throw new MissingJTypeLookupException(typeName);
}
return (refBinding);
}
static int removeSynchronizedOnBridge(int accessFlags) {
if (JModifier.isBridge(accessFlags)) {
accessFlags &= ~JModifier.SYNCHRONIZED;
}
return accessFlags;
}
/**
* Get the {@link MethodKind} of a non constructor method from its access flags.
*/
@Nonnull
static MethodKind getMethodKind(int accessFlags) {
if (JModifier.isStatic(accessFlags)) {
return MethodKind.STATIC;
} else if (JModifier.isPrivate(accessFlags)) {
return MethodKind.INSTANCE_NON_VIRTUAL;
} else {
return MethodKind.INSTANCE_VIRTUAL;
}
}
@CheckForNull
private JField findField(@Nonnull FieldBinding binding,
@Nonnull JDefinedClassOrInterface enclosingType) {
JField field = null;
String name = new String(binding.name);
String typeSignature = new String(binding.type.signature());
for (JField existing: enclosingType.getFields()) {
if (name.equals(existing.getName()) &&
typeSignature.equals(Jack.getLookupFormatter().getName(existing.getType()))) {
field = existing;
break;
}
}
return field;
}
@CheckForNull
private JMethod findMethod(
@Nonnull MethodBinding binding, @Nonnull JDefinedClassOrInterface enclosingType) {
JMethod method = null;
String paramsSignature = new String(binding.signature());
String searchedSignature = new String(binding.selector) + paramsSignature;
int paramsCount = countParams(paramsSignature);
for (JMethod existing : enclosingType.getMethods()) {
if (equals(paramsCount, searchedSignature, existing)) {
method = existing;
break;
}
}
return method;
}
@Nonnegative
@VisibleForTesting
static int countParams(@Nonnull String signature) {
int result = 0;
int pos = 1; // skip '('
while (pos < signature.length() && signature.charAt(pos) != ')') {
switch (signature.charAt(pos)) {
case 'L':
do {
pos++;
} while (pos < signature.length() && signature.charAt(pos) != ';');
assert pos < signature.length() && signature.charAt(pos) == ';';
// Fall-through.
case 'Z':
case 'B':
case 'C':
case 'S':
case 'I':
case 'J':
case 'F':
case 'D':
result++;
break;
case '[':
// ignore
break;
default:
throw new AssertionError();
}
pos++;
}
return result;
}
private boolean equals(@Nonnegative int paramsCount,
@Nonnull String bindingSignature, @Nonnull JMethod method) {
if (paramsCount != method.getParams().size() ||
!bindingSignature.startsWith(method.getName())) {
return false;
}
return bindingSignature.equals(lookupFormater.getName(method));
}
static SourceInfo makeSourceInfo(@Nonnull CudInfo cuInfo, @Nonnull AbstractMethodDeclaration x,
@Nonnull SourceInfoFactory factory) {
return JackIrBuilder.makeSourceInfo(cuInfo, x.declarationSourceStart, x.declarationSourceEnd,
factory);
}
static SourceInfo makeSourceInfo(@Nonnull CudInfo cuInfo, @Nonnull ASTNode x,
@Nonnull SourceInfoFactory factory) {
return JackIrBuilder.makeSourceInfo(cuInfo, x.sourceStart, x.sourceEnd, factory);
}
static boolean isCompileTimeConstant(@Nonnull FieldBinding binding) {
assert !binding.isFinal() || !binding.isVolatile();
boolean isCompileTimeConstant =
binding.isStatic() && binding.isFinal() && (binding.constant() != Constant.NotAConstant);
if (isCompileTimeConstant) {
assert binding.type.isBaseType() || (binding.type.id == TypeIds.T_JavaLangString);
}
return isCompileTimeConstant;
}
private void cacheMethod(@Nonnull SignatureKey key, @Nonnull JMethod method) {
assert !methods.containsKey(key);
methods.put(key, method);
}
private void cacheField(@Nonnull SignatureKey key, @Nonnull JField field) {
assert !fields.containsKey(key);
fields.put(key, field);
}
private static class SignatureKey {
private static final int PRIME = 277;
@Nonnull
private final char[] declaringClass;
@Nonnull
private final char[] name;
@Nonnull
private final char[] signature;
@Nonnegative
private final int hashCode;
public SignatureKey(@Nonnull char[] declaringClass, @Nonnull char[] name,
@Nonnull char[] signature) {
this.declaringClass = declaringClass;
this.name = name;
this.signature = signature;
hashCode = hash(declaringClass) ^ hash(name) ^ hash(signature);
}
private static int hash(@Nonnull char[] data) {
int hash = 0;
for (int i = 0; i < data.length; ++i) {
hash = hash * PRIME + data[i];
}
return hash;
}
public SignatureKey(@Nonnull MethodBinding binding) {
this(binding.declaringClass.constantPoolName(), binding.selector, binding.signature());
}
public SignatureKey(@Nonnull FieldBinding binding) {
this(binding.declaringClass.constantPoolName(), binding.name, binding.type.signature());
}
@Override
public final boolean equals(@CheckForNull Object obj) {
if (!(obj instanceof SignatureKey)) {
return false;
}
SignatureKey key = (SignatureKey) obj;
return Arrays.equals(declaringClass, key.declaringClass) && Arrays.equals(name, key.name) &&
Arrays.equals(signature, key.signature);
}
@Override
public final int hashCode() {
return hashCode;
}
}
}