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.ASM4, 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     * Convenient {@link ClassVisitor} layer to write classfiles into the {@link WeaveEnvironment}.
078     */
079    class WriteClass extends PrivilizerClassVisitor {
080        WriteClass(final ClassWriter classWriter) {
081            super(classWriter);
082        }
083
084        @Override
085        public void visitEnd() {
086            super.visitEnd();
087            final byte[] bytecode = ((ClassWriter) cv).toByteArray();
088
089            if (verify) {
090                verify(className, bytecode);
091            }
092
093            final DataSource classfile = env.getClassfile(className);
094            env.debug("Writing class %s to resource %s", className, classfile.getName());
095            OutputStream outputStream = null;
096            try {
097                outputStream = classfile.getOutputStream();
098                IOUtils.write(bytecode, outputStream);
099            } catch (final IOException e) {
100                throw new RuntimeException(e);
101            } finally {
102                IOUtils.closeQuietly(outputStream);
103            }
104        }
105    }
106
107    /**
108     * Privilizer weaver configuration prefix.
109     */
110    public static final String CONFIG_WEAVER = "privilizer.";
111
112    /**
113     * {@link AccessLevel} configuration key.
114     * @see AccessLevel#parse(String)
115     */
116    public static final String CONFIG_ACCESS_LEVEL = CONFIG_WEAVER + "accessLevel";
117
118    /**
119     * Weave {@link Policy} configuration key.
120     * @see Policy#parse(String)
121     */
122    public static final String CONFIG_POLICY = CONFIG_WEAVER + "policy";
123
124    /**
125     * Verification configuration key.
126     * @see BooleanUtils#toBoolean(String)
127     */
128    public static final String CONFIG_VERIFY = CONFIG_WEAVER + "verify";
129
130    private static final String GENERATE_NAME = "__privileged_%s";
131
132    static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
133
134    final WeaveEnvironment env;
135    final AccessLevel accessLevel;
136    final Policy policy;
137    final boolean verify;
138
139    /**
140     * Create a new {@link Privilizer}.
141     * @param env to use
142     */
143    public Privilizer(final WeaveEnvironment env) {
144        super();
145        this.env = env;
146        this.policy = Policy.parse(env.config.getProperty(CONFIG_POLICY));
147        this.accessLevel = AccessLevel.parse(env.config.getProperty(CONFIG_ACCESS_LEVEL));
148        verify = BooleanUtils.toBoolean(env.config.getProperty(CONFIG_VERIFY));
149    }
150
151    String generateName(final String simple) {
152        return String.format(GENERATE_NAME, simple);
153    }
154
155    Type wrap(final Type type) {
156        switch (type.getSort()) {
157        case Type.BOOLEAN:
158            return Type.getType(Boolean.class);
159        case Type.BYTE:
160            return Type.getType(Byte.class);
161        case Type.SHORT:
162            return Type.getType(Short.class);
163        case Type.INT:
164            return Type.getType(Integer.class);
165        case Type.CHAR:
166            return Type.getType(Character.class);
167        case Type.LONG:
168            return Type.getType(Long.class);
169        case Type.FLOAT:
170            return Type.getType(Float.class);
171        case Type.DOUBLE:
172            return Type.getType(Double.class);
173        case Type.VOID:
174            return Type.getType(Void.class);
175        default:
176            return type;
177        }
178    }
179
180    void blueprint(final Class<?> type, final Privilizing privilizing) {
181        final Object[] args = { type.getName(), privilizing };
182        env.debug("blueprinting class %s %s", args);
183        InputStream bytecode = null;
184        try {
185            bytecode = env.getClassfile(type).getInputStream();
186            final ClassReader classReader = new ClassReader(bytecode);
187
188            ClassVisitor cvr;
189            cvr = new WriteClass(new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS));
190            cvr = new PrivilizingVisitor(this, cvr);
191            cvr = new BlueprintingVisitor(this, cvr, privilizing);
192
193            classReader.accept(cvr, ClassReader.EXPAND_FRAMES);
194        } catch (final Exception e) {
195            throw new RuntimeException(e);
196        } finally {
197            IOUtils.closeQuietly(bytecode);
198        }
199    }
200
201    void privilize(final Class<?> type) {
202        final Object[] args = { type.getName() };
203        env.debug("privilizing class %s", args);
204        InputStream bytecode = null;
205        try {
206            bytecode = env.getClassfile(type).getInputStream();
207            final ClassReader classReader = new ClassReader(bytecode);
208            ClassVisitor cv; //NOPMD
209            cv = new WriteClass(new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS));
210            cv = new PrivilizingVisitor(this, cv);
211
212            classReader.accept(cv, ClassReader.EXPAND_FRAMES);
213        } catch (final Exception e) {
214            throw new RuntimeException(e);
215        } finally {
216            IOUtils.closeQuietly(bytecode);
217        }
218    }
219
220    void verify(final String className, final byte[] bytecode) {
221        final ClassReader reader = new ClassReader(bytecode);
222
223        env.debug("Verifying bytecode for class %s", className);
224        final StringWriter w = new StringWriter(); //NOPMD
225        CheckClassAdapter.verify(reader, env.classLoader, false, new PrintWriter(w));
226        final String error = w.toString();
227        if (!error.isEmpty()) {
228            env.error(error);
229            final StringWriter trace = new StringWriter();
230            reader.accept(new TraceClassVisitor(new PrintWriter(trace)), ClassReader.SKIP_DEBUG);
231            env.debug(trace.toString());
232            throw new IllegalStateException();
233        }
234        Validate.validState(StringUtils.isBlank(error), error);
235
236        final ClassVisitor checkInnerClasses = new ClassVisitor(Opcodes.ASM4, null) {
237            final Set<String> innerNames = new HashSet<String>();
238
239            @Override
240            public void visitInnerClass(final String name, final String outerName, final String innerName,
241                final int access) {
242                super.visitInnerClass(name, outerName, innerName, access);
243                Validate.validState(innerNames.add(innerName), "%s already defined", innerName);
244            }
245        };
246        reader.accept(checkInnerClasses, ClassReader.SKIP_CODE);
247    }
248}