1 /*
2  * Copyright (C) 2006 Google Inc.
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.google.inject.internal.util;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.base.Preconditions;
22 import com.google.common.collect.Maps;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.Member;
28 import java.lang.reflect.Method;
29 import java.util.Map;
30 import org.objectweb.asm.AnnotationVisitor;
31 import org.objectweb.asm.ClassReader;
32 import org.objectweb.asm.ClassVisitor;
33 import org.objectweb.asm.FieldVisitor;
34 import org.objectweb.asm.Label;
35 import org.objectweb.asm.MethodVisitor;
36 import org.objectweb.asm.Opcodes;
37 
38 /**
39  * Looks up line numbers for classes and their members.
40  *
41  * @author Chris Nokleberg
42  */
43 final class LineNumbers {
44 
45   private final Class type;
46   private final Map<String, Integer> lines = Maps.newHashMap();
47   private String source;
48   private int firstLine = Integer.MAX_VALUE;
49 
50   /**
51    * Reads line number information from the given class, if available.
52    *
53    * @param type the class to read line number information from
54    * @throws IllegalArgumentException if the bytecode for the class cannot be found
55    * @throws java.io.IOException if an error occurs while reading bytecode
56    */
LineNumbers(Class type)57   public LineNumbers(Class type) throws IOException {
58     this.type = type;
59 
60     if (!type.isArray()) {
61       InputStream in = type.getResourceAsStream("/" + type.getName().replace('.', '/') + ".class");
62       if (in != null) {
63         try {
64           new ClassReader(in).accept(new LineNumberReader(), ClassReader.SKIP_FRAMES);
65         } finally {
66           try {
67             in.close();
68           } catch (IOException ignored) {
69           }
70         }
71       }
72     }
73   }
74 
75   /**
76    * Get the source file name as read from the bytecode.
77    *
78    * @return the source file name if available, or null
79    */
getSource()80   public String getSource() {
81     return source;
82   }
83 
84   /**
85    * Get the line number associated with the given member.
86    *
87    * @param member a field, constructor, or method belonging to the class used during construction
88    * @return the wrapped line number, or null if not available
89    * @throws IllegalArgumentException if the member does not belong to the class used during
90    *     construction
91    */
getLineNumber(Member member)92   public Integer getLineNumber(Member member) {
93     Preconditions.checkArgument(
94         type == member.getDeclaringClass(),
95         "Member %s belongs to %s, not %s",
96         member,
97         member.getDeclaringClass(),
98         type);
99     return lines.get(memberKey(member));
100   }
101 
102   /** Gets the first line number. */
getFirstLine()103   public int getFirstLine() {
104     return firstLine == Integer.MAX_VALUE ? 1 : firstLine;
105   }
106 
memberKey(Member member)107   private String memberKey(Member member) {
108     checkNotNull(member, "member");
109 
110     /*if[AOP]*/
111     if (member instanceof Field) {
112       return member.getName();
113 
114     } else if (member instanceof Method) {
115       return member.getName() + org.objectweb.asm.Type.getMethodDescriptor((Method) member);
116 
117     } else if (member instanceof Constructor) {
118       StringBuilder sb = new StringBuilder().append("<init>(");
119       for (Class param : ((Constructor) member).getParameterTypes()) {
120         sb.append(org.objectweb.asm.Type.getDescriptor(param));
121       }
122       return sb.append(")V").toString();
123 
124     } else {
125       throw new IllegalArgumentException(
126           "Unsupported implementation class for Member, " + member.getClass());
127     }
128     /*end[AOP]*/
129     /*if[NO_AOP]
130     return "<NO_MEMBER_KEY>";
131     end[NO_AOP]*/
132   }
133 
134   private class LineNumberReader extends ClassVisitor {
135 
136     private int line = -1;
137     private String pendingMethod;
138     private String name;
139 
LineNumberReader()140     LineNumberReader() {
141       super(Opcodes.ASM6);
142     }
143 
144     @Override
visit( int version, int access, String name, String signature, String superName, String[] interfaces)145     public void visit(
146         int version,
147         int access,
148         String name,
149         String signature,
150         String superName,
151         String[] interfaces) {
152       this.name = name;
153     }
154 
155     @Override
visitMethod( int access, String name, String desc, String signature, String[] exceptions)156     public MethodVisitor visitMethod(
157         int access, String name, String desc, String signature, String[] exceptions) {
158       if ((access & Opcodes.ACC_PRIVATE) != 0) {
159         return null;
160       }
161       pendingMethod = name + desc;
162       line = -1;
163       return new LineNumberMethodVisitor();
164     }
165 
166     @Override
visitSource(String source, String debug)167     public void visitSource(String source, String debug) {
168       LineNumbers.this.source = source;
169     }
170 
visitLineNumber(int line, Label start)171     public void visitLineNumber(int line, Label start) {
172       if (line < firstLine) {
173         firstLine = line;
174       }
175 
176       this.line = line;
177       if (pendingMethod != null) {
178         lines.put(pendingMethod, line);
179         pendingMethod = null;
180       }
181     }
182 
183     @Override
visitField( int access, String name, String desc, String signature, Object value)184     public FieldVisitor visitField(
185         int access, String name, String desc, String signature, Object value) {
186       return null;
187     }
188 
189     @Override
visitAnnotation(String desc, boolean visible)190     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
191       return new LineNumberAnnotationVisitor();
192     }
193 
visitParameterAnnotation(int parameter, String desc, boolean visible)194     public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
195       return new LineNumberAnnotationVisitor();
196     }
197 
198     class LineNumberMethodVisitor extends MethodVisitor {
LineNumberMethodVisitor()199       LineNumberMethodVisitor() {
200         super(Opcodes.ASM6);
201       }
202 
203       @Override
visitAnnotation(String desc, boolean visible)204       public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
205         return new LineNumberAnnotationVisitor();
206       }
207 
208       @Override
visitAnnotationDefault()209       public AnnotationVisitor visitAnnotationDefault() {
210         return new LineNumberAnnotationVisitor();
211       }
212 
213       @Override
visitFieldInsn(int opcode, String owner, String name, String desc)214       public void visitFieldInsn(int opcode, String owner, String name, String desc) {
215         if (opcode == Opcodes.PUTFIELD
216             && LineNumberReader.this.name.equals(owner)
217             && !lines.containsKey(name)
218             && line != -1) {
219           lines.put(name, line);
220         }
221       }
222 
223       @Override
visitLineNumber(int line, Label start)224       public void visitLineNumber(int line, Label start) {
225         LineNumberReader.this.visitLineNumber(line, start);
226       }
227     }
228 
229     class LineNumberAnnotationVisitor extends AnnotationVisitor {
LineNumberAnnotationVisitor()230       LineNumberAnnotationVisitor() {
231         super(Opcodes.ASM6);
232       }
233 
234       @Override
visitAnnotation(String name, String desc)235       public AnnotationVisitor visitAnnotation(String name, String desc) {
236         return this;
237       }
238 
239       @Override
visitArray(String name)240       public AnnotationVisitor visitArray(String name) {
241         return this;
242       }
243 
visitLocalVariable( String name, String desc, String signature, Label start, Label end, int index)244       public void visitLocalVariable(
245           String name, String desc, String signature, Label start, Label end, int index) {}
246     }
247   }
248 }
249