001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel.standard;
018
019import java.net.URL;
020import java.net.URLClassLoader;
021import java.util.Map;
022import java.util.concurrent.atomic.AtomicInteger;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.asm.ClassWriter;
028import org.springframework.asm.MethodVisitor;
029import org.springframework.asm.Opcodes;
030import org.springframework.expression.Expression;
031import org.springframework.expression.spel.CodeFlow;
032import org.springframework.expression.spel.CompiledExpression;
033import org.springframework.expression.spel.SpelParserConfiguration;
034import org.springframework.expression.spel.ast.SpelNodeImpl;
035import org.springframework.lang.Nullable;
036import org.springframework.util.ClassUtils;
037import org.springframework.util.ConcurrentReferenceHashMap;
038import org.springframework.util.ReflectionUtils;
039import org.springframework.util.StringUtils;
040
041/**
042 * A SpelCompiler will take a regular parsed expression and create (and load) a class
043 * containing byte code that does the same thing as that expression. The compiled form of
044 * an expression will evaluate far faster than the interpreted form.
045 *
046 * <p>The SpelCompiler is not currently handling all expression types but covers many of
047 * the common cases. The framework is extensible to cover more cases in the future. For
048 * absolute maximum speed there is *no checking* in the compiled code. The compiled
049 * version of the expression uses information learned during interpreted runs of the
050 * expression when it generates the byte code. For example if it knows that a particular
051 * property dereference always seems to return a Map then it will generate byte code that
052 * expects the result of the property dereference to be a Map. This ensures maximal
053 * performance but should the dereference result in something other than a map, the
054 * compiled expression will fail - like a ClassCastException would occur if passing data
055 * of an unexpected type in a regular Java program.
056 *
057 * <p>Due to the lack of checking there are likely some expressions that should never be
058 * compiled, for example if an expression is continuously dealing with different types of
059 * data. Due to these cases the compiler is something that must be selectively turned on
060 * for an associated SpelExpressionParser (through the {@link SpelParserConfiguration}
061 * object), it is not on by default.
062 *
063 * <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
064 *
065 * @author Andy Clement
066 * @author Juergen Hoeller
067 * @since 4.1
068 */
069public final class SpelCompiler implements Opcodes {
070
071        private static final int CLASSES_DEFINED_LIMIT = 100;
072
073        private static final Log logger = LogFactory.getLog(SpelCompiler.class);
074
075        // A compiler is created for each classloader, it manages a child class loader of that
076        // classloader and the child is used to load the compiled expressions.
077        private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
078
079
080        // The child ClassLoader used to load the compiled expression classes
081        private ChildClassLoader ccl;
082
083        // Counter suffix for generated classes within this SpelCompiler instance
084        private final AtomicInteger suffixId = new AtomicInteger(1);
085
086
087        private SpelCompiler(@Nullable ClassLoader classloader) {
088                this.ccl = new ChildClassLoader(classloader);
089        }
090
091
092        /**
093         * Attempt compilation of the supplied expression. A check is made to see
094         * if it is compilable before compilation proceeds. The check involves
095         * visiting all the nodes in the expression AST and ensuring enough state
096         * is known about them that bytecode can be generated for them.
097         * @param expression the expression to compile
098         * @return an instance of the class implementing the compiled expression,
099         * or {@code null} if compilation is not possible
100         */
101        @Nullable
102        public CompiledExpression compile(SpelNodeImpl expression) {
103                if (expression.isCompilable()) {
104                        if (logger.isDebugEnabled()) {
105                                logger.debug("SpEL: compiling " + expression.toStringAST());
106                        }
107                        Class<? extends CompiledExpression> clazz = createExpressionClass(expression);
108                        if (clazz != null) {
109                                try {
110                                        return ReflectionUtils.accessibleConstructor(clazz).newInstance();
111                                }
112                                catch (Throwable ex) {
113                                        throw new IllegalStateException("Failed to instantiate CompiledExpression", ex);
114                                }
115                        }
116                }
117
118                if (logger.isDebugEnabled()) {
119                        logger.debug("SpEL: unable to compile " + expression.toStringAST());
120                }
121                return null;
122        }
123
124        private int getNextSuffix() {
125                return this.suffixId.incrementAndGet();
126        }
127
128        /**
129         * Generate the class that encapsulates the compiled expression and define it.
130         * The generated class will be a subtype of CompiledExpression.
131         * @param expressionToCompile the expression to be compiled
132         * @return the expression call, or {@code null} if the decision was to opt out of
133         * compilation during code generation
134         */
135        @Nullable
136        private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
137                // Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
138                String className = "spel/Ex" + getNextSuffix();
139                ClassWriter cw = new ExpressionClassWriter();
140                cw.visit(V1_5, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
141
142                // Create default constructor
143                MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
144                mv.visitCode();
145                mv.visitVarInsn(ALOAD, 0);
146                mv.visitMethodInsn(INVOKESPECIAL, "org/springframework/expression/spel/CompiledExpression",
147                                "<init>", "()V", false);
148                mv.visitInsn(RETURN);
149                mv.visitMaxs(1, 1);
150                mv.visitEnd();
151
152                // Create getValue() method
153                mv = cw.visitMethod(ACC_PUBLIC, "getValue",
154                                "(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
155                                new String[] {"org/springframework/expression/EvaluationException"});
156                mv.visitCode();
157
158                CodeFlow cf = new CodeFlow(className, cw);
159
160                // Ask the expression AST to generate the body of the method
161                try {
162                        expressionToCompile.generateCode(mv, cf);
163                }
164                catch (IllegalStateException ex) {
165                        if (logger.isDebugEnabled()) {
166                                logger.debug(expressionToCompile.getClass().getSimpleName() +
167                                                ".generateCode opted out of compilation: " + ex.getMessage());
168                        }
169                        return null;
170                }
171
172                CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor());
173                if ("V".equals(cf.lastDescriptor())) {
174                        mv.visitInsn(ACONST_NULL);
175                }
176                mv.visitInsn(ARETURN);
177
178                mv.visitMaxs(0, 0);  // not supplied due to COMPUTE_MAXS
179                mv.visitEnd();
180                cw.visitEnd();
181
182                cf.finish();
183
184                byte[] data = cw.toByteArray();
185                // TODO need to make this conditionally occur based on a debug flag
186                // dump(expressionToCompile.toStringAST(), clazzName, data);
187                return loadClass(StringUtils.replace(className, "/", "."), data);
188        }
189
190        /**
191         * Load a compiled expression class. Makes sure the classloaders aren't used too much
192         * because they anchor compiled classes in memory and prevent GC. If you have expressions
193         * continually recompiling over time then by replacing the classloader periodically
194         * at least some of the older variants can be garbage collected.
195         * @param name the name of the class
196         * @param bytes the bytecode for the class
197         * @return the Class object for the compiled expression
198         */
199        @SuppressWarnings("unchecked")
200        private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) {
201                if (this.ccl.getClassesDefinedCount() > CLASSES_DEFINED_LIMIT) {
202                        this.ccl = new ChildClassLoader(this.ccl.getParent());
203                }
204                return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
205        }
206
207
208        /**
209         * Factory method for compiler instances. The returned SpelCompiler will
210         * attach a class loader as the child of the given class loader and this
211         * child will be used to load compiled expressions.
212         * @param classLoader the ClassLoader to use as the basis for compilation
213         * @return a corresponding SpelCompiler instance
214         */
215        public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) {
216                ClassLoader clToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
217                synchronized (compilers) {
218                        SpelCompiler compiler = compilers.get(clToUse);
219                        if (compiler == null) {
220                                compiler = new SpelCompiler(clToUse);
221                                compilers.put(clToUse, compiler);
222                        }
223                        return compiler;
224                }
225        }
226
227        /**
228         * Request that an attempt is made to compile the specified expression.
229         * It may fail if components of the expression are not suitable for compilation
230         * or the data types involved are not suitable for compilation. Used for testing.
231         * @param expression the expression to compile
232         * @return {@code true} if the expression was successfully compiled,
233         * {@code false} otherwise
234         */
235        public static boolean compile(Expression expression) {
236                return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
237        }
238
239        /**
240         * Request to revert to the interpreter for expression evaluation.
241         * Any compiled form is discarded but can be recreated by later recompiling again.
242         * @param expression the expression
243         */
244        public static void revertToInterpreted(Expression expression) {
245                if (expression instanceof SpelExpression) {
246                        ((SpelExpression) expression).revertToInterpreted();
247                }
248        }
249
250
251        /**
252         * A ChildClassLoader will load the generated compiled expression classes.
253         */
254        private static class ChildClassLoader extends URLClassLoader {
255
256                private static final URL[] NO_URLS = new URL[0];
257
258                private int classesDefinedCount = 0;
259
260                public ChildClassLoader(@Nullable ClassLoader classLoader) {
261                        super(NO_URLS, classLoader);
262                }
263
264                public Class<?> defineClass(String name, byte[] bytes) {
265                        Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
266                        this.classesDefinedCount++;
267                        return clazz;
268                }
269
270                public int getClassesDefinedCount() {
271                        return this.classesDefinedCount;
272                }
273        }
274
275
276        /**
277         * An ASM ClassWriter extension bound to the SpelCompiler's ClassLoader.
278         */
279        private class ExpressionClassWriter extends ClassWriter {
280
281                public ExpressionClassWriter() {
282                        super(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
283                }
284
285                @Override
286                protected ClassLoader getClassLoader() {
287                        return ccl;
288                }
289        }
290
291}