1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx.dex.cf; 18 19 import com.android.dx.cf.attrib.AttAnnotationDefault; 20 import com.android.dx.cf.attrib.AttEnclosingMethod; 21 import com.android.dx.cf.attrib.AttExceptions; 22 import com.android.dx.cf.attrib.AttInnerClasses; 23 import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations; 24 import com.android.dx.cf.attrib.AttRuntimeInvisibleParameterAnnotations; 25 import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations; 26 import com.android.dx.cf.attrib.AttRuntimeVisibleParameterAnnotations; 27 import com.android.dx.cf.attrib.AttSignature; 28 import com.android.dx.cf.attrib.AttSourceDebugExtension; 29 import com.android.dx.cf.attrib.InnerClassList; 30 import com.android.dx.cf.direct.DirectClassFile; 31 import com.android.dx.cf.iface.AttributeList; 32 import com.android.dx.cf.iface.Method; 33 import com.android.dx.cf.iface.MethodList; 34 import com.android.dx.dex.file.AnnotationUtils; 35 import com.android.dx.rop.annotation.Annotation; 36 import com.android.dx.rop.annotation.AnnotationVisibility; 37 import com.android.dx.rop.annotation.Annotations; 38 import com.android.dx.rop.annotation.AnnotationsList; 39 import com.android.dx.rop.annotation.NameValuePair; 40 import com.android.dx.rop.code.AccessFlags; 41 import com.android.dx.rop.cst.CstMethodRef; 42 import com.android.dx.rop.cst.CstNat; 43 import com.android.dx.rop.cst.CstType; 44 import com.android.dx.rop.type.StdTypeList; 45 import com.android.dx.rop.type.Type; 46 import com.android.dx.rop.type.TypeList; 47 import com.android.dx.util.Warning; 48 import java.util.ArrayList; 49 50 /** 51 * Utility methods that translate various classfile attributes 52 * into forms suitable for use in creating {@code dex} files. 53 */ 54 /*package*/ class AttributeTranslator { 55 /** 56 * This class is uninstantiable. 57 */ AttributeTranslator()58 private AttributeTranslator() { 59 // This space intentionally left blank. 60 } 61 62 /** 63 * Gets the list of thrown exceptions for a given method. 64 * 65 * @param method {@code non-null;} the method in question 66 * @return {@code non-null;} the list of thrown exceptions 67 */ getExceptions(Method method)68 public static TypeList getExceptions(Method method) { 69 AttributeList attribs = method.getAttributes(); 70 AttExceptions exceptions = (AttExceptions) 71 attribs.findFirst(AttExceptions.ATTRIBUTE_NAME); 72 73 if (exceptions == null) { 74 return StdTypeList.EMPTY; 75 } 76 77 return exceptions.getExceptions(); 78 } 79 80 /** 81 * Gets the annotations out of a given {@link AttributeList}. This 82 * combines both visible and invisible annotations into a single 83 * result set and also adds in a system annotation for the 84 * {@code Signature} attribute if present. 85 * 86 * @param attribs {@code non-null;} the attributes list to search in 87 * @return {@code non-null;} the set of annotations, which may be empty 88 */ getAnnotations(AttributeList attribs)89 public static Annotations getAnnotations(AttributeList attribs) { 90 Annotations result = getAnnotations0(attribs); 91 Annotation signature = getSignature(attribs); 92 Annotation sourceDebugExtension = getSourceDebugExtension(attribs); 93 94 if (signature != null) { 95 result = Annotations.combine(result, signature); 96 } 97 98 if (sourceDebugExtension != null) { 99 result = Annotations.combine(result, sourceDebugExtension); 100 } 101 102 return result; 103 } 104 105 /** 106 * Gets the annotations out of a given class, similar to {@link 107 * #getAnnotations}, also including annotations for translations 108 * of class-level attributes {@code EnclosingMethod} and 109 * {@code InnerClasses}, if present. Additionally, if the 110 * class is an annotation class, then this also includes a 111 * representation of all the {@code AnnotationDefault} 112 * values. 113 * 114 * @param cf {@code non-null;} the class in question 115 * @param args {@code non-null;} the high-level options 116 * @return {@code non-null;} the set of annotations, which may be empty 117 */ getClassAnnotations(DirectClassFile cf, CfOptions args)118 public static Annotations getClassAnnotations(DirectClassFile cf, 119 CfOptions args) { 120 CstType thisClass = cf.getThisClass(); 121 AttributeList attribs = cf.getAttributes(); 122 Annotations result = getAnnotations(attribs); 123 Annotation enclosingMethod = translateEnclosingMethod(attribs); 124 125 try { 126 Annotations innerClassAnnotations = 127 translateInnerClasses(thisClass, attribs, 128 enclosingMethod == null); 129 if (innerClassAnnotations != null) { 130 result = Annotations.combine(result, innerClassAnnotations); 131 } 132 } catch (Warning warn) { 133 args.warn.println("warning: " + warn.getMessage()); 134 } 135 136 if (enclosingMethod != null) { 137 result = Annotations.combine(result, enclosingMethod); 138 } 139 140 if (AccessFlags.isAnnotation(cf.getAccessFlags())) { 141 Annotation annotationDefault = 142 translateAnnotationDefaults(cf); 143 if (annotationDefault != null) { 144 result = Annotations.combine(result, annotationDefault); 145 } 146 } 147 148 return result; 149 } 150 151 /** 152 * Gets the annotations out of a given method, similar to {@link 153 * #getAnnotations}, also including an annotation for the translation 154 * of the method-specific attribute {@code Exceptions}. 155 * 156 * @param method {@code non-null;} the method in question 157 * @return {@code non-null;} the set of annotations, which may be empty 158 */ getMethodAnnotations(Method method)159 public static Annotations getMethodAnnotations(Method method) { 160 Annotations result = getAnnotations(method.getAttributes()); 161 TypeList exceptions = getExceptions(method); 162 163 if (exceptions.size() != 0) { 164 Annotation throwsAnnotation = 165 AnnotationUtils.makeThrows(exceptions); 166 result = Annotations.combine(result, throwsAnnotation); 167 } 168 169 return result; 170 } 171 172 /** 173 * Helper method for {@link #getAnnotations} which just gets the 174 * existing annotations, per se. 175 * 176 * @param attribs {@code non-null;} the attributes list to search in 177 * @return {@code non-null;} the set of annotations, which may be empty 178 */ getAnnotations0(AttributeList attribs)179 private static Annotations getAnnotations0(AttributeList attribs) { 180 AttRuntimeVisibleAnnotations visible = 181 (AttRuntimeVisibleAnnotations) 182 attribs.findFirst(AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); 183 AttRuntimeInvisibleAnnotations invisible = 184 (AttRuntimeInvisibleAnnotations) 185 attribs.findFirst(AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME); 186 187 if (visible == null) { 188 if (invisible == null) { 189 return Annotations.EMPTY; 190 } 191 return invisible.getAnnotations(); 192 } 193 194 if (invisible == null) { 195 return visible.getAnnotations(); 196 } 197 198 // Both are non-null, so combine them. 199 200 return Annotations.combine(visible.getAnnotations(), 201 invisible.getAnnotations()); 202 } 203 204 /** 205 * Gets the {@code Signature} attribute out of a given 206 * {@link AttributeList}, if any, translating it to an annotation. 207 * 208 * @param attribs {@code non-null;} the attributes list to search in 209 * @return {@code null-ok;} the converted {@code Signature} annotation, 210 * if there was an attribute to translate 211 */ getSignature(AttributeList attribs)212 private static Annotation getSignature(AttributeList attribs) { 213 AttSignature signature = (AttSignature) 214 attribs.findFirst(AttSignature.ATTRIBUTE_NAME); 215 216 if (signature == null) { 217 return null; 218 } 219 220 return AnnotationUtils.makeSignature(signature.getSignature()); 221 } 222 223 getSourceDebugExtension(AttributeList attribs)224 private static Annotation getSourceDebugExtension(AttributeList attribs) { 225 AttSourceDebugExtension extension = (AttSourceDebugExtension) 226 attribs.findFirst(AttSourceDebugExtension.ATTRIBUTE_NAME); 227 228 if (extension == null) { 229 return null; 230 } 231 232 return AnnotationUtils.makeSourceDebugExtension(extension.getSmapString()); 233 } 234 235 /** 236 * Gets the {@code EnclosingMethod} attribute out of a given 237 * {@link AttributeList}, if any, translating it to an annotation. 238 * If the class really has an enclosing method, this returns an 239 * {@code EnclosingMethod} annotation; if not, this returns 240 * an {@code EnclosingClass} annotation. 241 * 242 * @param attribs {@code non-null;} the attributes list to search in 243 * @return {@code null-ok;} the converted {@code EnclosingMethod} or 244 * {@code EnclosingClass} annotation, if there was an 245 * attribute to translate 246 */ translateEnclosingMethod(AttributeList attribs)247 private static Annotation translateEnclosingMethod(AttributeList attribs) { 248 AttEnclosingMethod enclosingMethod = (AttEnclosingMethod) 249 attribs.findFirst(AttEnclosingMethod.ATTRIBUTE_NAME); 250 251 if (enclosingMethod == null) { 252 return null; 253 } 254 255 CstType enclosingClass = enclosingMethod.getEnclosingClass(); 256 CstNat nat = enclosingMethod.getMethod(); 257 258 if (nat == null) { 259 /* 260 * Dalvik doesn't use EnclosingMethod annotations unless 261 * there really is an enclosing method. Anonymous classes 262 * are unambiguously identified by having an InnerClass 263 * annotation with an empty name along with an appropriate 264 * EnclosingClass. 265 */ 266 return AnnotationUtils.makeEnclosingClass(enclosingClass); 267 } 268 269 return AnnotationUtils.makeEnclosingMethod( 270 new CstMethodRef(enclosingClass, nat)); 271 } 272 273 /** 274 * Gets the {@code InnerClasses} attribute out of a given 275 * {@link AttributeList}, if any, translating it to one or more of an 276 * {@code InnerClass}, {@code EnclosingClass}, or 277 * {@code MemberClasses} annotation. 278 * 279 * @param thisClass {@code non-null;} type representing the class being 280 * processed 281 * @param attribs {@code non-null;} the attributes list to search in 282 * @param needEnclosingClass whether to include an 283 * {@code EnclosingClass} annotation 284 * @return {@code null-ok;} the converted list of annotations, if there 285 * was an attribute to translate 286 */ translateInnerClasses(CstType thisClass, AttributeList attribs, boolean needEnclosingClass)287 private static Annotations translateInnerClasses(CstType thisClass, 288 AttributeList attribs, boolean needEnclosingClass) { 289 AttInnerClasses innerClasses = (AttInnerClasses) 290 attribs.findFirst(AttInnerClasses.ATTRIBUTE_NAME); 291 292 if (innerClasses == null) { 293 return null; 294 } 295 296 /* 297 * Search the list for the element representing the current class 298 * as well as for any named member classes. 299 */ 300 301 InnerClassList list = innerClasses.getInnerClasses(); 302 int size = list.size(); 303 InnerClassList.Item foundThisClass = null; 304 ArrayList<Type> membersList = new ArrayList<Type>(); 305 306 for (int i = 0; i < size; i++) { 307 InnerClassList.Item item = list.get(i); 308 CstType innerClass = item.getInnerClass(); 309 if (innerClass.equals(thisClass)) { 310 foundThisClass = item; 311 } else if (thisClass.equals(item.getOuterClass())) { 312 membersList.add(innerClass.getClassType()); 313 } 314 } 315 316 int membersSize = membersList.size(); 317 318 if ((foundThisClass == null) && (membersSize == 0)) { 319 return null; 320 } 321 322 Annotations result = new Annotations(); 323 324 if (foundThisClass != null) { 325 result.add(AnnotationUtils.makeInnerClass( 326 foundThisClass.getInnerName(), 327 foundThisClass.getAccessFlags())); 328 if (needEnclosingClass) { 329 CstType outer = foundThisClass.getOuterClass(); 330 if (outer == null) { 331 throw new Warning( 332 "Ignoring InnerClasses attribute for an " + 333 "anonymous inner class\n" + 334 "(" + thisClass.toHuman() + 335 ") that doesn't come with an\n" + 336 "associated EnclosingMethod attribute. " + 337 "This class was probably produced by a\n" + 338 "compiler that did not target the modern " + 339 ".class file format. The recommended\n" + 340 "solution is to recompile the class from " + 341 "source, using an up-to-date compiler\n" + 342 "and without specifying any \"-target\" type " + 343 "options. The consequence of ignoring\n" + 344 "this warning is that reflective operations " + 345 "on this class will incorrectly\n" + 346 "indicate that it is *not* an inner class."); 347 } 348 result.add(AnnotationUtils.makeEnclosingClass( 349 foundThisClass.getOuterClass())); 350 } 351 } 352 353 if (membersSize != 0) { 354 StdTypeList typeList = new StdTypeList(membersSize); 355 for (int i = 0; i < membersSize; i++) { 356 typeList.set(i, membersList.get(i)); 357 } 358 typeList.setImmutable(); 359 result.add(AnnotationUtils.makeMemberClasses(typeList)); 360 } 361 362 result.setImmutable(); 363 return result; 364 } 365 366 /** 367 * Gets the parameter annotations out of a given method. This 368 * combines both visible and invisible annotations into a single 369 * result set. 370 * 371 * @param method {@code non-null;} the method in question 372 * @return {@code non-null;} the list of annotation sets, which may be 373 * empty 374 */ getParameterAnnotations(Method method)375 public static AnnotationsList getParameterAnnotations(Method method) { 376 AttributeList attribs = method.getAttributes(); 377 AttRuntimeVisibleParameterAnnotations visible = 378 (AttRuntimeVisibleParameterAnnotations) 379 attribs.findFirst( 380 AttRuntimeVisibleParameterAnnotations.ATTRIBUTE_NAME); 381 AttRuntimeInvisibleParameterAnnotations invisible = 382 (AttRuntimeInvisibleParameterAnnotations) 383 attribs.findFirst( 384 AttRuntimeInvisibleParameterAnnotations.ATTRIBUTE_NAME); 385 386 if (visible == null) { 387 if (invisible == null) { 388 return AnnotationsList.EMPTY; 389 } 390 return invisible.getParameterAnnotations(); 391 } 392 393 if (invisible == null) { 394 return visible.getParameterAnnotations(); 395 } 396 397 // Both are non-null, so combine them. 398 399 return AnnotationsList.combine(visible.getParameterAnnotations(), 400 invisible.getParameterAnnotations()); 401 } 402 403 /** 404 * Gets the {@code AnnotationDefault} attributes out of a 405 * given class, if any, reforming them as an 406 * {@code AnnotationDefault} annotation. 407 * 408 * @param cf {@code non-null;} the class in question 409 * @return {@code null-ok;} an appropriately-constructed 410 * {@code AnnotationDefault} annotation, if there were any 411 * annotation defaults in the class, or {@code null} if not 412 */ translateAnnotationDefaults(DirectClassFile cf)413 private static Annotation translateAnnotationDefaults(DirectClassFile cf) { 414 CstType thisClass = cf.getThisClass(); 415 MethodList methods = cf.getMethods(); 416 int sz = methods.size(); 417 Annotation result = 418 new Annotation(thisClass, AnnotationVisibility.EMBEDDED); 419 boolean any = false; 420 421 for (int i = 0; i < sz; i++) { 422 Method one = methods.get(i); 423 AttributeList attribs = one.getAttributes(); 424 AttAnnotationDefault oneDefault = (AttAnnotationDefault) 425 attribs.findFirst(AttAnnotationDefault.ATTRIBUTE_NAME); 426 427 if (oneDefault != null) { 428 NameValuePair pair = new NameValuePair( 429 one.getNat().getName(), 430 oneDefault.getValue()); 431 result.add(pair); 432 any = true; 433 } 434 } 435 436 if (! any) { 437 return null; 438 } 439 440 result.setImmutable(); 441 return AnnotationUtils.makeAnnotationDefault(result); 442 } 443 } 444