blob: a2233bdcc7a1a7c18557dc34d363ed2617e0dba6 [file] [log] [blame]
/*
* 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.Collection;
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 java.util.stream.Collectors;
import java.util.stream.Stream;
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);
}
/**
* 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<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;
}
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();
}
private void buildASTs() {
Boolean bpe = configuration.getOptimizationOptions().get(CompilerConfiguration.PARALLEL_PARSE);
boolean parallelParseEnabled = null != bpe && bpe;
Collection<SourceUnit> sourceUnits = sources.values();
Stream<SourceUnit> 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();
// DON'T replace `collect(Collectors.counting())` with `count()` here, otherwise peek will NOT be triggered
sourceUnitStream.peek(SourceUnit::buildAST).collect(Collectors.counting());
}
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 class generation 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 (Map.Entry<String, SourceUnit> entry : unit.sources.entrySet()) {
SourceUnit source = entry.getValue();
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 {
}
}