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

org.codehaus.groovy.control.CompilationUnit Maven / Gradle / Ivy

The newest version!
/*
 *  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.
 */
package org.codehaus.groovy.control;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyRuntimeException;
import groovy.transform.CompilationUnitAware;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.GroovyClassVisitor;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.codehaus.groovy.classgen.ClassCompletionVerifier;
import org.codehaus.groovy.classgen.EnumCompletionVisitor;
import org.codehaus.groovy.classgen.EnumVisitor;
import org.codehaus.groovy.classgen.ExtendedVerifier;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.classgen.InnerClassCompletionVisitor;
import org.codehaus.groovy.classgen.InnerClassVisitor;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.io.InputStreamReaderSource;
import org.codehaus.groovy.control.io.ReaderSource;
import org.codehaus.groovy.control.messages.ExceptionMessage;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.syntax.RuntimeParserException;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.tools.GroovyClass;
import org.codehaus.groovy.transform.ASTTransformationVisitor;
import org.codehaus.groovy.transform.AnnotationCollectorTransform;
import org.codehaus.groovy.transform.trait.TraitComposer;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;

import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.DYNAMIC_OUTER_NODE_CALLBACK;
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.SWITCH_CONDITION_EXPRESSION_TYPE;

/**
 * The CompilationUnit collects all compilation data as it is generated by the compiler system.
 * You can use this object to add additional source units to the compilation, or force the
 * compilation to be run again (to affect only the deltas).
 * 

* You can also add PhaseOperations to this compilation using the addPhaseOperation method. * This is commonly used when you want to wire a new AST Transformation into the compilation. */ public class CompilationUnit extends ProcessingUnit { /** The overall AST for this CompilationUnit. */ protected CompileUnit ast; // TODO: Switch to private and access through getAST(). /** The source units from which this unit is built. */ protected Map sources = new LinkedHashMap<>(); protected Queue queuedSources = new LinkedList<>(); /** The classes generated during classgen. */ private List generatedClasses = new ArrayList<>(); private Deque[] phaseOperations; private Deque[] newPhaseOperations; { final int n = Phases.ALL + 1; phaseOperations = new Deque[n]; newPhaseOperations = new Deque[n]; for (int i = 0; i < n; i += 1) { phaseOperations[i] = new LinkedList<>(); newPhaseOperations[i] = new LinkedList<>(); } } /** If set, outputs a little more information during compilation when errors occur. */ protected boolean debug; /** True after the first {@link #configure(CompilerConfiguration)} operation. */ protected boolean configured; /** A callback called during the {@code classgen} phase of compilation */ protected ClassgenCallback classgenCallback; /** A callback for use during {@link #compile()} */ protected ProgressCallback progressCallback; protected ClassNodeResolver classNodeResolver = new ClassNodeResolver(); protected ResolveVisitor resolveVisitor = new ResolveVisitor(this); /** The AST transformations state data. */ protected ASTTransformationsContext astTransformationsContext; private Set javaCompilationUnitSet = new HashSet<>(); /** * Initializes the CompilationUnit with defaults. */ public CompilationUnit() { this(null, null, null); } /** * Initializes the CompilationUnit with defaults except for class loader. */ public CompilationUnit(final GroovyClassLoader loader) { this(null, null, loader); } /** * Initializes the CompilationUnit with no security considerations. */ public CompilationUnit(final CompilerConfiguration configuration) { this(configuration, null, null); } /** * Initializes the CompilationUnit with a CodeSource for controlling * security stuff and a class loader for loading classes. */ public CompilationUnit(final CompilerConfiguration configuration, final CodeSource codeSource, final GroovyClassLoader loader) { this(configuration, codeSource, loader, null); } /** * Initializes the CompilationUnit with a CodeSource for controlling * security stuff, a class loader for loading classes, and a class * loader for loading AST transformations. *

* Note: The transform loader must be able to load compiler classes. * That means {@link #classLoader} must be at last a parent to {@code transformLoader}. * The other loader has no such constraint. * * @param transformLoader - the loader for transforms * @param loader - loader used to resolve classes against during compilation * @param codeSource - security setting for the compilation * @param configuration - compilation configuration */ public CompilationUnit(final CompilerConfiguration configuration, final CodeSource codeSource, final GroovyClassLoader loader, final GroovyClassLoader transformLoader) { super(configuration, loader, null); this.astTransformationsContext = new ASTTransformationsContext(this, transformLoader); this.ast = new CompileUnit(getClassLoader(), codeSource, getConfiguration()); addPhaseOperations(); applyCompilationCustomizers(); } private void addPhaseOperations() { addPhaseOperation(SourceUnit::parse, Phases.PARSING); addPhaseOperation(source -> { source.convert(); // add module to compile unit getAST().addModule(source.getAST()); Optional.ofNullable(getProgressCallback()) .ifPresent(callback -> callback.call(source, getPhase())); }, Phases.CONVERSION); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { GroovyClassVisitor visitor = new EnumVisitor(this, source); visitor.visitClass(classNode); }, Phases.CONVERSION); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { GroovyClassVisitor visitor = new PlaceholderVisitor(this, source); visitor.visitClass(classNode); }, Phases.CONVERSION); addPhaseOperation(source -> { try { resolveVisitor.phase = 1; // resolve head of each class resolveVisitor.setClassNodeResolver(classNodeResolver); for (ClassNode classNode : source.getAST().getClasses()) { resolveVisitor.startResolving(classNode, source); } } finally { resolveVisitor.phase = 0; } }, Phases.SEMANTIC_ANALYSIS); addPhaseOperation(source -> { try { resolveVisitor.phase = 2; // resolve body of each class for (ClassNode classNode : source.getAST().getClasses()) { resolveVisitor.startResolving(classNode, source); } } finally { resolveVisitor.phase = 0; } }, Phases.SEMANTIC_ANALYSIS); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { GroovyClassVisitor visitor = new StaticImportVisitor(classNode, source); visitor.visitClass(classNode); }, Phases.SEMANTIC_ANALYSIS); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { GroovyClassVisitor visitor = new InnerClassVisitor(this, source); visitor.visitClass(classNode); }, Phases.SEMANTIC_ANALYSIS); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { if (!classNode.isSynthetic()) { GroovyClassVisitor visitor = new GenericsVisitor(source); visitor.visitClass(classNode); } }, Phases.SEMANTIC_ANALYSIS); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { AnnotationCollectorTransform.ClassChanger xformer = new AnnotationCollectorTransform.ClassChanger(); xformer.transformClass(classNode); }, Phases.SEMANTIC_ANALYSIS); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { TraitComposer.doExtendTraits(classNode, source, this); }, Phases.CANONICALIZATION); addPhaseOperation(source -> { List classes = source.getAST().getClasses(); for (ClassNode node : classes) { CompileUnit cu = node.getCompileUnit(); for (Iterator it = cu.getClassesToCompile().keySet().iterator(); it.hasNext(); ) { String name = it.next(); StringBuilder message = new StringBuilder("Compilation incomplete: expected to find the class ") .append(name) .append(" in ") .append(source.getName()) .append(", but the file "); if (classes.isEmpty()) { message.append("seems not to contain any classes"); } else { message.append("contains the classes: "); boolean first = true; for (ClassNode cn : classes) { if (first) { first = false; } else { message.append(", "); } message.append(cn.getName()); } } getErrorCollector().addErrorAndContinue(Message.create(message.toString(), this)); it.remove(); } } }, Phases.CANONICALIZATION); addPhaseOperation(source -> { for (ClassNode cn : source.getAST().getClasses()) { // GROOVY-10540: add GroovyObject before STC and classgen if (!cn.isInterface() && !cn.isDerivedFromGroovyObject()) { boolean cs = false, pojo = false, trait = false; for (AnnotationNode an : cn.getAnnotations()) { switch (an.getClassNode().getName()) { case "groovy.transform.CompileStatic": cs = true; break; case "groovy.transform.stc.POJO": pojo = true; break; case "groovy.transform.Trait": trait = true; break; } } if (!(cs && pojo) && !trait) cn.addInterface(ClassHelper.GROOVY_OBJECT_TYPE); } } }, Phases.INSTRUCTION_SELECTION); addPhaseOperation(verification, Phases.CLASS_GENERATION); addPhaseOperation(classgen, Phases.CLASS_GENERATION); addPhaseOperation(groovyClass -> { String name = groovyClass.getName().replace('.', File.separatorChar) + ".class"; File path = new File(getConfiguration().getTargetDirectory(), name); // ensure the path is ready for the file File directory = path.getParentFile(); if (directory != null && !directory.exists()) { directory.mkdirs(); } // create the file and write out the data try (FileOutputStream stream = new FileOutputStream(path)) { stream.write(groovyClass.getBytes()); } catch (IOException e) { getErrorCollector().addError(Message.create(e.getMessage(), this)); } }); ASTTransformationVisitor.addPhaseOperations(this); // post-transform operations: addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { StaticVerifier verifier = new StaticVerifier(); verifier.visitClass(classNode, source); }, Phases.SEMANTIC_ANALYSIS); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { GroovyClassVisitor visitor = new InnerClassCompletionVisitor(this, source); visitor.visitClass(classNode); visitor = new EnumCompletionVisitor(this, source); visitor.visitClass(classNode); }, Phases.CANONICALIZATION); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { Object callback = classNode.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK); if (callback instanceof IPrimaryClassNodeOperation) { ((IPrimaryClassNodeOperation) callback).call(source, context, classNode); classNode.removeNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK); } }, Phases.INSTRUCTION_SELECTION); addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { // TODO: Can this be moved into org.codehaus.groovy.transform.sc.transformers.VariableExpressionTransformer? GroovyClassVisitor visitor = new ClassCodeExpressionTransformer() { @Override protected SourceUnit getSourceUnit() { return source; } @Override public Expression transform(final Expression expression) { if (expression instanceof VariableExpression) { // check for "switch(enumType) { case CONST: ... }" ClassNode enumType = expression.getNodeMetaData(SWITCH_CONDITION_EXPRESSION_TYPE); if (enumType != null) { // replace "CONST" variable expression with "EnumType.CONST" property expression Expression propertyExpression = propX(classX(enumType), expression.getText()); setSourcePosition(propertyExpression, expression); return propertyExpression; } } return expression; } }; visitor.visitClass(classNode); }, Phases.INSTRUCTION_SELECTION); } private void applyCompilationCustomizers() { for (CompilationCustomizer customizer : getConfiguration().getCompilationCustomizers()) { if (customizer instanceof CompilationUnitAware) { ((CompilationUnitAware) customizer).setCompilationUnit(this); } addPhaseOperation(customizer, customizer.getPhase().getPhaseNumber()); } } public void addPhaseOperation(final IGroovyClassOperation op) { phaseOperations[Phases.OUTPUT].addFirst(op); } public void addPhaseOperation(final ISourceUnitOperation op, final int phase) { validatePhase(phase); phaseOperations[phase].add(op); } public void addPhaseOperation(final IPrimaryClassNodeOperation op, final int phase) { validatePhase(phase); phaseOperations[phase].add(op); } public void addFirstPhaseOperation(final IPrimaryClassNodeOperation op, final int phase) { validatePhase(phase); phaseOperations[phase].addFirst(op); } public void addNewPhaseOperation(final ISourceUnitOperation op, final int phase) { validatePhase(phase); newPhaseOperations[phase].add(op); } private static void validatePhase(final int phase) { if (phase < 1 || phase > Phases.ALL) { throw new IllegalArgumentException("phase " + phase + " is unknown"); } } /** * Configures its debugging mode and classloader classpath from a given compiler configuration. * This cannot be done more than once due to limitations in {@link java.net.URLClassLoader URLClassLoader}. */ @Override public void configure(final CompilerConfiguration configuration) { super.configure(configuration); this.debug = getConfiguration().getDebug(); this.configured = true; } /** * Returns the CompileUnit that roots our AST. */ public CompileUnit getAST() { return this.ast; } /** * Get the GroovyClasses generated by compile(). */ public List getClasses() { return generatedClasses; } /** * Convenience routine to get the first ClassNode, for * when you are sure there is only one. */ public ClassNode getFirstClassNode() { return getAST().getModules().get(0).getClasses().get(0); } /** * Convenience routine to get the named ClassNode. */ public ClassNode getClassNode(final String name) { ClassNode[] result = new ClassNode[1]; IPrimaryClassNodeOperation handler = (source, context, classNode) -> { if (classNode.getName().equals(name)) { result[0] = classNode; } }; try { handler.doPhaseOperation(this); } catch (CompilationFailedException e) { if (debug) e.printStackTrace(); } return result[0]; } /** * @return the AST transformations current context */ public ASTTransformationsContext getASTTransformationsContext() { return astTransformationsContext; } public ClassNodeResolver getClassNodeResolver() { return classNodeResolver; } public void setClassNodeResolver(final ClassNodeResolver classNodeResolver) { this.classNodeResolver = classNodeResolver; } public Set getJavaCompilationUnitSet() { return javaCompilationUnitSet; } public void addJavaCompilationUnits(final Set javaCompilationUnitSet) { this.javaCompilationUnitSet.addAll(javaCompilationUnitSet); } /** * Returns the class loader for loading AST transformations. */ public GroovyClassLoader getTransformLoader() { return Optional.ofNullable(getASTTransformationsContext().getTransformLoader()).orElseGet(this::getClassLoader); } //--------------------------------------------------------------------------- // SOURCE CREATION /** * Adds a set of file paths to the unit. */ public void addSources(final String[] paths) { for (String path : paths) { addSource(new File(path)); } } /** * Adds a set of source files to the unit. */ public void addSources(final File[] files) { for (File file : files) { addSource(file); } } /** * Adds a source file to the unit. */ public SourceUnit addSource(final File file) { return addSource(new SourceUnit(file, getConfiguration(), getClassLoader(), getErrorCollector())); } /** * Adds a source file to the unit. */ public SourceUnit addSource(final URL url) { return addSource(new SourceUnit(url, getConfiguration(), getClassLoader(), getErrorCollector())); } /** * Adds a InputStream source to the unit. */ public SourceUnit addSource(final String name, final InputStream stream) { ReaderSource source = new InputStreamReaderSource(stream, getConfiguration()); return addSource(new SourceUnit(name, source, getConfiguration(), getClassLoader(), getErrorCollector())); } public SourceUnit addSource(final String name, final String scriptText) { return addSource(new SourceUnit(name, scriptText, getConfiguration(), getClassLoader(), getErrorCollector())); } /** * Adds a SourceUnit to the unit. */ public SourceUnit addSource(final SourceUnit source) { String name = source.getName(); source.setClassLoader(getClassLoader()); for (SourceUnit su : queuedSources) { if (name.equals(su.getName())) return su; } queuedSources.add(source); return source; } /** * Returns an iterator on the unit's SourceUnits. */ public Iterator iterator() { return new Iterator() { private Iterator nameIterator = sources.keySet().iterator(); @Override public boolean hasNext() { return nameIterator.hasNext(); } @Override public SourceUnit next() { String name = nameIterator.next(); return sources.get(name); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } /** * Adds a ClassNode directly to the unit (i.e. without source). * WARNING: the source is needed for error reporting, using * this method without setting a SourceUnit will cause * NullPointerExceptions */ public void addClassNode(final ClassNode node) { ModuleNode module = new ModuleNode(getAST()); getAST().addModule(module); module.addClass(node); } //--------------------------------------------------------------------------- // EXTERNAL CALLBACKS /** * A callback interface you can use during the {@code classgen} * phase of compilation as the compiler traverses the ClassNode tree. * You will be called-back for each primary and inner class. * Use setClassgenCallback() before running compile() to set your callback. */ @FunctionalInterface public interface ClassgenCallback { void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException; } public ClassgenCallback getClassgenCallback() { return classgenCallback; } /** * Sets a ClassgenCallback. You can have only one, and setting * it to {@code null} removes any existing setting. */ public void setClassgenCallback(final ClassgenCallback visitor) { this.classgenCallback = visitor; } /** * A callback interface you can use to get a callback after every * unit of the compile process. You will be called-back with a * ProcessingUnit and a phase indicator. Use setProgressCallback() * before running compile() to set your callback. */ @FunctionalInterface public interface ProgressCallback { void call(ProcessingUnit context, int phase) throws CompilationFailedException; } public ProgressCallback getProgressCallback() { return progressCallback; } /** * Sets a ProgressCallback. You can have only one, and setting * it to {@code null} removes any existing setting. */ public void setProgressCallback(final ProgressCallback callback) { this.progressCallback = callback; } //--------------------------------------------------------------------------- // ACTIONS /** * Synonym for {@code compile(Phases.ALL)}. */ public void compile() throws CompilationFailedException { compile(Phases.ALL); } /** * Compiles the compilation unit from sources. */ public void compile(int throughPhase) throws CompilationFailedException { // to support incremental compilation, always restart the compiler; // individual passes are responsible for not re-processing old code gotoPhase(Phases.INITIALIZATION); throughPhase = Math.min(throughPhase, Phases.ALL); while (throughPhase >= phase && phase <= Phases.ALL) { if (phase == Phases.CONVERSION) { (sources.size() > 1 && Boolean.TRUE.equals(configuration.getOptimizationOptions().get(CompilerConfiguration.PARALLEL_PARSE)) ? sources.values().parallelStream() : sources.values().stream() ).forEach(SourceUnit::buildAST); } try { processPhaseOperations(phase); } catch (ResolveVisitor.Interrupt x) { assert !queuedSources.isEmpty(); } if (dequeued()) continue; // bring new sources into phase // Grab processing may have brought in new AST transforms into various phases, process them as well processNewPhaseOperations(phase); Optional.ofNullable(getProgressCallback()) .ifPresent(callback -> callback.call(this, phase)); completePhase(); mark(); gotoPhase(phase + 1); if (phase == Phases.CLASS_GENERATION) { getAST().getModules().forEach(ModuleNode::sortClasses); } } getErrorCollector().failIfErrors(); } private void processPhaseOperations(final int phase) { for (PhaseOperation op : phaseOperations[phase]) { op.doPhaseOperation(this); } } private void processNewPhaseOperations(final int phase) { recordPhaseOpsInAllOtherPhases(phase); Deque currentPhaseNewOps = newPhaseOperations[phase]; while (!currentPhaseNewOps.isEmpty()) { PhaseOperation operation = currentPhaseNewOps.removeFirst(); // push this operation to master list and then process it phaseOperations[phase].add(operation); operation.doPhaseOperation(this); // if this operation has brought in more phase ops for ast transforms, keep recording them // in master list of other phases and keep processing them for this phase recordPhaseOpsInAllOtherPhases(phase); currentPhaseNewOps = newPhaseOperations[phase]; } } private void recordPhaseOpsInAllOtherPhases(final int phase) { // apart from current phase, push new operations for every other phase in the master phase ops list for (int ph = Phases.INITIALIZATION; ph <= Phases.ALL; ph += 1) { if (ph != phase && !newPhaseOperations[ph].isEmpty()) { phaseOperations[ph].addAll(newPhaseOperations[ph]); newPhaseOperations[ph].clear(); } } } /** * Dequeues any source units added through addSource and resets the compiler * phase to initialization. *

* Note: this does not mean a file is recompiled. If a SourceUnit has already * passed a phase it is skipped until a higher phase is reached. * * @return true if there was a queued source * @throws CompilationFailedException */ protected boolean dequeued() throws CompilationFailedException { if (!queuedSources.isEmpty()) { SourceUnit unit; while ((unit = queuedSources.poll()) != null) { sources.put(unit.getName(), unit); } gotoPhase(Phases.INITIALIZATION); return true; } return false; } private final IPrimaryClassNodeOperation verification = new IPrimaryClassNodeOperation() { @Override public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException { new OptimizerVisitor(CompilationUnit.this).visitClass(classNode, source); // GROOVY-4272: repositioned from static import visitor GroovyClassVisitor visitor = new Verifier(); try { visitor.visitClass(classNode); } catch (RuntimeParserException rpe) { getErrorCollector().addError(new SyntaxException(rpe.getMessage(), rpe.getNode()), source); } visitor = new LabelVerifier(source); visitor.visitClass(classNode); visitor = new InstanceOfVerifier() { @Override protected SourceUnit getSourceUnit() { return source; } }; visitor.visitClass(classNode); visitor = new ClassCompletionVerifier(source); visitor.visitClass(classNode); visitor = new ExtendedVerifier(source); visitor.visitClass(classNode); // because the class may be generated even if an error was found // and that class may have an invalid format we fail here if needed getErrorCollector().failIfErrors(); } @Override public boolean needSortedInput() { return true; } }; /** * Generates bytecode for a single {@code ClassNode}. */ private final IPrimaryClassNodeOperation classgen = new IPrimaryClassNodeOperation() { @Override public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException { // // Prep the generator machinery // ClassVisitor classVisitor = createClassVisitor(); String sourceName = (source == null ? classNode.getModule().getDescription() : source.getName()); // only show the file name and its extension like javac does in its stacktraces rather than the full path // also takes care of both \ and / depending on the host compiling environment if (sourceName != null) { sourceName = sourceName.substring(Math.max(sourceName.lastIndexOf('\\'), sourceName.lastIndexOf('/')) + 1); } // // Run the generation and create the class (if required) // AsmClassGenerator visitor = new AsmClassGenerator(source, context, classVisitor, sourceName); visitor.visitClass(classNode); byte[] bytes = ((ClassWriter) classVisitor).toByteArray(); getClasses().add(new GroovyClass(classNode.getName(), bytes)); // // Handle any callback that's been set // if (classgenCallback != null) { classgenCallback.call(classVisitor, classNode); } // // Recurse for generated classes // Deque innerClasses = visitor.getInnerClasses(); while (!innerClasses.isEmpty()) { ClassNode innerClass = innerClasses.removeFirst(); verification.call(source, context, innerClass); classgen.call(source, context, innerClass); } } @Override public boolean needSortedInput() { return true; } }; protected ClassVisitor createClassVisitor() { return new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) { private ClassNode getClassNode(String name) { // try classes under compilation CompileUnit cu = getAST(); ClassNode cn = cu.getClass(name); if (cn != null) return cn; // try inner classes cn = cu.getGeneratedInnerClass(name); if (cn != null) return cn; ClassNodeResolver.LookupResult lookupResult = getClassNodeResolver().resolveName(name, CompilationUnit.this); return lookupResult == null ? null : lookupResult.getClassNode(); } private ClassNode getCommonSuperClassNode(ClassNode c, ClassNode d) { // adapted from ClassWriter code if (c.isDerivedFrom(d)) return d; if (d.isDerivedFrom(c)) return c; if (c.isInterface() || d.isInterface()) return ClassHelper.OBJECT_TYPE; do { c = c.getSuperClass(); } while (c != null && !d.isDerivedFrom(c)); if (c == null) return ClassHelper.OBJECT_TYPE; return c; } @Override protected String getCommonSuperClass(String arg1, String arg2) { ClassNode a = getClassNode(arg1.replace('/', '.')); ClassNode b = getClassNode(arg2.replace('/', '.')); return getCommonSuperClassNode(a,b).getName().replace('.','/'); } }; } //--------------------------------------------------------------------------- // PHASE HANDLING /** * Updates the phase marker on all sources. */ protected void mark() throws CompilationFailedException { ISourceUnitOperation mark = (final SourceUnit source) -> { if (source.phase < phase) { source.gotoPhase(phase); } if (source.phase == phase && phaseComplete && !source.phaseComplete) { source.completePhase(); } }; mark.doPhaseOperation(this); } //--------------------------------------------------------------------------- // LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS private interface PhaseOperation { void doPhaseOperation(CompilationUnit unit); } @FunctionalInterface public interface ISourceUnitOperation extends PhaseOperation { void call(SourceUnit source) throws CompilationFailedException; /** * A loop driver for applying operations to all SourceUnits. * Automatically skips units that have already been processed * through the current phase. */ @Override default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException { for (SourceUnit source : unit.sources.values()) { if (source.phase < unit.phase || (source.phase == unit.phase && !source.phaseComplete)) { try { this.call(source); } catch (CompilationFailedException e) { throw e; } catch (Exception e) { GroovyBugError gbe = new GroovyBugError(e); unit.changeBugText(gbe, source); throw gbe; } catch (GroovyBugError e) { unit.changeBugText(e, source); throw e; } } } unit.getErrorCollector().failIfErrors(); } } //--------------------------------------------------------------------------- // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS @FunctionalInterface public interface IPrimaryClassNodeOperation extends PhaseOperation { void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException; /** * A loop driver for applying operations to all primary ClassNodes in * our AST. Automatically skips units that have already been processed * through the current phase. */ @Override default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException { for (ClassNode classNode : unit.getPrimaryClassNodes(this.needSortedInput())) { SourceUnit context = null; try { context = classNode.getModule().getContext(); if (context == null || context.phase < unit.phase || (context.phase == unit.phase && !context.phaseComplete)) { int offset = 1; for (Iterator it = classNode.getInnerClasses(); it.hasNext(); ) { it.next(); offset += 1; } this.call(context, new GeneratorContext(unit.getAST(), offset), classNode); } } catch (CompilationFailedException e) { // fall through } catch (NullPointerException npe) { GroovyBugError gbe = new GroovyBugError("unexpected NullPointerException", npe); unit.changeBugText(gbe, context); throw gbe; } catch (GroovyBugError e) { unit.changeBugText(e, context); throw e; } catch (Exception | LinkageError e) { ErrorCollector errorCollector = null; // check for a nested compilation exception for (Throwable t = e.getCause(); t != e && t != null; t = t.getCause()) { if (t instanceof MultipleCompilationErrorsException) { errorCollector = ((MultipleCompilationErrorsException) t).getErrorCollector(); break; } } if (errorCollector != null) { unit.getErrorCollector().addCollectorContents(errorCollector); } else { if (e instanceof GroovyRuntimeException) { GroovyRuntimeException gre = (GroovyRuntimeException) e; context = Optional.ofNullable(gre.getModule()).map(ModuleNode::getContext).orElse(context); } if (context != null) { if (e instanceof SyntaxException) { unit.getErrorCollector().addError((SyntaxException) e, context); } else if (e.getCause() instanceof SyntaxException) { unit.getErrorCollector().addError((SyntaxException) e.getCause(), context); } else { unit.getErrorCollector().addException(e instanceof Exception ? (Exception) e : new RuntimeException(e), context); } } else { unit.getErrorCollector().addError(new ExceptionMessage(e instanceof Exception ? (Exception) e : new RuntimeException(e), unit.debug, unit)); } } } } unit.getErrorCollector().failIfErrors(); } default boolean needSortedInput() { return false; } } @FunctionalInterface public interface IGroovyClassOperation extends PhaseOperation { void call(GroovyClass groovyClass) throws CompilationFailedException; @Override default void doPhaseOperation(final CompilationUnit unit) throws CompilationFailedException { if (unit.phase != Phases.OUTPUT && !(unit.phase == Phases.CLASS_GENERATION && unit.phaseComplete)) { throw new GroovyBugError("CompilationUnit not ready for output(). Current phase=" + unit.getPhaseDescription()); } for (GroovyClass groovyClass : unit.getClasses()) { try { this.call(groovyClass); } catch (CompilationFailedException e) { // fall through } catch (NullPointerException npe) { throw npe; } catch (GroovyBugError e) { unit.changeBugText(e, null); throw e; } catch (Exception e) { throw new GroovyBugError(e); } } unit.getErrorCollector().failIfErrors(); } } private static int getSuperClassCount(ClassNode classNode) { int count = 0; while (classNode != null) { count += 1; classNode = classNode.getSuperClass(); } return count; } private static int getInterfacesCount(final ClassNode classNode) { int count = 1; for (ClassNode face : classNode.getInterfaces()) { count = Math.max(count, getInterfacesCount(face) + 1); } return count; } private List getPrimaryClassNodes(final boolean sort) { List classes = getAST().getClasses(); if (sort && classes.size() > 1) { classes.sort(Comparator.comparingInt(cn -> { int count; if (cn.isInterface()) { count = getInterfacesCount(cn); } else { count = getSuperClassCount(cn) + 1000; } if (cn.getOuterClass() == null && cn.getInnerClasses().hasNext()) { count += 2000; // GROOVY-10687: nest host must follow members (with closures) } return count; })); } return classes; } private void changeBugText(final GroovyBugError e, final SourceUnit context) { e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + (context != null ? context.getName() : "?") + "' " + e.getBugText()); } //-------------------------------------------------------------------------- @Deprecated public void addPhaseOperation(final GroovyClassOperation op) { addPhaseOperation((IGroovyClassOperation) op); } @Deprecated public void addPhaseOperation(final SourceUnitOperation op, final int phase) { addPhaseOperation((ISourceUnitOperation) op, phase); } @Deprecated public void addPhaseOperation(final PrimaryClassNodeOperation op, final int phase) { addPhaseOperation((IPrimaryClassNodeOperation) op, phase); } @Deprecated public void addFirstPhaseOperation(final PrimaryClassNodeOperation op, final int phase) { addFirstPhaseOperation((IPrimaryClassNodeOperation) op, phase); } @Deprecated public void addNewPhaseOperation(final SourceUnitOperation op, final int phase) { addNewPhaseOperation((ISourceUnitOperation) op, phase); } @Deprecated public void applyToSourceUnits(final SourceUnitOperation op) throws CompilationFailedException { op.doPhaseOperation(this); } @Deprecated public void applyToPrimaryClassNodes(final PrimaryClassNodeOperation op) throws CompilationFailedException { op.doPhaseOperation(this); } @Deprecated public abstract static class SourceUnitOperation implements ISourceUnitOperation { } @Deprecated public abstract static class GroovyClassOperation implements IGroovyClassOperation { } @Deprecated public abstract static class PrimaryClassNodeOperation implements IPrimaryClassNodeOperation { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy