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

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

There is a newer version: 3.0.8-01
Show 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.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.VariableScopeVisitor;
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.SimpleMessage;
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 groovyjarjarasm.asm.ClassVisitor;
import groovyjarjarasm.asm.ClassWriter;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
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 java.util.stream.Collectors.toList;
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<>(); } } /** Controls behavior of {@link #classgen()} and other routines. */ protected boolean debug; /** True after the first {@link #configure(CompilerConfiguration)} operation. */ protected boolean configured; /** A callback for use during {@link #classgen()} */ protected ClassgenCallback classgenCallback; /** A callback for use during {@link #compile()} */ protected ProgressCallback progressCallback; protected ClassNodeResolver classNodeResolver = new ClassNodeResolver(); protected ResolveVisitor resolveVisitor = new ResolveVisitor(this); protected final Verifier verifier = new Verifier(); /** 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) { /* GRECLIPSE edit this(configuration, codeSource, loader, null); */ this(configuration, codeSource, loader, null, true, null); // GRECLIPSE end } /** * 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 /*GRECLIPSE add*/, final boolean allowTransforms, final String legacyString/*GRECLIPSE end*/) { super(configuration, loader, null); // GRECLIPSE add this.allowTransforms = allowTransforms; // GRECLIPSE end 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(resolve, 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) -> { 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.iterateClassNodeToCompile(); it.hasNext(); ) { String name = it.next(); StringBuilder message = new StringBuilder(); message .append("Compilation incomplete: expected to find the class ") .append(name) .append(" in ") .append(source.getName()); if (classes.isEmpty()) { message.append(", but the file seems not to contain any classes"); } else { message.append(", but the file contains the classes: "); boolean first = true; for (ClassNode cn : classes) { if (first) { first = false; } else { message.append(", "); } message.append(cn.getName()); } } getErrorCollector().addErrorAndContinue( new SimpleMessage(message.toString(), this) ); it.remove(); } } }, Phases.CANONICALIZATION); addPhaseOperation(classgen, Phases.CLASS_GENERATION); /* GRECLIPSE edit -- skip output phase 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)) { byte[] bytes = groovyClass.getBytes(); stream.write(bytes, 0, bytes.length); } catch (IOException e) { getErrorCollector().addError(Message.create(e.getMessage(), this)); } }); */ addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> { AnnotationCollectorTransform.ClassChanger xformer = new AnnotationCollectorTransform.ClassChanger(); xformer.transformClass(classNode); }, Phases.SEMANTIC_ANALYSIS); 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 (ie. without source). * WARNING: the source is needed for error reporting, using * this method without setting a SourceUnit will cause * NullPinterExceptions */ 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 to "accompany" the classgen() * code as it 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 delta compilations, we always restart // the compiler. The individual passes are responsible // for not reprocessing old code. gotoPhase(Phases.INITIALIZATION); throughPhase = Math.min(throughPhase, Phases.ALL); while (throughPhase >= phase && phase <= Phases.ALL) { // GRECLIPSE add if (phase == Phases.CONVERSION) { if (sources.size() > 1 && Boolean.TRUE.equals(configuration.getOptimizationOptions().get(CompilerConfiguration.PARALLEL_PARSE))) { sources.values().parallelStream().forEach(SourceUnit::buildAST); } else { sources.values().forEach(SourceUnit::buildAST); } } else // GRECLIPSE end if (phase == Phases.SEMANTIC_ANALYSIS) { resolve.doPhaseOperation(this); if (dequeued()) continue; } /* GRECLIPSE edit if (phase == Phases.CONVERSION) { buildASTs(); } */ processPhaseOperations(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(); if (dequeued()) continue; gotoPhase(phase + 1); if (phase == Phases.CLASS_GENERATION) { sortClasses(); } } getErrorCollector().failIfErrors(); } /* GRECLIPSE edit private void buildASTs() { Boolean bpe = configuration.getOptimizationOptions().get(CompilerConfiguration.PARALLEL_PARSE); boolean parallelParseEnabled = null != bpe && bpe; Collection sourceUnits = sources.values(); Stream sourceUnitStream = (!parallelParseEnabled || sourceUnits.size() < 2) ? sourceUnits.stream() // no need to build AST with parallel stream when we just have one/no source unit : sourceUnits.parallelStream(); sourceUnitStream.forEach(SourceUnit::buildAST); } */ 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(); } } } private void sortClasses() { for (ModuleNode module : getAST().getModules()) { module.sortClasses(); } } /** * Dequeues any source units add 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 { boolean dequeue = !queuedSources.isEmpty(); while (!queuedSources.isEmpty()) { SourceUnit unit = queuedSources.remove(); String name = unit.getName(); sources.put(name, unit); } if (dequeue) { gotoPhase(Phases.INITIALIZATION); } return dequeue; } /** * Resolves all types. */ private final ISourceUnitOperation resolve = (final SourceUnit source) -> { for (ClassNode classNode : source.getAST().getClasses()) { GroovyClassVisitor visitor = new VariableScopeVisitor(source); visitor.visitClass(classNode); resolveVisitor.setClassNodeResolver(classNodeResolver); resolveVisitor.startResolving(classNode, source); } }; /** * Runs {@link #classgen()} on 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 { new OptimizerVisitor(CompilationUnit.this).visitClass(classNode, source); // GROOVY-4272: repositioned from static import visitor // // Run the Verifier on the outer class // GroovyClassVisitor visitor = 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 a error was found // and that class may have an invalid format we fail here if needed getErrorCollector().failIfErrors(); // // 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); } // GRECLIPSE add -- if there are errors, don't generate code // code gen can fail unexpectedly if there was an earlier error if (source != null && source.getErrorCollector().hasErrors()) return; // GRECLIPSE end // // Run the generation and create the class (if required) // visitor = new AsmClassGenerator(source, context, classVisitor, sourceName); visitor.visitClass(classNode); byte[] bytes = ((ClassWriter) classVisitor).toByteArray(); getClasses().add(new GroovyClass(classNode.getName(), bytes/*GRECLIPSE add*/, classNode, source/*GRECLIPSE end*/)); // // Handle any callback that's been set // if (classgenCallback != null) { classgenCallback.call(classVisitor, classNode); } // GRECLIPSE add if (progressListener != null) { progressListener.generateComplete(phase, classNode); } // GRECLIPSE end // // Recurse for inner classes // LinkedList innerClasses = ((AsmClassGenerator) visitor).getInnerClasses(); while (!innerClasses.isEmpty()) { classgen.call(source, context, innerClasses.removeFirst()); } } @Override public boolean needSortedInput() { return true; } }; protected ClassVisitor createClassVisitor() { CompilerConfiguration config = getConfiguration(); int computeMaxStackAndFrames = ClassWriter.COMPUTE_MAXS; if (CompilerConfiguration.isPostJDK7(config.getTargetBytecode()) || config.isIndyEnabled()) { computeMaxStackAndFrames += ClassWriter.COMPUTE_FRAMES; } return new ClassWriter(computeMaxStackAndFrames) { 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; // GRECLIPSE add -- try JDT model cn = getResolveVisitor().resolve(name); if (cn != null) return cn; // GRECLIPSE end 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 { // GRECLIPSE edit -- prevent concurrent modification exceptions for (String name : unit.sources.keySet().toArray(new String[unit.sources.size()])) { SourceUnit source = unit.sources.get(name); if (source.phase < unit.phase || (source.phase == unit.phase && !source.phaseComplete)) { try { this.call(source); // GRECLIPSE add if (unit.phase == Phases.CONVERSION && unit.phaseOperations[unit.phase].getLast() == this) { if (unit.progressListener != null) unit.progressListener.parseComplete(unit.phase, name); } // GRECLIPSE end } 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 getSuperInterfaceCount(final ClassNode classNode) { int count = 1; for (ClassNode face : classNode.getInterfaces()) { count = Math.max(count, getSuperInterfaceCount(face) + 1); } return count; } private List getPrimaryClassNodes(final boolean sort) { // GRECLIPSE add if (sort) { List sorted = this.ast.getSortedClasses(); if (sorted != null) return sorted; } // GRECLIPSE end List unsorted = getAST().getModules().stream() .flatMap(module -> module.getClasses().stream()).collect(toList()); if (!sort) return unsorted; int n = unsorted.size(); /* GRECLIPSE edit int[] indexClass = new int[n]; int[] indexInterface = new int[n]; { int i = 0; for (ClassNode element : unsorted) { if (element.isInterface()) { indexInterface[i] = getSuperInterfaceCount(element); indexClass[i] = -1; } else { indexClass[i] = getSuperClassCount(element); indexInterface[i] = -1; } i += 1; } } List sorted = getSorted(indexInterface, unsorted); sorted.addAll(getSorted(indexClass, unsorted)); */ // Sort them by how many types are in their hierarchy, but all interfaces first. // Algorithm: // Create a list of integers. Each integer captures the index into the unsorted // list (bottom 16 bits) and the count of how many types are in that types // hierarchy (top 16 bits). For classes the count is augmented so that when // sorting the classes will come out after the interfaces. This list of integers // is sorted. We then just go through it and for the lower 16 bits of each entry // that is the index of the next value to pull from the unsorted list and put into // the sorted list. int[] countIndexPairs = new int[n]; int count, index = 0; for (ClassNode node : unsorted) { if (node.isInterface()) { count = getSuperInterfaceCount(node); } else { count = getSuperClassCount(node) + 5000; } countIndexPairs[index] = ((count << 16) + index); index += 1; } Arrays.sort(countIndexPairs); List sorted = new ArrayList<>(index); for (int i : countIndexPairs) { sorted.add(unsorted.get(i & 0xFFFF)); } this.ast.setSortedClasses(sorted); // GRECLIPSE end return sorted; } /* GRECLIPSE edit private static List getSorted(final int[] index, final List unsorted) { int unsortedSize = unsorted.size(); List sorted = new ArrayList<>(unsortedSize); for (int i = 0; i < unsortedSize; i += 1) { int min = -1; for (int j = 0; j < unsortedSize; j += 1) { if (index[j] == -1) continue; if (min == -1 || index[j] < index[min]) { min = j; } } if (min == -1) break; sorted.add(unsorted.get(min)); index[min] = -1; } return sorted; } */ private void changeBugText(final GroovyBugError e, final SourceUnit context) { e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + (context != null ? context.getName() : "?") + "' " + e.getBugText()); } // GRECLIPSE add public interface ProgressListener { void parseComplete(int phase, String sourceUnitName); void generateComplete(int phase, ClassNode classNode); } public ProgressListener getProgressListener() { return this.progressListener; } public void setProgressListener(final ProgressListener progressListener) { this.progressListener = progressListener; } public ResolveVisitor getResolveVisitor() { return this.resolveVisitor; } public void setResolveVisitor(final ResolveVisitor resolveVisitor) { this.resolveVisitor = resolveVisitor; } public void ensureASTTransformVisitorAdded() { ASTTransformationVisitor.addPhaseOperations(this); } public String toString() { if (sources != null && !sources.isEmpty()) { String source = sources.keySet().iterator().next(); return "CompilationUnit: first source is " + source; } return super.toString(); } /** * Modifies the behaviour of the phases based on what the caller really needs. * Some invocations of the compilation infrastructure don't need the bytecode, * so we can skip creating it; they would rather have a more "source like" AST. */ public void tweak(final boolean isReconcile) { verifier.inlineStaticFieldInitializersIntoClinit = !isReconcile; } public final boolean allowTransforms; private ProgressListener progressListener; // GRECLIPSE end //-------------------------------------------------------------------------- @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