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