1/*
2 * Copyright 2014, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.smalidea.debugging;
33
34import com.google.common.collect.Lists;
35import com.google.common.collect.Maps;
36import com.intellij.debugger.SourcePosition;
37import com.intellij.debugger.engine.evaluation.*;
38import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilder;
39import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
40import com.intellij.debugger.engine.jdi.StackFrameProxy;
41import com.intellij.openapi.application.ApplicationManager;
42import com.intellij.openapi.fileTypes.LanguageFileType;
43import com.intellij.openapi.project.Project;
44import com.intellij.openapi.util.Computable;
45import com.intellij.openapi.util.Key;
46import com.intellij.psi.JavaCodeFragment;
47import com.intellij.psi.JavaRecursiveElementVisitor;
48import com.intellij.psi.PsiElement;
49import com.intellij.psi.PsiLocalVariable;
50import com.intellij.psi.util.PsiMatchers;
51import com.sun.jdi.*;
52import com.sun.tools.jdi.LocalVariableImpl;
53import com.sun.tools.jdi.LocationImpl;
54import org.jf.dexlib2.analysis.AnalyzedInstruction;
55import org.jf.dexlib2.analysis.RegisterType;
56import org.jf.smalidea.SmaliFileType;
57import org.jf.smalidea.SmaliLanguage;
58import org.jf.smalidea.debugging.value.LazyValue;
59import org.jf.smalidea.psi.impl.SmaliInstruction;
60import org.jf.smalidea.psi.impl.SmaliMethod;
61import org.jf.smalidea.util.NameUtils;
62import org.jf.smalidea.util.PsiUtil;
63
64import javax.annotation.Nullable;
65import java.lang.reflect.Constructor;
66import java.lang.reflect.InvocationTargetException;
67import java.util.List;
68import java.util.Map;
69
70public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory {
71    static final Key<List<LazyValue>> SMALI_LAZY_VALUES_KEY = Key.create("_smali_register_value_key_");
72
73    @Override
74    public JavaCodeFragment createCodeFragment(TextWithImports item, PsiElement context, Project project) {
75        context = wrapContext(project, context);
76        JavaCodeFragment fragment = super.createCodeFragment(item, context, project);
77        List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY);
78        if (lazyValues != null) {
79            fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues);
80        }
81        return fragment;
82    }
83
84    @Override
85    public boolean isContextAccepted(PsiElement contextElement) {
86        if (contextElement == null) {
87            return false;
88        }
89        return contextElement.getLanguage() == SmaliLanguage.INSTANCE;
90    }
91
92    @Override
93    public JavaCodeFragment createPresentationCodeFragment(TextWithImports item, PsiElement context, Project project) {
94        context = wrapContext(project, context);
95        JavaCodeFragment fragment = super.createPresentationCodeFragment(item, context, project);
96        List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY);
97        if (lazyValues != null) {
98            fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues);
99        }
100        return fragment;
101    }
102
103    @Override public LanguageFileType getFileType() {
104        return SmaliFileType.INSTANCE;
105    }
106
107    @Override public EvaluatorBuilder getEvaluatorBuilder() {
108        final EvaluatorBuilder builder = super.getEvaluatorBuilder();
109        return new EvaluatorBuilder() {
110
111            @Override
112            public ExpressionEvaluator build(PsiElement codeFragment, SourcePosition position)
113                    throws EvaluateException {
114                return new SmaliExpressionEvaluator(codeFragment, builder.build(codeFragment, position));
115            }
116        };
117    }
118
119    private PsiElement wrapContext(final Project project, final PsiElement originalContext) {
120        if (project.isDefault()) return originalContext;
121
122        final List<LazyValue> lazyValues = Lists.newArrayList();
123
124        SmaliInstruction currentInstruction = (SmaliInstruction)PsiUtil.searchBackward(originalContext,
125                PsiMatchers.hasClass(SmaliInstruction.class),
126                PsiMatchers.hasClass(SmaliMethod.class));
127
128        if (currentInstruction == null) {
129            currentInstruction = (SmaliInstruction)PsiUtil.searchForward(originalContext,
130                    PsiMatchers.hasClass(SmaliInstruction.class),
131                    PsiMatchers.hasClass(SmaliMethod.class));
132            if (currentInstruction == null) {
133                return originalContext;
134            }
135        }
136
137        final SmaliMethod containingMethod = currentInstruction.getParentMethod();
138        AnalyzedInstruction analyzedInstruction = currentInstruction.getAnalyzedInstruction();
139        if (analyzedInstruction == null) {
140            return originalContext;
141        }
142
143        final int firstParameterRegister = containingMethod.getRegisterCount() -
144                containingMethod.getParameterRegisterCount();
145
146        final Map<String, String> registerMap = Maps.newHashMap();
147        StringBuilder variablesText = new StringBuilder();
148        for (int i=0; i<containingMethod.getRegisterCount(); i++) {
149            int parameterRegisterNumber = i - firstParameterRegister;
150
151            RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(i);
152            switch (registerType.category) {
153                case RegisterType.UNKNOWN:
154                case RegisterType.UNINIT:
155                case RegisterType.CONFLICTED:
156                case RegisterType.LONG_HI:
157                case RegisterType.DOUBLE_HI:
158                    continue;
159                case RegisterType.NULL:
160                case RegisterType.ONE:
161                case RegisterType.INTEGER:
162                    variablesText.append("int v").append(i).append(";\n");
163                    registerMap.put("v" + i, "I");
164                    if (parameterRegisterNumber >= 0) {
165                        variablesText.append("int p").append(parameterRegisterNumber).append(";\n");
166                        registerMap.put("p" + parameterRegisterNumber, "I");
167                    }
168                    break;
169                case RegisterType.BOOLEAN:
170                    variablesText.append("boolean v").append(i).append(";\n");
171                    registerMap.put("v" + i, "Z");
172                    if (parameterRegisterNumber >= 0) {
173                        variablesText.append("boolean p").append(parameterRegisterNumber).append(";\n");
174                        registerMap.put("p" + parameterRegisterNumber, "Z");
175                    }
176                    break;
177                case RegisterType.BYTE:
178                case RegisterType.POS_BYTE:
179                    variablesText.append("byte v").append(i).append(";\n");
180                    registerMap.put("v" + i, "B");
181                    if (parameterRegisterNumber >= 0) {
182                        variablesText.append("byte p").append(parameterRegisterNumber).append(";\n");
183                        registerMap.put("p" + parameterRegisterNumber, "B");
184                    }
185                    break;
186                case RegisterType.SHORT:
187                case RegisterType.POS_SHORT:
188                    variablesText.append("short v").append(i).append(";\n");
189                    registerMap.put("v" + i, "S");
190                    if (parameterRegisterNumber >= 0) {
191                        variablesText.append("short p").append(parameterRegisterNumber).append(";\n");
192                        registerMap.put("p" + parameterRegisterNumber, "S");
193                    }
194                    break;
195                case RegisterType.CHAR:
196                    variablesText.append("char v").append(i).append(";\n");
197                    registerMap.put("v" + i, "C");
198                    if (parameterRegisterNumber >= 0) {
199                        variablesText.append("char p").append(parameterRegisterNumber).append(";\n");
200                        registerMap.put("p" + parameterRegisterNumber, "C");
201                    }
202                    break;
203                case RegisterType.FLOAT:
204                    variablesText.append("float v").append(i).append(";\n");
205                    registerMap.put("v" + i, "F");
206                    if (parameterRegisterNumber >= 0) {
207                        variablesText.append("float p").append(parameterRegisterNumber).append(";\n");
208                        registerMap.put("p" + parameterRegisterNumber, "F");
209                    }
210                    break;
211                case RegisterType.LONG_LO:
212                    variablesText.append("long v").append(i).append(";\n");
213                    registerMap.put("v" + i, "J");
214                    if (parameterRegisterNumber >= 0) {
215                        variablesText.append("long p").append(parameterRegisterNumber).append(";\n");
216                        registerMap.put("p" + parameterRegisterNumber, "J");
217                    }
218                    break;
219                case RegisterType.DOUBLE_LO:
220                    variablesText.append("double v").append(i).append(";\n");
221                    registerMap.put("v" + i, "D");
222                    if (parameterRegisterNumber >= 0) {
223                        variablesText.append("double p").append(parameterRegisterNumber).append(";\n");
224                        registerMap.put("p" + parameterRegisterNumber, "D");
225                    }
226                    break;
227                case RegisterType.UNINIT_REF:
228                case RegisterType.UNINIT_THIS:
229                case RegisterType.REFERENCE:
230                    String smaliType = registerType.type.getType();
231                    String javaType = NameUtils.smaliToJavaType(smaliType);
232                    variablesText.append(javaType).append(" v").append(i).append(";\n");
233                    registerMap.put("v" + i, smaliType);
234                    if (parameterRegisterNumber >= 0) {
235                        variablesText.append(javaType).append(" p").append(parameterRegisterNumber).append(";\n");
236                        registerMap.put("p" + parameterRegisterNumber, "Ljava/lang/Object;");
237                    }
238                    break;
239            }
240        }
241        final TextWithImportsImpl textWithImports = new TextWithImportsImpl(CodeFragmentKind.CODE_BLOCK,
242                variablesText.toString(), "", getFileType());
243
244        final JavaCodeFragment codeFragment = super.createCodeFragment(textWithImports, originalContext, project);
245
246        codeFragment.accept(new JavaRecursiveElementVisitor() {
247            @Override
248            public void visitLocalVariable(final PsiLocalVariable variable) {
249                final String name = variable.getName();
250                if (name != null && registerMap.containsKey(name)) {
251                    int registerNumber = Integer.parseInt(name.substring(1));
252                    if (name.charAt(0) == 'p') {
253                        registerNumber += ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
254                            @Override public Integer compute() {
255                                return containingMethod.getRegisterCount() -
256                                        containingMethod.getParameterRegisterCount();
257                            }
258                        });
259                    }
260                    LazyValue lazyValue = LazyValue.create(containingMethod, project, registerNumber,
261                            registerMap.get(name));
262                    variable.putUserData(CodeFragmentFactoryContextWrapper.LABEL_VARIABLE_VALUE_KEY, lazyValue);
263                    lazyValues.add(lazyValue);
264                }
265            }
266        });
267
268        int offset = variablesText.length() - 1;
269
270        final PsiElement newContext = codeFragment.findElementAt(offset);
271        if (newContext != null) {
272            newContext.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues);
273            return newContext;
274        }
275        return originalContext;
276    }
277
278    @Nullable
279    public static Value evaluateRegister(EvaluationContext context, final SmaliMethod smaliMethod,
280                                         final int registerNum, final String type) throws EvaluateException {
281
282        if (registerNum >= smaliMethod.getRegisterCount()) {
283            return null;
284        }
285
286        final StackFrameProxy frameProxy = context.getSuspendContext().getFrameProxy();
287        if (frameProxy == null) {
288            return null;
289        }
290
291        VirtualMachine vm = frameProxy.getStackFrame().virtualMachine();
292        Location currentLocation = frameProxy.location();
293        if (currentLocation == null) {
294            return null;
295        }
296
297        Method method = currentLocation.method();
298
299        try {
300            final Constructor<LocalVariableImpl> localVariableConstructor = LocalVariableImpl.class.getDeclaredConstructor(
301                    VirtualMachine.class, Method.class, Integer.TYPE, Location.class, Location.class, String.class,
302                    String.class, String.class);
303            localVariableConstructor.setAccessible(true);
304
305            Constructor<LocationImpl> locationConstructor = LocationImpl.class.getDeclaredConstructor(
306                    VirtualMachine.class, Method.class, Long.TYPE);
307            locationConstructor.setAccessible(true);
308
309            int methodSize = 0;
310            for (SmaliInstruction instruction: smaliMethod.getInstructions()) {
311                methodSize += instruction.getInstructionSize();
312            }
313            Location endLocation = null;
314            for (int endCodeIndex = (methodSize/2) - 1; endCodeIndex >= 0; endCodeIndex--) {
315                endLocation = method.locationOfCodeIndex(endCodeIndex);
316                if (endLocation != null) {
317                    break;
318                }
319            }
320            if (endLocation == null) {
321                return null;
322            }
323
324            LocalVariable localVariable = localVariableConstructor.newInstance(vm,
325                    method,
326                    mapRegister(frameProxy.getStackFrame().virtualMachine(), smaliMethod, registerNum),
327                    method.location(),
328                    endLocation,
329                    String.format("v%d", registerNum), type, null);
330
331            return frameProxy.getStackFrame().getValue(localVariable);
332        } catch (NoSuchMethodException e) {
333            return null;
334        } catch (InstantiationException e) {
335            return null;
336        } catch (IllegalAccessException e) {
337            return null;
338        } catch (InvocationTargetException e) {
339            return null;
340        }
341    }
342
343    private static int mapRegister(final VirtualMachine vm, final SmaliMethod smaliMethod, final int register) {
344        if (vm.version().equals("1.5.0")) {
345            return mapRegisterForDalvik(smaliMethod, register);
346        } else {
347            return mapRegisterForArt(smaliMethod, register);
348        }
349    }
350
351    private static int mapRegisterForArt(final SmaliMethod smaliMethod, final int register) {
352        return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
353            @Override public Integer compute() {
354
355                int totalRegisters = smaliMethod.getRegisterCount();
356                int parameterRegisters = smaliMethod.getParameterRegisterCount();
357
358                if (smaliMethod.getModifierList().hasModifierProperty("static")) {
359                    return register;
360                }
361
362                // For ART, the parameter registers are rotated to the front
363                if (register >= (totalRegisters - parameterRegisters)) {
364                    return register - (totalRegisters - parameterRegisters);
365                }
366                return register + parameterRegisters;
367            }
368        });
369    }
370
371    private static int mapRegisterForDalvik(final SmaliMethod smaliMethod, final int register) {
372        return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
373            @Override public Integer compute() {
374                if (smaliMethod.getModifierList().hasModifierProperty("static")) {
375                    return register;
376                }
377
378                int totalRegisters = smaliMethod.getRegisterCount();
379                int parameterRegisters = smaliMethod.getParameterRegisterCount();
380
381                // For dalvik, p0 is mapped to register 1, and register 0 is mapped to register 1000
382                if (register == (totalRegisters - parameterRegisters)) {
383                    return 0;
384                }
385                if (register == 0) {
386                    return 1000;
387                }
388                return register;
389            }
390        });
391    }
392}
393
394