summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
blob: 6d926106287623a38a0f0ce408677660863f2688 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*******************************************************************************
 * Copyright (c) 2015 Jeff Martin.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public
 * License v3.0 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Contributors:
 * Jeff Martin - initial API and implementation
 ******************************************************************************/
package cuchaz.enigma.bytecode;

import com.google.common.collect.Lists;

import java.util.Collection;
import java.util.List;

import cuchaz.enigma.analysis.JarIndex;
import cuchaz.enigma.mapping.BehaviorEntry;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.EntryFactory;
import javassist.CtClass;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.ConstPool;
import javassist.bytecode.EnclosingMethodAttribute;
import javassist.bytecode.InnerClassesAttribute;

public class InnerClassWriter {

    private JarIndex index;

    public InnerClassWriter(JarIndex index) {
        this.index = index;
    }

    public void write(CtClass c) {

        // don't change anything if there's already an attribute there
        InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
        if (oldAttr != null) {
            // bail!
            return;
        }

        ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
        List<ClassEntry> obfClassChain = this.index.getObfClassChain(obfClassEntry);

        boolean isInnerClass = obfClassChain.size() > 1;
        if (isInnerClass) {

            // it's an inner class, rename it to the fully qualified name
            c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());

            BehaviorEntry caller = this.index.getAnonymousClassCaller(obfClassEntry);
            if (caller != null) {

                // write the enclosing method attribute
                if (caller.getName().equals("<clinit>")) {
                    c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
                } else {
                    c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
                }
            }
        }

        // does this class have any inner classes?
        Collection<ClassEntry> obfInnerClassEntries = this.index.getInnerClasses(obfClassEntry);

        if (isInnerClass || !obfInnerClassEntries.isEmpty()) {

            // create an inner class attribute
            InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
            c.getClassFile().addAttribute(attr);

            // write the ancestry, but not the outermost class
            for (int i = 1; i < obfClassChain.size(); i++) {
                ClassEntry obfInnerClassEntry = obfClassChain.get(i);
                writeInnerClass(attr, obfClassChain, obfInnerClassEntry);

                // update references to use the fully qualified inner class name
                c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
            }

            // write the inner classes
            for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {

                // extend the class chain
                List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
                extendedObfClassChain.add(obfInnerClassEntry);

                writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);

                // update references to use the fully qualified inner class name
                c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
            }
        }
    }

    private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {

        // get the new inner class name
        ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
        ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();

        // here's what the JVM spec says about the InnerClasses attribute
        // append(inner, parent, 0 if anonymous else simple name, flags);

        // update the attribute with this inner class
        ConstPool constPool = attr.getConstPool();
        int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
        int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
        int innerClassNameIndex = 0;
        int accessFlags = AccessFlag.PUBLIC;
        // TODO: need to figure out if we can put static or not
        if (!this.index.isAnonymousClass(obfClassEntry)) {
            innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
        }

        attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);

		/* DEBUG 
        System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
			obfClassEntry,
			attr.innerClass(attr.tableLength() - 1),
			attr.outerClass(attr.tableLength() - 1),
			attr.innerName(attr.tableLength() - 1),
			Constants.NonePackage + "/" + obfInnerClassName,
			obfClassEntry.getName()
		));
		*/
    }
}