All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cloudbees.groovy.cps.CpsTransformer Maven / Gradle / Ivy

There is a newer version: 1.31
Show newest version
package com.cloudbees.groovy.cps;

import com.cloudbees.groovy.cps.impl.CpsCallableInvocation;
import com.cloudbees.groovy.cps.impl.CpsFunction;
import com.cloudbees.groovy.cps.sandbox.Trusted;
import com.cloudbees.groovy.cps.sandbox.Untrusted;
import com.google.common.annotations.VisibleForTesting;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.codehaus.groovy.classgen.BytecodeExpression;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.runtime.powerassert.SourceText;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Token;
import static org.codehaus.groovy.syntax.Types.*;

/**
 * Performs CPS transformation of Groovy methods.
 *
 * 

* Every method not annotated with {@link NonCPS} gets rewritten. The general * strategy of CPS transformation is as follows: * *

* Before: *

 * Object foo(int x, int y) {
 *   return x+y;
 * }
 * 
* *

* After: *

 * Object foo(int x, int y) {
 *   // the first part is AST of the method body
 *   // the rest (including implicit receiver argument) is actual value of arguments
 *   throw new CpsCallableInvocation(___cps___N, this, new Object[] {x, y});
 * }
 *
 * private static CpsFunction ___cps___N = ___cps___N();
 *
 * private static final CpsFunction ___cps___N() {
 *   Builder b = new Builder(...);
 *   return new CpsFunction(['x','y'], b.plus(b.localVariable("x"), b.localVariable("y"))
 * }
 * 
* *

* That is, we transform a Groovy AST of the method body into a tree of * {@link Block}s by using {@link Builder}, then the method just returns this * function object and expect the caller to evaluate it, instead of executing * the method synchronously before it returns. * *

* This class achieves this transformation by implementing * {@link GroovyCodeVisitor} and traverse Groovy AST tree in the in-order. As we * traverse this tree, we produce another Groovy AST tree that invokes * {@link Builder}. Note that we aren't calling Builder directly here; that's * supposed to happen when the Groovy code under transformation actually runs. * *

* Groovy AST that calls {@link Builder} is a tree of function call, so we build * {@link MethodCallExpression}s in the top-down manner. We do this by * {@link CpsTransformer#makeNode(String, Runnable)}, which creates a call to * {@code Builder.xxx(...)}, then supply the closure that fills in the arguments * to this call by walking down the original Groovy AST tree. This walk-down is * done by calling {@link CpsTransformer#visit(ASTNode)} (to recursively visit * ASTs), or by calling {@link CpsTransformer#literal(String)} methods, which * generate string/class/etc literals, as sometimes {@link Builder} methods need * them as well. * * @author Kohsuke Kawaguchi */ public class CpsTransformer extends CompilationCustomizer implements GroovyCodeVisitor { private static final Logger LOGGER = Logger.getLogger(CpsTransformer.class.getName()); @VisibleForTesting public static final AtomicLong iota = new AtomicLong(); private SourceUnit sourceUnit; protected ClassNode classNode; protected TransformerConfiguration config = new TransformerConfiguration(); public CpsTransformer() { super(CompilePhase.CANONICALIZATION); } public void setConfiguration(@Nonnull TransformerConfiguration config) { this.config = config; } @Override public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) { if (classNode.isInterface()) { return; // not touching interfaces } this.sourceUnit = source; this.classNode = classNode; try { for (FieldNode field : new ArrayList<>(classNode.getFields())) { visitNontransformedField(field); } for (MethodNode method : new ArrayList<>(classNode.getMethods())) { visitMethod(method); } processConstructors(classNode); for (Statement statement : new ArrayList<>(classNode.getObjectInitializerStatements())) { visitNontransformedStatement(statement); } classNode.addInterface(SERIALIZABLE_TYPE); // groovy puts timestamp of compilation into a class file, causing serialVersionUID to change. // this tends to be undesirable for CPS involving persistence. // set the timestamp to some bogus value will prevent Verifier from adding a field that encodes // timestamp in the field name // see http://stackoverflow.com/questions/15310136/neverhappen-variable-in-compiled-classes if (classNode.getField(Verifier.__TIMESTAMP) == null) { classNode.addField(Verifier.__TIMESTAMP, Modifier.STATIC | Modifier.PRIVATE, ClassHelper.long_TYPE, new ConstantExpression(0L)); } classNode.addAnnotation(new AnnotationNode(WORKFLOW_TRANSFORMED_TYPE)); } finally { this.sourceUnit = null; this.classNode = null; this.parent = null; } } protected void processConstructors(ClassNode classNode) { for (ConstructorNode constructor : new ArrayList<>(classNode.getDeclaredConstructors())) { visitNontransformedMethod(constructor); } } /** * Should this method be transformed? */ protected boolean shouldBeTransformed(MethodNode node) { return !node.isSynthetic() && !hasAnnotation(node, NonCPS.class) && !hasAnnotation(node, WorkflowTransformed.class) && !node.isAbstract(); } boolean hasAnnotation(MethodNode node, Class a) { for (AnnotationNode ann : node.getAnnotations()) { if (ann.getClassNode().getName().equals(a.getName())) { return true; } } return false; } /** * Transforms asynchronous workflow method. * * From: * * ReturnT foo( T1 arg1, T2 arg2, ...) { ... body ... } * * To: * * private static CpsFunction ___cps___N = ___cps___N(); * * private static final CpsFunction ___cps___N() { return new * CpsFunction(['arg1','arg2','arg3',...], CPS-transformed-method-body) } * * ReturnT foo( T1 arg1, T2 arg2, ...) { throw new * CpsCallableInvocation(___cps___N, this, new Object[] {arg1, arg2, ...}) } */ public void visitMethod(final MethodNode m) { if (!shouldBeTransformed(m)) { visitNontransformedMethod(m); return; } final AtomicReference body = new AtomicReference<>(); // transform the body parent = new ParentClosure() { @Override public void call(Expression e) { body.set(e); } }; visitWithSafepoint(m.getCode()); ListExpression params = new ListExpression(); for (Parameter p : m.getParameters()) { params.addExpression(new ConstantExpression(p.getName())); } /* CpsFunction ___cps___N() { Builder b = new Builder(...); return new CpsFunction( << parameters >>, << body: AST tree building code >>); } */ String cpsName = "___cps___" + iota.getAndIncrement(); DeclarationExpression builderDeclaration = new DeclarationExpression(BUILDER, new Token(ASSIGN, "=", -1, -1), makeBuilder(m)); ReturnStatement returnStatement = new ReturnStatement(new ConstructorCallExpression(FUNCTION_TYPE, new TupleExpression(params, body.get()))); MethodNode builderMethod = m.getDeclaringClass().addMethod(cpsName, PRIVATE_STATIC_FINAL, FUNCTION_TYPE, new Parameter[0], new ClassNode[0], new BlockStatement(Arrays.asList(new ExpressionStatement(builderDeclaration), returnStatement), new VariableScope()) ); builderMethod.addAnnotation(new AnnotationNode(WORKFLOW_TRANSFORMED_TYPE)); FieldNode f = m.getDeclaringClass().addField(cpsName, PRIVATE_STATIC_FINAL, FUNCTION_TYPE, new StaticMethodCallExpression(m.getDeclaringClass(), cpsName, new TupleExpression())); // new ConstructorCallExpression(FUNCTION_TYPE, new TupleExpression(params, body))); Parameter[] pms = m.getParameters(); List paramExpressions = new ArrayList<>(pms.length); for (Parameter p : pms) { paramExpressions.add(new VariableExpression(p)); } ArrayExpression paramArray = new ArrayExpression(ClassHelper.OBJECT_TYPE, paramExpressions); TupleExpression args = new TupleExpression(new VariableExpression(f), THIS, paramArray); ConstructorCallExpression cce = new ConstructorCallExpression(CPSCALLINVK_TYPE, args); m.setCode(new ThrowStatement(cce)); m.addAnnotation(new AnnotationNode(WORKFLOW_TRANSFORMED_TYPE)); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "in {0} transformed {1} to {2}: throw {3} plus {4}: {5}; {6}", new Object[] { classNode.getName(), m.getTypeDescriptor(), // TODO https://github.com/apache/groovy/pull/574 m.getCode().getText() does not work well m.getText(), cce.getText(), // TODO ditto for builderMethod.getCode().getText() builderMethod.getText(), builderDeclaration.getText(), returnStatement.getText() }); } } /** * Generates code that instantiates a new {@link Builder}. * *

* Hook for subtypes to tweak builder, for example to * {@link Builder#contextualize(com.cloudbees.groovy.cps.sandbox.CallSiteTag...)} * *

     * Builder b = new Builder(new MethodLocation(...));
     * b.withClosureType(...);
     * 
* * @param m Method being transformed. */ protected Expression makeBuilder(MethodNode m) { Expression b = new ConstructorCallExpression(BUIDER_TYPE, new TupleExpression( new ConstructorCallExpression(METHOD_LOCATION_TYPE, new TupleExpression( new ConstantExpression(m.getDeclaringClass().getName()), new ConstantExpression(m.getName()), new ConstantExpression(sourceUnit.getName()) )) )); b = new MethodCallExpression(b, "withClosureType", new TupleExpression(new ClassExpression(config.getClosureType()))); Class tag = getTrustTag(); if (tag != null) { b = new MethodCallExpression(b, "contextualize", new PropertyExpression(new ClassExpression(ClassHelper.makeCached(tag)), "INSTANCE")); } return b; } /** * {@link Trusted} or {@link Untrusted} tag that gets added to call site. * * @see "doc/sandbox.md" */ protected Class getTrustTag() { return Trusted.class; } /** * For methods that are not CPS-transformed. */ protected void visitNontransformedMethod(MethodNode m) { } protected void visitNontransformedField(FieldNode f) { } protected void visitNontransformedStatement(Statement s) { } // TODO Java 8 @FunctionalInterface, or switch to Consumer protected interface ParentClosure { void call(Expression e); } /** * As we visit expressions in the method body, we convert them to the * {@link Builder} invocations and pass them back to this closure. */ protected ParentClosure parent; protected void visit(ASTNode e) { LOGGER.log(Level.FINER, "visiting {0}:{1}", new Object[] {sourceUnit.getName(), e.getLineNumber()}); if (e instanceof EmptyExpression) { // working around a bug in EmptyExpression.visit() that doesn't call any method visitEmptyExpression((EmptyExpression) e); } else if (e instanceof EmptyStatement) { // working around a bug in EmptyStatement.visit() that doesn't call any method visitEmptyStatement((EmptyStatement) e); } else { e.visit(this); } } protected void visit(Collection col) { for (ASTNode e : col) { visit(e); } } /** * Like {@link #visit(ASTNode)} but also inserts the safepoint at the top. */ protected void visitWithSafepoint(final Statement st) { if (config.getSafepoints().isEmpty()) { visit(st); // common case optimization } else { makeNode("block", new Runnable() { @Override public void run() { // insert function call for each safepoint for (final Safepoint s : config.getSafepoints()) { makeNode("staticCall", new Runnable() { @Override public void run() { loc(st); literal(s.node); literal(s.methodName); } }); } visit(st); } }); } } /** * Makes an AST fragment that calls {@link Builder} with specific method. * * @param methodName Method on {@link Builder} to call. */ protected void makeNode(String methodName, Expression... args) { parent.call(new MethodCallExpression(BUILDER, methodName, makeChildren(args))); } /** * Makes an AST fragment that calls {@link Builder} with specific method. * * @param methodName Method on {@link Builder} to call. */ protected void makeNode(String methodName, Runnable body) { parent.call(new MethodCallExpression(BUILDER, methodName, makeChildren(body))); } /** * Makes an AST fragment that instantiates a new instance of the given type. */ protected void makeNode(ClassNode type, Expression... args) { parent.call(new ConstructorCallExpression(type, makeChildren(args))); } /** * Makes an AST fragment that instantiates a new instance of the given type. */ protected void makeNode(ClassNode type, Runnable body) { parent.call(new ConstructorCallExpression(type, makeChildren(body))); } /** * Shorthand for {@link TupleExpression#TupleExpression(Expression[])}. */ protected TupleExpression makeChildren(Expression... args) { return new TupleExpression(args); } /** * Given closure, package them up into a tuple. */ protected TupleExpression makeChildren(Runnable body) { final List argExps = new ArrayList<>(); ParentClosure old = parent; try { parent = new ParentClosure() { @Override public void call(Expression e) { argExps.add(e); } }; body.run(); // evaluate arguments return new TupleExpression(argExps); } finally { parent = old; } } protected void loc(ASTNode e) { literal(e.getLineNumber()); } /** * Used in the closure block of {@link #makeNode(String, Runnable)} to create * a literal string argument. */ protected void literal(String s) { parent.call(new ConstantExpression(s)); } protected void literal(ClassNode c) { parent.call(new ClassExpression(c)); } protected void literal(int n) { parent.call(new ConstantExpression(n, true)); } protected void literal(boolean b) { parent.call(new ConstantExpression(b, true)); } void visitEmptyExpression(EmptyExpression e) { makeNode("noop"); } void visitEmptyStatement(EmptyStatement e) { makeNode("noop"); } @Override public void visitMethodCallExpression(final MethodCallExpression call) { makeNode("functionCall", new Runnable() { @Override public void run() { loc(call); // isImplicitThis==true even when objectExpression is not 'this'. // See InvocationWriter.makeCall, if (call.isImplicitThis() && AsmClassGenerator.isThisExpression(call.getObjectExpression())) { makeNode("javaThis_"); } else { visit(call.getObjectExpression()); } if (call.isSpreadSafe()) { throw new UnsupportedOperationException("spread not yet supported in " + call.getText()); // TODO will require safepoints } visit(call.getMethod()); literal(call.isSafe()); visit(((TupleExpression) call.getArguments()).getExpressions()); } }); } @Override public void visitBlockStatement(final BlockStatement b) { makeNode("block", new Runnable() { @Override public void run() { visit(b.getStatements()); } }); } @Override public void visitForLoop(final ForStatement forLoop) { if (ForStatement.FOR_LOOP_DUMMY.equals(forLoop.getVariable())) { // for ( e1; e2; e3 ) { ... } final ClosureListExpression loop = (ClosureListExpression) forLoop.getCollectionExpression(); assert loop.getExpressions().size() == 3; makeNode("forLoop", new Runnable() { @Override public void run() { literal(forLoop.getStatementLabel()); visit(loop.getExpressions()); visitWithSafepoint(forLoop.getLoopBlock()); } }); } else { // for (x in col) { ... } makeNode("forInLoop", new Runnable() { @Override public void run() { loc(forLoop); literal(forLoop.getStatementLabel()); literal(forLoop.getVariableType()); literal(forLoop.getVariable().getName()); visit(forLoop.getCollectionExpression()); visitWithSafepoint(forLoop.getLoopBlock()); } }); } } @Override public void visitWhileLoop(final WhileStatement loop) { makeNode("while_", new Runnable() { @Override public void run() { literal(loop.getStatementLabel()); visit(loop.getBooleanExpression()); visitWithSafepoint(loop.getLoopBlock()); } }); } @Override public void visitDoWhileLoop(final DoWhileStatement loop) { makeNode("doWhile", new Runnable() { @Override public void run() { literal(loop.getStatementLabel()); visit(loop.getBooleanExpression()); visitWithSafepoint(loop.getLoopBlock()); } }); } @Override public void visitIfElse(final IfStatement stmt) { makeNode("if_", new Runnable() { @Override public void run() { visit(stmt.getBooleanExpression()); visit(stmt.getIfBlock()); visit(stmt.getElseBlock()); } }); } @Override public void visitExpressionStatement(ExpressionStatement statement) { visit(statement.getExpression()); } @Override public void visitReturnStatement(final ReturnStatement statement) { makeNode("return_", new Runnable() { @Override public void run() { visit(statement.getExpression()); } }); } @Override public void visitAssertStatement(final AssertStatement statement) { Janitor j = new Janitor(); final String text = new SourceText(statement, sourceUnit, j).getNormalizedText(); j.cleanup(); makeNode("assert_", new Runnable() { @Override public void run() { visit(statement.getBooleanExpression()); visit(statement.getMessageExpression()); literal(text); } }); } @Override public void visitTryCatchFinally(final TryCatchStatement stmt) { makeNode("tryCatch", new Runnable() { @Override public void run() { visit(stmt.getTryStatement()); visit(stmt.getFinallyStatement()); visit(stmt.getCatchStatements()); } }); } @Override public void visitSwitch(final SwitchStatement stmt) { makeNode("switch_", new Runnable() { @Override public void run() { literal(stmt.getStatementLabel()); visit(stmt.getExpression()); visit(stmt.getDefaultStatement()); visit(stmt.getCaseStatements()); } }); } @Override public void visitCaseStatement(final CaseStatement stmt) { makeNode("case_", new Runnable() { @Override public void run() { loc(stmt); visit(stmt.getExpression()); visit(stmt.getCode()); } }); } @Override public void visitBreakStatement(BreakStatement statement) { makeNode("break_", new ConstantExpression(statement.getLabel())); } @Override public void visitContinueStatement(ContinueStatement statement) { makeNode("continue_", new ConstantExpression(statement.getLabel())); } @Override public void visitThrowStatement(final ThrowStatement st) { makeNode("throw_", new Runnable() { @Override public void run() { loc(st); visit(st.getExpression()); } }); } @Override public void visitSynchronizedStatement(SynchronizedStatement statement) { throw new UnsupportedOperationException(); } @Override public void visitCatchStatement(final CatchStatement stmt) { makeNode(CATCH_EXPRESSION_TYPE, new Runnable() { @Override public void run() { literal(stmt.getExceptionType()); literal(stmt.getVariable().getName()); visit(stmt.getCode()); } }); } @Override public void visitStaticMethodCallExpression(final StaticMethodCallExpression exp) { makeNode("staticCall", new Runnable() { @Override public void run() { loc(exp); literal(exp.getOwnerType()); literal(exp.getMethod()); visit(((TupleExpression) exp.getArguments()).getExpressions()); } }); } @Override public void visitConstructorCallExpression(final ConstructorCallExpression call) { makeNode("new_", new Runnable() { @Override public void run() { loc(call); literal(call.getType()); visit(((TupleExpression) call.getArguments()).getExpressions()); } }); } @Override public void visitTernaryExpression(final TernaryExpression exp) { makeNode("ternaryOp", new Runnable() { @Override public void run() { visit(exp.getBooleanExpression()); visit(exp.getTrueExpression()); visit(exp.getFalseExpression()); } }); } @Override public void visitShortTernaryExpression(final ElvisOperatorExpression exp) { makeNode("elvisOp", new Runnable() { @Override public void run() { visit(exp.getBooleanExpression()); visit(exp.getFalseExpression()); } }); } // Constants from Token.type to a method on Builder private static final Map BINARY_OP_TO_BUILDER_METHOD = new HashMap<>(); static { BINARY_OP_TO_BUILDER_METHOD.put(COMPARE_EQUAL, "compareEqual"); BINARY_OP_TO_BUILDER_METHOD.put(COMPARE_NOT_EQUAL, "compareNotEqual"); BINARY_OP_TO_BUILDER_METHOD.put(COMPARE_TO, "compareTo"); BINARY_OP_TO_BUILDER_METHOD.put(COMPARE_GREATER_THAN, "greaterThan"); BINARY_OP_TO_BUILDER_METHOD.put(COMPARE_GREATER_THAN_EQUAL, "greaterThanEqual"); BINARY_OP_TO_BUILDER_METHOD.put(COMPARE_LESS_THAN, "lessThan"); BINARY_OP_TO_BUILDER_METHOD.put(COMPARE_LESS_THAN_EQUAL, "lessThanEqual"); BINARY_OP_TO_BUILDER_METHOD.put(LOGICAL_AND, "logicalAnd"); BINARY_OP_TO_BUILDER_METHOD.put(LOGICAL_OR, "logicalOr"); BINARY_OP_TO_BUILDER_METHOD.put(BITWISE_AND, "bitwiseAnd"); BINARY_OP_TO_BUILDER_METHOD.put(BITWISE_AND_EQUAL, "bitwiseAndEqual"); BINARY_OP_TO_BUILDER_METHOD.put(BITWISE_OR, "bitwiseOr"); BINARY_OP_TO_BUILDER_METHOD.put(BITWISE_OR_EQUAL, "bitwiseOrEqual"); BINARY_OP_TO_BUILDER_METHOD.put(BITWISE_XOR, "bitwiseXor"); BINARY_OP_TO_BUILDER_METHOD.put(BITWISE_XOR_EQUAL, "bitwiseXorEqual"); BINARY_OP_TO_BUILDER_METHOD.put(PLUS, "plus"); BINARY_OP_TO_BUILDER_METHOD.put(PLUS_EQUAL, "plusEqual"); BINARY_OP_TO_BUILDER_METHOD.put(MINUS, "minus"); BINARY_OP_TO_BUILDER_METHOD.put(MINUS_EQUAL, "minusEqual"); BINARY_OP_TO_BUILDER_METHOD.put(MULTIPLY, "multiply"); BINARY_OP_TO_BUILDER_METHOD.put(MULTIPLY_EQUAL, "multiplyEqual"); BINARY_OP_TO_BUILDER_METHOD.put(DIVIDE, "div"); BINARY_OP_TO_BUILDER_METHOD.put(DIVIDE_EQUAL, "divEqual"); BINARY_OP_TO_BUILDER_METHOD.put(INTDIV, "intdiv"); BINARY_OP_TO_BUILDER_METHOD.put(INTDIV_EQUAL, "intdivEqual"); BINARY_OP_TO_BUILDER_METHOD.put(MOD, "mod"); BINARY_OP_TO_BUILDER_METHOD.put(MOD_EQUAL, "modEqual"); BINARY_OP_TO_BUILDER_METHOD.put(POWER, "power"); BINARY_OP_TO_BUILDER_METHOD.put(POWER_EQUAL, "powerEqual"); BINARY_OP_TO_BUILDER_METHOD.put(EQUAL, "assign"); BINARY_OP_TO_BUILDER_METHOD.put(KEYWORD_INSTANCEOF, "instanceOf"); BINARY_OP_TO_BUILDER_METHOD.put(LEFT_SQUARE_BRACKET, "array"); BINARY_OP_TO_BUILDER_METHOD.put(LEFT_SHIFT, "leftShift"); BINARY_OP_TO_BUILDER_METHOD.put(LEFT_SHIFT_EQUAL, "leftShiftEqual"); BINARY_OP_TO_BUILDER_METHOD.put(RIGHT_SHIFT, "rightShift"); BINARY_OP_TO_BUILDER_METHOD.put(RIGHT_SHIFT_EQUAL, "rightShiftEqual"); BINARY_OP_TO_BUILDER_METHOD.put(RIGHT_SHIFT_UNSIGNED, "rightShiftUnsigned"); BINARY_OP_TO_BUILDER_METHOD.put(RIGHT_SHIFT_UNSIGNED_EQUAL, "rightShiftUnsignedEqual"); BINARY_OP_TO_BUILDER_METHOD.put(FIND_REGEX, "findRegex"); BINARY_OP_TO_BUILDER_METHOD.put(MATCH_REGEX, "matchRegex"); BINARY_OP_TO_BUILDER_METHOD.put(KEYWORD_IN, "isCase"); } private void multipleAssignment(final Expression parentExpression, final TupleExpression tuple, final Expression rhs) { List tupleExpressions = tuple.getExpressions(); for (int i = 0, tupleExpressionsSize = tupleExpressions.size(); i < tupleExpressionsSize; i++) { final Expression tupleExpression = tupleExpressions.get(i); final Expression index = new ConstantExpression(i, true); // def (a, b, c) = [1, 2] is allowed - c will just be null in that scenario. // def (a, b) = [1, 2, 3] is allowed as well - 3 is just discarded. // def (a, b) = 4 will error due to Integer.getAt(int) not being a thing // def (a, b) = "what" is allowed - a will equal 'w', and b will equal 'h' makeNode("assign", new Runnable() { @Override public void run() { loc(parentExpression); visit(tupleExpression); makeNode("array", new Runnable() { @Override public void run() { loc(rhs); visit(rhs); makeNode("constant", index); } }); } }); } } /** * @see * org.codehaus.groovy.classgen.asm.BinaryExpressionHelper#eval(BinaryExpression) */ @Override public void visitBinaryExpression(final BinaryExpression exp) { String name = BINARY_OP_TO_BUILDER_METHOD.get(exp.getOperation().getType()); if (name != null) { if (name.equals("assign") && exp.getLeftExpression() instanceof TupleExpression) { multipleAssignment(exp, (TupleExpression)exp.getLeftExpression(), exp.getRightExpression()); } else { makeNode(name, new Runnable() { @Override public void run() { loc(exp); visit(exp.getLeftExpression()); visit(exp.getRightExpression()); } }); } return; } throw new UnsupportedOperationException("Operation: " + exp.getOperation() + " not supported"); } @Override public void visitPrefixExpression(final PrefixExpression exp) { makeNode("prefix" + prepostfixOperatorSuffix(exp.getOperation()), new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); } }); } @Override public void visitPostfixExpression(final PostfixExpression exp) { makeNode("postfix" + prepostfixOperatorSuffix(exp.getOperation()), new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); } }); } protected String prepostfixOperatorSuffix(Token operation) { switch (operation.getType()) { case PLUS_PLUS: return "Inc"; case MINUS_MINUS: return "Dec"; default: throw new UnsupportedOperationException("Unknown operator:" + operation.getText()); } } @Override public void visitBooleanExpression(BooleanExpression exp) { visit(exp.getExpression()); } @Override public void visitClosureExpression(final ClosureExpression exp) { makeNode("closure", new Runnable() { @Override public void run() { loc(exp); ListExpression types; ListExpression params; // the interpretation of the 'parameters' is messed up. According to ClosureWriter, // when the user explicitly defines no parameter "{ -> foo() }" then this is null, // when the user doesn't define any parameter explicitly { foo() }, then this is empty, if (exp.getParameters() == null) { types = new ListExpression(Collections.EMPTY_LIST); params = new ListExpression(Collections.EMPTY_LIST); } else if (exp.getParameters().length == 0) { types = new ListExpression(Collections.singletonList(new ClassExpression(OBJECT_TYPE))); params = new ListExpression(Collections.singletonList(new ConstantExpression("it"))); } else { Parameter[] paramArray = exp.getParameters(); List typesList = new ArrayList(paramArray.length); List paramsList = new ArrayList(paramArray.length); for (Parameter p : paramArray) { typesList.add(new ClassExpression(p.getType())); paramsList.add(new ConstantExpression(p.getName())); } types = new ListExpression(typesList); params = new ListExpression(paramsList); } parent.call(types); parent.call(params); visitWithSafepoint(exp.getCode()); } }); } @Override public void visitTupleExpression(TupleExpression expression) { throw new UnsupportedOperationException(); } @Override public void visitMapExpression(final MapExpression exp) { if (exp.getMapEntryExpressions().size() > 125) { sourceUnit.addError(new SyntaxException("Map expressions can only contain up to 125 entries", exp.getLineNumber(), exp.getColumnNumber())); } else { makeNode("map", new Runnable() { @Override public void run() { for (MapEntryExpression e : exp.getMapEntryExpressions()) { visit(e.getKeyExpression()); visit(e.getValueExpression()); } } }); } } @Override public void visitMapEntryExpression(MapEntryExpression expression) { throw new UnsupportedOperationException(); } @Override public void visitListExpression(final ListExpression exp) { if (exp.getExpressions().size() > 250) { sourceUnit.addError(new SyntaxException("List expressions can only contain up to 250 elements", exp.getLineNumber(), exp.getColumnNumber())); } else { makeNode("list", new Runnable() { @Override public void run() { visit(exp.getExpressions()); } }); } } @Override public void visitRangeExpression(final RangeExpression exp) { makeNode("range", new Runnable() { @Override public void run() { loc(exp); visit(exp.getFrom()); visit(exp.getTo()); literal(exp.isInclusive()); } }); } @Override public void visitPropertyExpression(final PropertyExpression exp) { // TODO: spread if (exp.getObjectExpression() instanceof VariableExpression && ((VariableExpression) exp.getObjectExpression()).isThisExpression() && exp.getProperty() instanceof ConstantExpression && classNode.getSetterMethod("set" + Verifier.capitalize((String) ((ConstantExpression) exp.getProperty()).getValue()), false) != null) { makeNode("attribute", new Runnable() { @Override public void run() { loc(exp); visit(exp.getObjectExpression()); visit(exp.getProperty()); literal(exp.isSafe()); } }); } else { makeNode("property", new Runnable() { @Override public void run() { loc(exp); visit(exp.getObjectExpression()); visit(exp.getProperty()); literal(exp.isSafe()); } }); } } @Override public void visitAttributeExpression(final AttributeExpression exp) { // TODO: spread makeNode("attribute", new Runnable() { @Override public void run() { loc(exp); visit(exp.getObjectExpression()); visit(exp.getProperty()); literal(exp.isSafe()); } }); } @Override public void visitFieldExpression(final FieldExpression exp) { final FieldNode f = exp.getField(); if (f.isStatic()) { makeNode("staticField", new Runnable() { @Override public void run() { loc(exp); literal(f.getType()); literal(exp.getFieldName()); } }); } else { makeNode("property", new Runnable() { @Override public void run() { loc(exp); makeNode("this_"); literal(exp.getFieldName()); } }); } } @Override public void visitMethodPointerExpression(final MethodPointerExpression exp) { makeNode("methodPointer", new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); visit(exp.getMethodName()); } }); } @Override public void visitConstantExpression(ConstantExpression expression) { makeNode("constant", expression); } @Override public void visitClassExpression(ClassExpression expression) { makeNode("constant", expression); } @Override public void visitVariableExpression(final VariableExpression exp) { Variable ref = exp.getAccessedVariable(); if (ref instanceof VariableExpression /* local variable */ || ref instanceof Parameter) { makeNode("localVariable", new Runnable() { @Override public void run() { loc(exp); literal(exp.getName()); } }); } else if (ref instanceof DynamicVariable || ref instanceof PropertyNode || ref instanceof FieldNode) { if (ref instanceof FieldNode && classNode.getGetterMethod("get" + Verifier.capitalize(exp.getName())) != null) { makeNode("attribute", new Runnable() { @Override public void run() { loc(exp); makeNode("javaThis_"); visit(new ConstantExpression(exp.getName())); literal(false); } }); } else { makeNode("property", new Runnable() { @Override public void run() { loc(exp); makeNode("javaThis_"); literal(exp.getName()); } }); } } else if ("this".equals(exp.getName())) { /* Kohsuke: TODO: I don't really understand the 'true' block of the code, so I'm missing something if (controller.isStaticMethod() || (!controller.getCompileStack().isImplicitThis() && controller.isStaticContext())) { if (controller.isInClosure()) classNode = controller.getOutermostClass(); visitClassExpression(new ClassExpression(classNode)); } else { loadThis(); } */ makeNode("this_"); } else if ("super".equals(exp.getName())) { makeNode("super_", new Runnable() { @Override public void run() { literal(classNode); } }); } else { sourceUnit.addError(new SyntaxException("Unsupported expression for CPS transformation", exp.getLineNumber(), exp.getColumnNumber())); } } @Override public void visitDeclarationExpression(final DeclarationExpression exp) { if (exp.isMultipleAssignmentDeclaration()) { // def (a,b)=list makeNode("sequence", new Runnable() { @Override public void run() { for (Expression e : exp.getTupleExpression().getExpressions()) { final VariableExpression v = (VariableExpression) e; makeNode("declareVariable", new Runnable() { @Override public void run() { literal(v.getType()); literal(v.getName()); } }); } multipleAssignment(exp, exp.getTupleExpression(), exp.getRightExpression()); } }); } else { // def x=v; makeNode("declareVariable", new Runnable() { @Override public void run() { VariableExpression v = exp.getVariableExpression(); loc(exp); literal(v.getType()); literal(v.getName()); visit(exp.getRightExpression()); // this will not produce anything if this is EmptyExpression } }); } } @Override public void visitGStringExpression(final GStringExpression exp) { makeNode("gstring", new Runnable() { @Override public void run() { loc(exp); makeNode("list", new Runnable() { @Override public void run() { visit(exp.getValues()); } }); makeNode("list", new Runnable() { @Override public void run() { visit(exp.getStrings()); } }); } }); } @Override public void visitArrayExpression(final ArrayExpression exp) { if (exp.getSizeExpression() != null) { // array instanation like new String[1][2][3] makeNode("newArray", new Runnable() { @Override public void run() { loc(exp); literal(exp.getElementType()); visit(exp.getSizeExpression()); } }); } else { throw new UnsupportedOperationException(exp.getText()); } } @Override public void visitSpreadExpression(SpreadExpression expression) { throw new UnsupportedOperationException(); } @Override public void visitSpreadMapExpression(SpreadMapExpression expression) { throw new UnsupportedOperationException(); } @Override public void visitNotExpression(final NotExpression exp) { makeNode("not", new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); } }); } @Override public void visitUnaryMinusExpression(final UnaryMinusExpression exp) { makeNode("unaryMinus", new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); } }); } @Override public void visitUnaryPlusExpression(final UnaryPlusExpression exp) { makeNode("unaryPlus", new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); } }); } @Override public void visitBitwiseNegationExpression(final BitwiseNegationExpression exp) { makeNode("bitwiseNegation", new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); } }); } @Override public void visitCastExpression(final CastExpression exp) { makeNode("cast", new Runnable() { @Override public void run() { loc(exp); visit(exp.getExpression()); literal(exp.getType()); literal(exp.isCoerce()); // TODO what about ignoreAutoboxing & strict? } }); } @Override public void visitArgumentlistExpression(ArgumentListExpression expression) { throw new UnsupportedOperationException(); } @Override public void visitClosureListExpression(ClosureListExpression closureListExpression) { throw new UnsupportedOperationException(); } @Override public void visitBytecodeExpression(BytecodeExpression expression) { throw new UnsupportedOperationException(); } private static final ClassNode OBJECT_TYPE = ClassHelper.makeCached(Object.class); private static final ClassNode FUNCTION_TYPE = ClassHelper.makeCached(CpsFunction.class); private static final ClassNode CATCH_EXPRESSION_TYPE = ClassHelper.makeCached(CatchExpression.class); private static final ClassNode BUILDER_TYPE = ClassHelper.makeCached(Builder.class); private static final ClassNode CPSCALLINVK_TYPE = ClassHelper.makeCached(CpsCallableInvocation.class); private static final ClassNode WORKFLOW_TRANSFORMED_TYPE = ClassHelper.makeCached(WorkflowTransformed.class); private static final ClassNode BUIDER_TYPE = ClassHelper.makeCached(Builder.class); private static final ClassNode METHOD_LOCATION_TYPE = ClassHelper.makeCached(MethodLocation.class); private static final ClassNode SERIALIZABLE_TYPE = ClassHelper.makeCached(Serializable.class); private static final VariableExpression BUILDER = new VariableExpression("b", BUILDER_TYPE); // new PropertyExpression(new ClassExpression(BUILDER_TYPE), "INSTANCE") private static final VariableExpression THIS = new VariableExpression("this"); /** * Closure's default "it" parameter. */ private static final Parameter IT = new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL); private static final int PRIVATE_STATIC_FINAL = Modifier.STATIC | Modifier.PRIVATE | Modifier.FINAL; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy