blob: 27b0a3a86450bfc456e9db5ee53379b92a72af98 [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 groovy.test.GroovyTestCase
import groovy.transform.ConditionalInterrupt
import groovy.util.logging.Log
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.ast.expr.AttributeExpression
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.codehaus.groovy.control.customizers.SecureASTCustomizer
import org.codehaus.groovy.control.customizers.SourceAwareCustomizer
import org.codehaus.groovy.control.CompilerConfiguration
import static org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder.withConfig
import static org.codehaus.groovy.syntax.Types.*
class CustomizersTest extends GroovyTestCase {
private CompilerConfiguration config;
private GroovyShell shell;
void setUp() {
config = new CompilerConfiguration()
shell = new GroovyShell(config)
}
@Override
protected void assertScript(final String script) throws Exception {
shell.evaluate(script, getTestClassName())
}
void testImportCustomizer() {
// tag::import_cz[]
def icz = new ImportCustomizer()
// "normal" import
icz.addImports('java.util.concurrent.atomic.AtomicInteger', 'java.util.concurrent.ConcurrentHashMap')
// "aliases" import
icz.addImport('CHM', 'java.util.concurrent.ConcurrentHashMap')
// "static" import
icz.addStaticImport('java.lang.Math', 'PI') // import static java.lang.Math.PI
// "aliased static" import
icz.addStaticImport('pi', 'java.lang.Math', 'PI') // import static java.lang.Math.PI as pi
// "star" import
icz.addStarImports 'java.util.concurrent' // import java.util.concurrent.*
// "static star" import
icz.addStaticStars 'java.lang.Math' // import static java.lang.Math.*
// end::import_cz[]
config.addCompilationCustomizers(icz)
assertScript '''
def a = new AtomicInteger(123)
def map = new CHM([:])
assert PI == Math.PI
assert pi == PI
def c = {} as Callable
assert cos(0) == 1
'''
}
void testLogCustomizer() {
// tag::ast_cz_simple[]
def acz = new ASTTransformationCustomizer(Log)
config.addCompilationCustomizers(acz)
// end::ast_cz_simple[]
assertScript '''
log.info "It works!"
'''
}
void testLogCustomizerWithCustomName() {
// tag::ast_cz_customname[]
def acz = new ASTTransformationCustomizer(Log, value: 'LOGGER')
// use name 'LOGGER' instead of the default 'log'
config.addCompilationCustomizers(acz)
// end::ast_cz_customname[]
assertScript '''
LOGGER.info "It works!"
'''
}
void testAstTransformationCustomizerWithClosureExpression() {
// tag::ast_cz_closure[]
def configuration = new CompilerConfiguration()
def expression = new AstBuilder().buildFromCode(CompilePhase.CONVERSION) { -> true }.expression[0]
def customizer = new ASTTransformationCustomizer(ConditionalInterrupt, value: expression, thrown: SecurityException)
configuration.addCompilationCustomizers(customizer)
def shell = new GroovyShell(configuration)
shouldFail(SecurityException) {
shell.evaluate("""
// equivalent to adding @ConditionalInterrupt(value={true}, thrown: SecurityException)
class MyClass {
void doIt() { }
}
new MyClass().doIt()
""")
}
// end::ast_cz_closure[]
}
void testSecureASTCustomizer() {
// tag::secure_cz[]
def scz = new SecureASTCustomizer()
scz.with {
closuresAllowed = false // user will not be able to write closures
methodDefinitionAllowed = false // user will not be able to define methods
importsWhitelist = [] // empty whitelist means imports are disallowed
staticImportsWhitelist = [] // same for static imports
staticStarImportsWhitelist = ['java.lang.Math'] // only java.lang.Math is allowed
// the list of tokens the user can find
// constants are defined in org.codehaus.groovy.syntax.Types
tokensWhitelist = [ // <1>
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()
// limit the types of constants that a user can define to number types only
constantTypesClassesWhiteList = [ // <2>
Integer,
Float,
Long,
Double,
BigDecimal,
Integer.TYPE,
Long.TYPE,
Float.TYPE,
Double.TYPE
].asImmutable()
// method calls are only allowed if the receiver is of one of those types
// be careful, it's not a runtime type!
receiversClassesWhiteList = [ // <2>
Math,
Integer,
Float,
Double,
Long,
BigDecimal
].asImmutable()
}
// end::secure_cz[]
config.addCompilationCustomizers(scz)
assertScript '''
1+1
'''
shouldFail {
assertScript '''
println "not allowed"
'''
}
}
void testSecureASTCustomizerWithCustomChecker() {
// tag::secure_cz_custom[]
def scz = new SecureASTCustomizer()
def checker = { expr ->
!(expr instanceof AttributeExpression)
} as SecureASTCustomizer.ExpressionChecker
scz.addExpressionCheckers(checker)
// end::secure_cz_custom[]
config.addCompilationCustomizers(scz)
shouldFail {
assertScript '''// tag::secure_cz_custom_assert[]
class A {
int val
}
def a = new A(val: 123)
a.@val // <1>
// end::secure_cz_custom_assert[]
'''
}
}
void testSourceAwareCustomizer() {
// tag::source_cz[]
def delegate = new ImportCustomizer()
def sac = new SourceAwareCustomizer(delegate)
// end::source_cz[]
// tag::source_cz_predicates[]
// the customizer will only be applied to classes contained in a file name ending with 'Bean'
sac.baseNameValidator = { baseName ->
baseName.endsWith 'Bean'
}
// the customizer will only be applied to files which extension is '.spec'
sac.extensionValidator = { ext -> ext == 'spec' }
// source unit validation
// allow compilation only if the file contains at most 1 class
sac.sourceUnitValidator = { SourceUnit sourceUnit -> sourceUnit.AST.classes.size() == 1 }
// class validation
// the customizer will only be applied to classes ending with 'Bean'
sac.classValidator = { ClassNode cn -> cn.endsWith('Bean') }
// end::source_cz_predicates[]
config.addCompilationCustomizers(sac)
}
void testCustomizerBuilder() {
// tag::customizer_withconfig[]
def conf = new CompilerConfiguration()
withConfig(conf) {
// ... <2>
}
// end::customizer_withconfig[]
}
}