blob: d1a5659bc86830e2499d356fafc2ba0b983ddaf6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.MultipleCompilationErrorsException
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.codehaus.groovy.control.customizers.SecureASTCustomizer
import org.codehaus.groovy.control.messages.ExceptionMessage
import static org.codehaus.groovy.syntax.Types.*
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.expr.BinaryExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression
import org.codehaus.groovy.ast.expr.ArgumentListExpression
import org.codehaus.groovy.ast.expr.PropertyExpression
import org.codehaus.groovy.ast.expr.UnaryMinusExpression
import org.codehaus.groovy.ast.expr.UnaryPlusExpression
import org.codehaus.groovy.ast.expr.PrefixExpression
import org.codehaus.groovy.ast.expr.PostfixExpression
import org.codehaus.groovy.ast.expr.TernaryExpression
import org.codehaus.groovy.ast.expr.ElvisOperatorExpression
import org.codehaus.groovy.ast.expr.BooleanExpression
import org.codehaus.groovy.ast.expr.ClassExpression
/**
* The arithmetic shell is similar to a GroovyShell in that it can evaluate text as
* code and return a result. It is not a subclass of GroovyShell because it does not widen
* the contract of GroovyShell, instead it narrows it. Using one of these shells like a
* GroovyShell would result in many runtime errors.
*
* @author Hamlet D'Arcy (hamletdrc@gmail.com)
* @author Guillaume Laforge
*/
class ArithmeticShell {
/**
* Compiles the text into a Groovy object and then executes it, returning the result.
* @param text
* the script to evaluate typed as a string
* @throws SecurityException
* most likely the script is doing something other than arithmetic
* @throws IllegalStateException
* if the script returns something other than a number
*/
Number evaluate(String text) {
try {
final ImportCustomizer imports = new ImportCustomizer().addStaticStars('java.lang.Math') // add static import of java.lang.Math
final SecureASTCustomizer secure = new SecureASTCustomizer()
secure.with {
closuresAllowed = false
methodDefinitionAllowed = false
importsWhitelist = []
staticImportsWhitelist = []
staticStarImportsWhitelist = ['java.lang.Math'] // only java.lang.Math is allowed
tokensWhitelist = [
PLUS,
MINUS,
MULTIPLY,
DIVIDE,
MOD,
POWER,
PLUS_PLUS,
MINUS_MINUS,
COMPARE_EQUAL,
COMPARE_NOT_EQUAL,
COMPARE_LESS_THAN,
COMPARE_LESS_THAN_EQUAL,
COMPARE_GREATER_THAN,
COMPARE_GREATER_THAN_EQUAL,
].asImmutable()
constantTypesClassesWhiteList = [
Integer,
Float,
Long,
Double,
BigDecimal,
Integer.TYPE,
Long.TYPE,
Float.TYPE,
Double.TYPE
].asImmutable()
receiversClassesWhiteList = [
Math,
Integer,
Float,
Double,
Long,
BigDecimal
].asImmutable()
statementsWhitelist = [
BlockStatement,
ExpressionStatement
].asImmutable()
expressionsWhitelist = [
BinaryExpression,
ConstantExpression,
MethodCallExpression,
StaticMethodCallExpression,
ArgumentListExpression,
PropertyExpression,
UnaryMinusExpression,
UnaryPlusExpression,
PrefixExpression,
PostfixExpression,
TernaryExpression,
ElvisOperatorExpression,
BooleanExpression,
// ClassExpression needed for processing of MethodCallExpression, PropertyExpression
// and StaticMethodCallExpression
ClassExpression
].asImmutable()
}
CompilerConfiguration config = new CompilerConfiguration()
config.addCompilationCustomizers(imports, secure)
GroovyClassLoader loader = new GroovyClassLoader(this.class.classLoader, config)
Class clazz = loader.parseClass(text)
Script script = (Script) clazz.newInstance();
Object result = script.run()
if (!(result instanceof Number)) throw new IllegalStateException("Script returned a non-number: $result");
return (Number) result
} catch (SecurityException ex) {
throw new SecurityException("Could not evaluate script: $text", ex)
} catch (MultipleCompilationErrorsException mce) {
//this allows compilation errors to be seen by the user
mce.errorCollector.errors.each {
if (it instanceof ExceptionMessage && it.cause instanceof SecurityException) {
throw it.cause
}
}
throw mce
}
}
}