1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package javassist.expr;
17
18import javassist.*;
19import javassist.bytecode.*;
20import javassist.compiler.*;
21import javassist.compiler.ast.ASTList;
22
23/**
24 * Expression for accessing a field.
25 */
26public class FieldAccess extends Expr {
27    int opcode;
28
29    protected FieldAccess(int pos, CodeIterator i, CtClass declaring,
30                          MethodInfo m, int op) {
31        super(pos, i, declaring, m);
32        opcode = op;
33    }
34
35    /**
36     * Returns the method or constructor containing the field-access
37     * expression represented by this object.
38     */
39    public CtBehavior where() { return super.where(); }
40
41    /**
42     * Returns the line number of the source line containing the
43     * field access.
44     *
45     * @return -1       if this information is not available.
46     */
47    public int getLineNumber() {
48        return super.getLineNumber();
49    }
50
51    /**
52     * Returns the source file containing the field access.
53     *
54     * @return null     if this information is not available.
55     */
56    public String getFileName() {
57        return super.getFileName();
58    }
59
60    /**
61     * Returns true if the field is static.
62     */
63    public boolean isStatic() {
64        return isStatic(opcode);
65    }
66
67    static boolean isStatic(int c) {
68        return c == Opcode.GETSTATIC || c == Opcode.PUTSTATIC;
69    }
70
71    /**
72     * Returns true if the field is read.
73     */
74    public boolean isReader() {
75        return opcode == Opcode.GETFIELD || opcode ==  Opcode.GETSTATIC;
76    }
77
78    /**
79     * Returns true if the field is written in.
80     */
81    public boolean isWriter() {
82        return opcode == Opcode.PUTFIELD || opcode ==  Opcode.PUTSTATIC;
83    }
84
85    /**
86     * Returns the class in which the field is declared.
87     */
88    private CtClass getCtClass() throws NotFoundException {
89        return thisClass.getClassPool().get(getClassName());
90    }
91
92    /**
93     * Returns the name of the class in which the field is declared.
94     */
95    public String getClassName() {
96        int index = iterator.u16bitAt(currentPos + 1);
97        return getConstPool().getFieldrefClassName(index);
98    }
99
100    /**
101     * Returns the name of the field.
102     */
103    public String getFieldName() {
104        int index = iterator.u16bitAt(currentPos + 1);
105        return getConstPool().getFieldrefName(index);
106    }
107
108    /**
109     * Returns the field accessed by this expression.
110     */
111    public CtField getField() throws NotFoundException {
112        CtClass cc = getCtClass();
113        return cc.getField(getFieldName());
114    }
115
116    /**
117     * Returns the list of exceptions that the expression may throw.
118     * This list includes both the exceptions that the try-catch statements
119     * including the expression can catch and the exceptions that
120     * the throws declaration allows the method to throw.
121     */
122    public CtClass[] mayThrow() {
123        return super.mayThrow();
124    }
125
126    /**
127     * Returns the signature of the field type.
128     * The signature is represented by a character string
129     * called field descriptor, which is defined in the JVM specification.
130     *
131     * @see javassist.bytecode.Descriptor#toCtClass(String, ClassPool)
132     * @since 3.1
133     */
134    public String getSignature() {
135        int index = iterator.u16bitAt(currentPos + 1);
136        return getConstPool().getFieldrefType(index);
137    }
138
139    /**
140     * Replaces the method call with the bytecode derived from
141     * the given source text.
142     *
143     * <p>$0 is available even if the called method is static.
144     * If the field access is writing, $_ is available but the value
145     * of $_ is ignored.
146     *
147     * @param statement         a Java statement except try-catch.
148     */
149    public void replace(String statement) throws CannotCompileException {
150        thisClass.getClassFile();   // to call checkModify().
151        ConstPool constPool = getConstPool();
152        int pos = currentPos;
153        int index = iterator.u16bitAt(pos + 1);
154
155        Javac jc = new Javac(thisClass);
156        CodeAttribute ca = iterator.get();
157        try {
158            CtClass[] params;
159            CtClass retType;
160            CtClass fieldType
161                = Descriptor.toCtClass(constPool.getFieldrefType(index),
162                                       thisClass.getClassPool());
163            boolean read = isReader();
164            if (read) {
165                params = new CtClass[0];
166                retType = fieldType;
167            }
168            else {
169                params = new CtClass[1];
170                params[0] = fieldType;
171                retType = CtClass.voidType;
172            }
173
174            int paramVar = ca.getMaxLocals();
175            jc.recordParams(constPool.getFieldrefClassName(index), params,
176                            true, paramVar, withinStatic());
177
178            /* Is $_ included in the source code?
179             */
180            boolean included = checkResultValue(retType, statement);
181            if (read)
182                included = true;
183
184            int retVar = jc.recordReturnType(retType, included);
185            if (read)
186                jc.recordProceed(new ProceedForRead(retType, opcode,
187                                                    index, paramVar));
188            else {
189                // because $type is not the return type...
190                jc.recordType(fieldType);
191                jc.recordProceed(new ProceedForWrite(params[0], opcode,
192                                                     index, paramVar));
193            }
194
195            Bytecode bytecode = jc.getBytecode();
196            storeStack(params, isStatic(), paramVar, bytecode);
197            jc.recordLocalVariables(ca, pos);
198
199            if (included)
200                if (retType == CtClass.voidType) {
201                    bytecode.addOpcode(ACONST_NULL);
202                    bytecode.addAstore(retVar);
203                }
204                else {
205                    bytecode.addConstZero(retType);
206                    bytecode.addStore(retVar, retType);     // initialize $_
207                }
208
209            jc.compileStmnt(statement);
210            if (read)
211                bytecode.addLoad(retVar, retType);
212
213            replace0(pos, bytecode, 3);
214        }
215        catch (CompileError e) { throw new CannotCompileException(e); }
216        catch (NotFoundException e) { throw new CannotCompileException(e); }
217        catch (BadBytecode e) {
218            throw new CannotCompileException("broken method");
219        }
220    }
221
222    /* <field type> $proceed()
223     */
224    static class ProceedForRead implements ProceedHandler {
225        CtClass fieldType;
226        int opcode;
227        int targetVar, index;
228
229        ProceedForRead(CtClass type, int op, int i, int var) {
230            fieldType = type;
231            targetVar = var;
232            opcode = op;
233            index = i;
234        }
235
236        public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
237            throws CompileError
238        {
239            if (args != null && !gen.isParamListName(args))
240                throw new CompileError(Javac.proceedName
241                        + "() cannot take a parameter for field reading");
242
243            int stack;
244            if (isStatic(opcode))
245                stack = 0;
246            else {
247                stack = -1;
248                bytecode.addAload(targetVar);
249            }
250
251            if (fieldType instanceof CtPrimitiveType)
252                stack += ((CtPrimitiveType)fieldType).getDataSize();
253            else
254                ++stack;
255
256            bytecode.add(opcode);
257            bytecode.addIndex(index);
258            bytecode.growStack(stack);
259            gen.setType(fieldType);
260        }
261
262        public void setReturnType(JvstTypeChecker c, ASTList args)
263            throws CompileError
264        {
265            c.setType(fieldType);
266        }
267    }
268
269    /* void $proceed(<field type>)
270     *          the return type is not the field type but void.
271     */
272    static class ProceedForWrite implements ProceedHandler {
273        CtClass fieldType;
274        int opcode;
275        int targetVar, index;
276
277        ProceedForWrite(CtClass type, int op, int i, int var) {
278            fieldType = type;
279            targetVar = var;
280            opcode = op;
281            index = i;
282        }
283
284        public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
285            throws CompileError
286        {
287            if (gen.getMethodArgsLength(args) != 1)
288                throw new CompileError(Javac.proceedName
289                        + "() cannot take more than one parameter "
290                        + "for field writing");
291
292            int stack;
293            if (isStatic(opcode))
294                stack = 0;
295            else {
296                stack = -1;
297                bytecode.addAload(targetVar);
298            }
299
300            gen.atMethodArgs(args, new int[1], new int[1], new String[1]);
301            gen.doNumCast(fieldType);
302            if (fieldType instanceof CtPrimitiveType)
303                stack -= ((CtPrimitiveType)fieldType).getDataSize();
304            else
305                --stack;
306
307            bytecode.add(opcode);
308            bytecode.addIndex(index);
309            bytecode.growStack(stack);
310            gen.setType(CtClass.voidType);
311            gen.addNullIfVoid();
312        }
313
314        public void setReturnType(JvstTypeChecker c, ASTList args)
315            throws CompileError
316        {
317            c.atMethodArgs(args, new int[1], new int[1], new String[1]);
318            c.setType(CtClass.voidType);
319            c.addNullIfVoid();
320        }
321    }
322}
323