View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.weaver.privilizer;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.io.PrintWriter;
25  import java.io.StringWriter;
26  import java.util.HashSet;
27  import java.util.Set;
28  
29  import org.apache.commons.lang3.BooleanUtils;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.lang3.Validate;
32  import org.apache.commons.weaver.model.WeaveEnvironment;
33  import org.objectweb.asm.ClassReader;
34  import org.objectweb.asm.ClassVisitor;
35  import org.objectweb.asm.ClassWriter;
36  import org.objectweb.asm.Opcodes;
37  import org.objectweb.asm.Type;
38  import org.objectweb.asm.util.CheckClassAdapter;
39  import org.objectweb.asm.util.TraceClassVisitor;
40  
41  /**
42   * Coordinates privilization activities.
43   */
44  public class Privilizer {
45      /**
46       * An ASM {@link ClassVisitor} for privilization.
47       */
48      abstract class PrivilizerClassVisitor extends ClassVisitor {
49          String className;
50          Type target;
51  
52          protected PrivilizerClassVisitor() {
53              this(null);
54          }
55  
56          protected PrivilizerClassVisitor(final ClassVisitor cv) { //NOPMD
57              super(ASM_VERSION, cv);
58          }
59  
60          protected Privilizer privilizer() {
61              return Privilizer.this;
62          }
63  
64          @Override
65          @SuppressWarnings("PMD.UseVarargs") //overridden method
66          public void visit(final int version, final int access, final String name, final String signature,
67              final String superName, final String[] interfaces) {
68              super.visit(version, access, name, signature, superName, interfaces);
69              className = name;
70              target = Type.getObjectType(name);
71          }
72      }
73  
74      private final class CustomClassWriter extends ClassWriter {
75          CustomClassWriter(final int flags) {
76              super(flags);
77          }
78  
79          CustomClassWriter(final ClassReader classReader, final int flags) {
80              super(classReader, flags);
81          }
82  
83          @Override
84          protected ClassLoader getClassLoader() {
85              return env.classLoader;
86          }
87      }
88  
89      /**
90       * Convenient {@link ClassVisitor} layer to write classfiles into the {@link WeaveEnvironment}.
91       */
92      class WriteClass extends PrivilizerClassVisitor {
93  
94          WriteClass(final ClassReader classReader, final int flags) {
95              super(new CustomClassWriter(classReader, flags));
96          }
97  
98          WriteClass(final int flags) {
99              super(new CustomClassWriter(flags));
100         }
101 
102         @Override
103         public void visitEnd() {
104             super.visitEnd();
105             final byte[] bytecode = ((ClassWriter) cv).toByteArray();
106 
107             if (verify) {
108                 verify(className, bytecode);
109             }
110             final WeaveEnvironment.Resource classfile = env.getClassfile(className);
111             env.debug("Writing class %s to resource %s", className, classfile.getName());
112             try (OutputStream outputStream = classfile.getOutputStream()) {
113                 outputStream.write(bytecode);
114             } catch (final IOException e) {
115                 throw new IllegalStateException(e);
116             }
117         }
118     }
119 
120     /**
121      * Privilizer weaver configuration prefix.
122      */
123     public static final String CONFIG_WEAVER = "privilizer.";
124 
125     /**
126      * {@link AccessLevel} configuration key.
127      * @see AccessLevel#parse(String)
128      */
129     public static final String CONFIG_ACCESS_LEVEL = CONFIG_WEAVER + "accessLevel";
130 
131     /**
132      * Weave {@link Policy} configuration key.
133      * @see Policy#parse(String)
134      */
135     public static final String CONFIG_POLICY = CONFIG_WEAVER + "policy";
136 
137     /**
138      * Verification configuration key.
139      * @see BooleanUtils#toBoolean(String)
140      */
141     public static final String CONFIG_VERIFY = CONFIG_WEAVER + "verify";
142 
143     private static final String GENERATE_NAME = "__privileged_%s";
144 
145     static final int ASM_VERSION = Opcodes.ASM6;
146     static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
147 
148     static Type wrap(final Type type) {
149         switch (type.getSort()) {
150         case Type.BOOLEAN:
151             return Type.getType(Boolean.class);
152         case Type.BYTE:
153             return Type.getType(Byte.class);
154         case Type.SHORT:
155             return Type.getType(Short.class);
156         case Type.INT:
157             return Type.getType(Integer.class);
158         case Type.CHAR:
159             return Type.getType(Character.class);
160         case Type.LONG:
161             return Type.getType(Long.class);
162         case Type.FLOAT:
163             return Type.getType(Float.class);
164         case Type.DOUBLE:
165             return Type.getType(Double.class);
166         case Type.VOID:
167             return Type.getType(Void.class);
168         default:
169             return type;
170         }
171     }
172 
173     final WeaveEnvironment env;
174     final AccessLevel accessLevel;
175     final Policy policy;
176     final boolean verify;
177 
178     /**
179      * Create a new {@link Privilizer}.
180      * @param env to use
181      */
182     public Privilizer(final WeaveEnvironment env) {
183         super();
184         this.env = env;
185         this.policy = Policy.parse(env.config.getProperty(CONFIG_POLICY));
186         this.accessLevel = AccessLevel.parse(env.config.getProperty(CONFIG_ACCESS_LEVEL));
187         verify = BooleanUtils.toBoolean(env.config.getProperty(CONFIG_VERIFY));
188     }
189 
190     String generateName(final String simple) {
191         return String.format(GENERATE_NAME, simple);
192     }
193 
194     void blueprint(final Class<?> type, final Privilizing privilizing) {
195         final Object[] args = { type.getName(), privilizing };
196         env.debug("blueprinting class %s %s", args);
197         try (InputStream bytecode = env.getClassfile(type).getInputStream()) {
198             final ClassReader classReader = new ClassReader(bytecode);
199 
200             ClassVisitor cvr;
201             cvr = new WriteClass(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
202             cvr = new PrivilizingVisitor(this, cvr);
203             cvr = new BlueprintingVisitor(this, cvr, privilizing);
204 
205             classReader.accept(cvr, ClassReader.EXPAND_FRAMES);
206         } catch (final Exception e) {
207             throw new IllegalStateException(e);
208         }
209     }
210 
211     void privilize(final Class<?> type) {
212         final Object[] args = { type.getName() };
213         env.debug("privilizing class %s", args);
214         try (InputStream bytecode = env.getClassfile(type).getInputStream()) {
215             final ClassReader classReader = new ClassReader(bytecode);
216             ClassVisitor cv; // NOPMD
217             cv = new WriteClass(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
218             cv = new PrivilizingVisitor(this, cv);
219 
220             classReader.accept(cv, ClassReader.EXPAND_FRAMES);
221         } catch (final Exception e) {
222             throw new IllegalStateException(e);
223         }
224     }
225 
226     void verify(final String className, final byte[] bytecode) {
227         final ClassReader reader = new ClassReader(bytecode);
228 
229         env.debug("Verifying bytecode for class %s", className);
230         final StringWriter w = new StringWriter(); //NOPMD
231         CheckClassAdapter.verify(reader, env.classLoader, false, new PrintWriter(w));
232         final String error = w.toString();
233         if (!error.isEmpty()) {
234             env.error(error);
235             final StringWriter trace = new StringWriter();
236             reader.accept(new TraceClassVisitor(new PrintWriter(trace)), ClassReader.SKIP_DEBUG);
237             env.debug(trace.toString());
238             throw new IllegalStateException();
239         }
240         Validate.validState(StringUtils.isBlank(error), error);
241 
242         final ClassVisitor checkInnerClasses = new ClassVisitor(ASM_VERSION, null) {
243             final Set<String> innerNames = new HashSet<>();
244 
245             @Override
246             public void visitInnerClass(final String name, final String outerName, final String innerName,
247                 final int access) {
248                 super.visitInnerClass(name, outerName, innerName, access);
249                 Validate.validState(innerNames.add(innerName), "%s already defined", innerName);
250             }
251         };
252         reader.accept(checkInnerClasses, ClassReader.SKIP_CODE);
253     }
254 }