| /* |
| * 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.Message; |
| 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 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.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). |
| * <p> |
| * 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<String, SourceUnit> sources = new LinkedHashMap<>(); |
| protected Queue<SourceUnit> queuedSources = new LinkedList<>(); |
| |
| /** The classes generated during classgen. */ |
| private List<GroovyClass> generatedClasses = new ArrayList<>(); |
| |
| private Deque<PhaseOperation>[] phaseOperations; |
| private Deque<PhaseOperation>[] 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); |
| |
| /** The AST transformations state data. */ |
| protected ASTTransformationsContext astTransformationsContext; |
| |
| private Set<javax.tools.JavaFileObject> 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. |
| * <p> |
| * <b>Note</b>: 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(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<ClassNode> classes = source.getAST().getClasses(); |
| for (ClassNode node : classes) { |
| CompileUnit cu = node.getCompileUnit(); |
| for (Iterator<String> 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); |
| |
| 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<GroovyClass> 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<javax.tools.JavaFileObject> getJavaCompilationUnitSet() { |
| return javaCompilationUnitSet; |
| } |
| |
| public void addJavaCompilationUnits(final Set<javax.tools.JavaFileObject> javaCompilationUnitSet) { |
| this.javaCompilationUnitSet.addAll(javaCompilationUnitSet); |
| } |
| |
| /** |
| * @return 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<SourceUnit> iterator() { |
| return new Iterator<SourceUnit>() { |
| private Iterator<String> 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) { |
| |
| if (phase == Phases.SEMANTIC_ANALYSIS) { |
| resolve.doPhaseOperation(this); |
| if (dequeued()) continue; |
| } |
| |
| 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(); |
| } |
| |
| private void processPhaseOperations(final int phase) { |
| for (PhaseOperation op : phaseOperations[phase]) { |
| op.doPhaseOperation(this); |
| } |
| } |
| |
| private void processNewPhaseOperations(final int phase) { |
| recordPhaseOpsInAllOtherPhases(phase); |
| Deque<PhaseOperation> 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. |
| * <p> |
| * 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 = 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 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); |
| } |
| |
| // |
| // 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)); |
| |
| // |
| // Handle any callback that's been set |
| // |
| Optional.ofNullable(getClassgenCallback()) |
| .ifPresent(callback -> callback.call(classVisitor, classNode)); |
| |
| // |
| // Recurse for inner classes |
| // |
| LinkedList<ClassNode> 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; |
| 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 (String name : unit.sources.keySet()) { |
| SourceUnit source = unit.sources.get(name); |
| 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<InnerClassNode> 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<ClassNode> getPrimaryClassNodes(final boolean sort) { |
| List<ClassNode> unsorted = getAST().getModules().stream() |
| .flatMap(module -> module.getClasses().stream()).collect(toList()); |
| |
| if (!sort) return unsorted; |
| |
| int n = unsorted.size(); |
| 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<ClassNode> sorted = getSorted(indexInterface, unsorted); |
| sorted.addAll(getSorted(indexClass, unsorted)); |
| return sorted; |
| } |
| |
| private static List<ClassNode> getSorted(final int[] index, final List<ClassNode> unsorted) { |
| int unsortedSize = unsorted.size(); |
| List<ClassNode> 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()); |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| @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 { |
| } |
| } |