/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.
*/
package org.elasticsearch.painless;
import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.painless.antlr.Walker;
import org.elasticsearch.painless.node.SSource;
import org.objectweb.asm.util.Printer;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;
import java.security.SecureClassLoader;
import java.security.cert.Certificate;
import java.util.BitSet;
import java.util.concurrent.atomic.AtomicInteger;
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
/**
* The Compiler is the entry point for generating a Painless script. The compiler will receive a Painless
* tree based on the type of input passed in (currently only ANTLR). Two passes will then be run over the tree,
* one for analysis and another to generate the actual byte code using ASM using the root of the tree {@link SSource}.
*/
final class Compiler {
/**
* The maximum number of characters allowed in the script source.
*/
static final int MAXIMUM_SOURCE_LENGTH = 16384;
/**
* Define the class with lowest privileges.
*/
private static final CodeSource CODESOURCE;
/**
* Setup the code privileges.
*/
static {
try {
// Setup the code privileges.
CODESOURCE = new CodeSource(new URL("file:" + BootstrapInfo.UNTRUSTED_CODEBASE), (Certificate[]) null);
} catch (MalformedURLException impossible) {
throw new RuntimeException(impossible);
}
}
/**
* A secure class loader used to define Painless scripts.
*/
static final class Loader extends SecureClassLoader {
private final AtomicInteger lambdaCounter = new AtomicInteger(0);
/**
* @param parent The parent ClassLoader.
*/
Loader(ClassLoader parent) {
super(parent);
}
/**
* Generates a Class object from the generated byte code.
* @param name The name of the class.
* @param bytes The generated byte code.
* @return A Class object extending {@link PainlessScript}.
*/
Class<? extends PainlessScript> defineScript(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length, CODESOURCE).asSubclass(PainlessScript.class);
}
/**
* Generates a Class object for a lambda method.
* @param name The name of the class.
* @param bytes The generated byte code.
* @return A Class object.
*/
Class<?> defineLambda(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length, CODESOURCE);
}
/**
* A counter used to generate a unique name for each lambda
* function/reference class in this classloader.
*/
int newLambdaIdentifier() {
return lambdaCounter.getAndIncrement();
}
}
/**
* Runs the two-pass compiler to generate a Painless script.
* @param <T> the type of the script
* @param loader The ClassLoader used to define the script.
* @param iface Interface the compiled script should implement
* @param name The name of the script.
* @param source The source code for the script.
* @param settings The CompilerSettings to be used during the compilation.
* @return An executable script that implements both {@code <T>} and is a subclass of {@link PainlessScript}
*/
static <T> T compile(Loader loader, Class<T> iface, String name, String source, CompilerSettings settings) {
if (source.length() > MAXIMUM_SOURCE_LENGTH) {
throw new IllegalArgumentException("Scripts may be no longer than " + MAXIMUM_SOURCE_LENGTH +
" characters. The passed in script is " + source.length() + " characters. Consider using a" +
" plugin if a script longer than this length is a requirement.");
}
Definition definition = Definition.BUILTINS;
ScriptInterface scriptInterface = new ScriptInterface(definition, iface);
SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, definition,
null);
root.analyze(definition);
root.write();
try {
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, root.getBytes());
clazz.getField("$DEFINITION").set(null, definition);
java.lang.reflect.Constructor<? extends PainlessScript> constructor =
clazz.getConstructor(String.class, String.class, BitSet.class);
return iface.cast(constructor.newInstance(name, source, root.getStatements()));
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
throw new IllegalStateException("An internal error occurred attempting to define the script [" + name + "].", exception);
}
}
/**
* Runs the two-pass compiler to generate a Painless script. (Used by the debugger.)
* @param iface Interface the compiled script should implement
* @param source The source code for the script.
* @param settings The CompilerSettings to be used during the compilation.
* @return The bytes for compilation.
*/
static byte[] compile(Class<?> iface, String name, String source, CompilerSettings settings, Printer debugStream) {
if (source.length() > MAXIMUM_SOURCE_LENGTH) {
throw new IllegalArgumentException("Scripts may be no longer than " + MAXIMUM_SOURCE_LENGTH +
" characters. The passed in script is " + source.length() + " characters. Consider using a" +
" plugin if a script longer than this length is a requirement.");
}
Definition definition = Definition.BUILTINS;
ScriptInterface scriptInterface = new ScriptInterface(definition, iface);
SSource root = Walker.buildPainlessTree(scriptInterface, name, source, settings, definition,
debugStream);
root.analyze(definition);
root.write();
return root.getBytes();
}
/**
* All methods in the compiler should be static.
*/
private Compiler() {}
}