001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.weaver.privilizer;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.PrintWriter;
025import java.io.StringWriter;
026import java.util.HashSet;
027import java.util.Set;
028
029import javax.activation.DataSource;
030
031import org.apache.commons.io.IOUtils;
032import org.apache.commons.lang3.BooleanUtils;
033import org.apache.commons.lang3.StringUtils;
034import org.apache.commons.lang3.Validate;
035import org.apache.commons.weaver.model.WeaveEnvironment;
036import org.objectweb.asm.ClassReader;
037import org.objectweb.asm.ClassVisitor;
038import org.objectweb.asm.ClassWriter;
039import org.objectweb.asm.Opcodes;
040import org.objectweb.asm.Type;
041import org.objectweb.asm.util.CheckClassAdapter;
042import org.objectweb.asm.util.TraceClassVisitor;
043
044/**
045 * Coordinates privilization activities.
046 */
047public class Privilizer {
048    /**
049     * An ASM {@link ClassVisitor} for privilization.
050     */
051    abstract class PrivilizerClassVisitor extends ClassVisitor {
052        String className;
053        Type target;
054
055        protected PrivilizerClassVisitor() {
056            this(null);
057        }
058
059        protected PrivilizerClassVisitor(final ClassVisitor cv) { //NOPMD
060            super(Opcodes.ASM5, cv);
061        }
062
063        protected Privilizer privilizer() {
064            return Privilizer.this;
065        }
066
067        @Override
068        public void visit(final int version, final int access, final String name, final String signature,
069            final String superName, final String[] interfaces) {
070            super.visit(version, access, name, signature, superName, interfaces);
071            className = name;
072            target = Type.getObjectType(name);
073        }
074    }
075
076    /**
077     * Necessary to resolve supertypes against WeaveEnvironment ClassLoader.
078     */
079    private final class CustomClassWriter extends ClassWriter {
080        CustomClassWriter(final int flags) {
081            super(flags);
082        }
083
084        CustomClassWriter(final ClassReader classReader, final int flags) {
085            super(classReader, flags);
086        }
087
088        @Override
089        protected String getCommonSuperClass(final String type1, final String type2) {
090            Class<?> class1;
091            Class<?> class2;
092            try {
093                class1 = Class.forName(type1.replace('/', '.'), false, env.classLoader);
094                class2 = Class.forName(type2.replace('/', '.'), false, env.classLoader);
095            } catch (Exception e) {
096                throw new RuntimeException(e.toString());
097            }
098            if (class1.isAssignableFrom(class2)) {
099                return type1;
100            }
101            if (class2.isAssignableFrom(class1)) {
102                return type2;
103            }
104            if (class1.isInterface() || class2.isInterface()) {
105                return "java/lang/Object";
106            }
107            do {
108                class1 = class1.getSuperclass();
109            } while (!class1.isAssignableFrom(class2));
110            return class1.getName().replace('.', '/');
111        }
112    }
113
114    /**
115     * Convenient {@link ClassVisitor} layer to write classfiles into the {@link WeaveEnvironment}.
116     */
117    class WriteClass extends PrivilizerClassVisitor {
118
119        WriteClass(final ClassReader classReader, final int flags) {
120            super(new CustomClassWriter(classReader, flags));
121        }
122
123        WriteClass(final int flags) {
124            super(new CustomClassWriter(flags));
125        }
126
127        @Override
128        public void visitEnd() {
129            super.visitEnd();
130            final byte[] bytecode = ((ClassWriter) cv).toByteArray();
131
132            if (verify) {
133                verify(className, bytecode);
134            }
135
136            final DataSource classfile = env.getClassfile(className);
137            env.debug("Writing class %s to resource %s", className, classfile.getName());
138            OutputStream outputStream = null;
139            try {
140                outputStream = classfile.getOutputStream();
141                IOUtils.write(bytecode, outputStream);
142            } catch (final IOException e) {
143                throw new RuntimeException(e);
144            } finally {
145                IOUtils.closeQuietly(outputStream);
146            }
147        }
148    }
149
150    /**
151     * Privilizer weaver configuration prefix.
152     */
153    public static final String CONFIG_WEAVER = "privilizer.";
154
155    /**
156     * {@link AccessLevel} configuration key.
157     * @see AccessLevel#parse(String)
158     */
159    public static final String CONFIG_ACCESS_LEVEL = CONFIG_WEAVER + "accessLevel";
160
161    /**
162     * Weave {@link Policy} configuration key.
163     * @see Policy#parse(String)
164     */
165    public static final String CONFIG_POLICY = CONFIG_WEAVER + "policy";
166
167    /**
168     * Verification configuration key.
169     * @see BooleanUtils#toBoolean(String)
170     */
171    public static final String CONFIG_VERIFY = CONFIG_WEAVER + "verify";
172
173    private static final String GENERATE_NAME = "__privileged_%s";
174
175    static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
176
177    final WeaveEnvironment env;
178    final AccessLevel accessLevel;
179    final Policy policy;
180    final boolean verify;
181
182    /**
183     * Create a new {@link Privilizer}.
184     * @param env to use
185     */
186    public Privilizer(final WeaveEnvironment env) {
187        super();
188        this.env = env;
189        this.policy = Policy.parse(env.config.getProperty(CONFIG_POLICY));
190        this.accessLevel = AccessLevel.parse(env.config.getProperty(CONFIG_ACCESS_LEVEL));
191        verify = BooleanUtils.toBoolean(env.config.getProperty(CONFIG_VERIFY));
192    }
193
194    String generateName(final String simple) {
195        return String.format(GENERATE_NAME, simple);
196    }
197
198    Type wrap(final Type type) {
199        switch (type.getSort()) {
200        case Type.BOOLEAN:
201            return Type.getType(Boolean.class);
202        case Type.BYTE:
203            return Type.getType(Byte.class);
204        case Type.SHORT:
205            return Type.getType(Short.class);
206        case Type.INT:
207            return Type.getType(Integer.class);
208        case Type.CHAR:
209            return Type.getType(Character.class);
210        case Type.LONG:
211            return Type.getType(Long.class);
212        case Type.FLOAT:
213            return Type.getType(Float.class);
214        case Type.DOUBLE:
215            return Type.getType(Double.class);
216        case Type.VOID:
217            return Type.getType(Void.class);
218        default:
219            return type;
220        }
221    }
222
223    void blueprint(final Class<?> type, final Privilizing privilizing) {
224        final Object[] args = { type.getName(), privilizing };
225        env.debug("blueprinting class %s %s", args);
226        InputStream bytecode = null;
227        try {
228            bytecode = env.getClassfile(type).getInputStream();
229            final ClassReader classReader = new ClassReader(bytecode);
230
231            ClassVisitor cvr;
232            cvr = new WriteClass(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
233            cvr = new PrivilizingVisitor(this, cvr);
234            cvr = new BlueprintingVisitor(this, cvr, privilizing);
235
236            classReader.accept(cvr, ClassReader.EXPAND_FRAMES);
237        } catch (final Exception e) {
238            throw new RuntimeException(e);
239        } finally {
240            IOUtils.closeQuietly(bytecode);
241        }
242    }
243
244    void privilize(final Class<?> type) {
245        final Object[] args = { type.getName() };
246        env.debug("privilizing class %s", args);
247        InputStream bytecode = null;
248        try {
249            bytecode = env.getClassfile(type).getInputStream();
250            final ClassReader classReader = new ClassReader(bytecode);
251            ClassVisitor cv; //NOPMD
252            cv = new WriteClass(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
253            cv = new PrivilizingVisitor(this, cv);
254
255            classReader.accept(cv, ClassReader.EXPAND_FRAMES);
256        } catch (final Exception e) {
257            throw new RuntimeException(e);
258        } finally {
259            IOUtils.closeQuietly(bytecode);
260        }
261    }
262
263    void verify(final String className, final byte[] bytecode) {
264        final ClassReader reader = new ClassReader(bytecode);
265
266        env.debug("Verifying bytecode for class %s", className);
267        final StringWriter w = new StringWriter(); //NOPMD
268        CheckClassAdapter.verify(reader, env.classLoader, false, new PrintWriter(w));
269        final String error = w.toString();
270        if (!error.isEmpty()) {
271            env.error(error);
272            final StringWriter trace = new StringWriter();
273            reader.accept(new TraceClassVisitor(new PrintWriter(trace)), ClassReader.SKIP_DEBUG);
274            env.debug(trace.toString());
275            throw new IllegalStateException();
276        }
277        Validate.validState(StringUtils.isBlank(error), error);
278
279        final ClassVisitor checkInnerClasses = new ClassVisitor(Opcodes.ASM5, null) {
280            final Set<String> innerNames = new HashSet<String>();
281
282            @Override
283            public void visitInnerClass(final String name, final String outerName, final String innerName,
284                final int access) {
285                super.visitInnerClass(name, outerName, innerName, access);
286                Validate.validState(innerNames.add(innerName), "%s already defined", innerName);
287            }
288        };
289        reader.accept(checkInnerClasses, ClassReader.SKIP_CODE);
290    }
291}