Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hazelcast.shaded.org.codehaus.janino.UnitCompiler Maven / Gradle / Ivy
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2019 Arno Unkrig. All rights reserved.
* Copyright (c) 2015-2016 TIBCO Software Inc. All rights reserved. // CHECKSTYLE:OFF CHECKSTYLE:ON
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.hazelcast.shaded.org.codehaus.janino;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import com.hazelcast.shaded.org.codehaus.commons.compiler.CompileException;
import com.hazelcast.shaded.org.codehaus.commons.compiler.ErrorHandler;
import com.hazelcast.shaded.org.codehaus.commons.compiler.InternalCompilerException;
import com.hazelcast.shaded.org.codehaus.commons.compiler.Location;
import com.hazelcast.shaded.org.codehaus.commons.compiler.WarningHandler;
import com.hazelcast.shaded.org.codehaus.commons.compiler.util.Numbers;
import com.hazelcast.shaded.org.codehaus.commons.compiler.util.SystemProperties;
import com.hazelcast.shaded.org.codehaus.commons.compiler.util.iterator.Iterables;
import com.hazelcast.shaded.org.codehaus.commons.nullanalysis.Nullable;
import com.hazelcast.shaded.org.codehaus.janino.CodeContext.BasicBlock;
import com.hazelcast.shaded.org.codehaus.janino.CodeContext.Inserter;
import com.hazelcast.shaded.org.codehaus.janino.CodeContext.Offset;
import com.hazelcast.shaded.org.codehaus.janino.IClass.IAnnotation;
import com.hazelcast.shaded.org.codehaus.janino.IClass.IConstructor;
import com.hazelcast.shaded.org.codehaus.janino.IClass.IField;
import com.hazelcast.shaded.org.codehaus.janino.IClass.IInvocable;
import com.hazelcast.shaded.org.codehaus.janino.IClass.IMethod;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractCompilationUnit;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractCompilationUnit.ImportDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractCompilationUnit.SingleStaticImportDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractCompilationUnit.SingleTypeImportDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractCompilationUnit.StaticImportOnDemandDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractCompilationUnit.TypeImportOnDemandDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AbstractTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AccessModifier;
import com.hazelcast.shaded.org.codehaus.janino.Java.AlternateConstructorInvocation;
import com.hazelcast.shaded.org.codehaus.janino.Java.AmbiguousName;
import com.hazelcast.shaded.org.codehaus.janino.Java.Annotation;
import com.hazelcast.shaded.org.codehaus.janino.Java.AnnotationTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.AnonymousClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.ArrayAccessExpression;
import com.hazelcast.shaded.org.codehaus.janino.Java.ArrayCreationReference;
import com.hazelcast.shaded.org.codehaus.janino.Java.ArrayInitializer;
import com.hazelcast.shaded.org.codehaus.janino.Java.ArrayInitializerOrRvalue;
import com.hazelcast.shaded.org.codehaus.janino.Java.ArrayLength;
import com.hazelcast.shaded.org.codehaus.janino.Java.ArrayType;
import com.hazelcast.shaded.org.codehaus.janino.Java.AssertStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.Assignment;
import com.hazelcast.shaded.org.codehaus.janino.Java.Atom;
import com.hazelcast.shaded.org.codehaus.janino.Java.BinaryOperation;
import com.hazelcast.shaded.org.codehaus.janino.Java.Block;
import com.hazelcast.shaded.org.codehaus.janino.Java.BlockStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.BooleanLiteral;
import com.hazelcast.shaded.org.codehaus.janino.Java.BooleanRvalue;
import com.hazelcast.shaded.org.codehaus.janino.Java.BreakStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.BreakableStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.Cast;
import com.hazelcast.shaded.org.codehaus.janino.Java.CatchClause;
import com.hazelcast.shaded.org.codehaus.janino.Java.CatchParameter;
import com.hazelcast.shaded.org.codehaus.janino.Java.CharacterLiteral;
import com.hazelcast.shaded.org.codehaus.janino.Java.ClassInstanceCreationReference;
import com.hazelcast.shaded.org.codehaus.janino.Java.ClassLiteral;
import com.hazelcast.shaded.org.codehaus.janino.Java.CompilationUnit;
import com.hazelcast.shaded.org.codehaus.janino.Java.ConditionalExpression;
import com.hazelcast.shaded.org.codehaus.janino.Java.ConstructorDeclarator;
import com.hazelcast.shaded.org.codehaus.janino.Java.ConstructorInvocation;
import com.hazelcast.shaded.org.codehaus.janino.Java.ContinuableStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.ContinueStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.Crement;
import com.hazelcast.shaded.org.codehaus.janino.Java.DoStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.DocCommentable;
import com.hazelcast.shaded.org.codehaus.janino.Java.ElementValue;
import com.hazelcast.shaded.org.codehaus.janino.Java.ElementValueArrayInitializer;
import com.hazelcast.shaded.org.codehaus.janino.Java.ElementValuePair;
import com.hazelcast.shaded.org.codehaus.janino.Java.EmptyStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.EnclosingScopeOfTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.EnumConstant;
import com.hazelcast.shaded.org.codehaus.janino.Java.EnumDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.ExpressionStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.FieldAccess;
import com.hazelcast.shaded.org.codehaus.janino.Java.FieldAccessExpression;
import com.hazelcast.shaded.org.codehaus.janino.Java.FieldDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.FieldDeclarationOrInitializer;
import com.hazelcast.shaded.org.codehaus.janino.Java.FloatingPointLiteral;
import com.hazelcast.shaded.org.codehaus.janino.Java.ForEachStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.ForStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.FunctionDeclarator;
import com.hazelcast.shaded.org.codehaus.janino.Java.FunctionDeclarator.FormalParameter;
import com.hazelcast.shaded.org.codehaus.janino.Java.FunctionDeclarator.FormalParameters;
import com.hazelcast.shaded.org.codehaus.janino.Java.IfStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.Initializer;
import com.hazelcast.shaded.org.codehaus.janino.Java.InnerClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.Instanceof;
import com.hazelcast.shaded.org.codehaus.janino.Java.IntegerLiteral;
import com.hazelcast.shaded.org.codehaus.janino.Java.InterfaceDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.Invocation;
import com.hazelcast.shaded.org.codehaus.janino.Java.LabeledStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.LambdaExpression;
import com.hazelcast.shaded.org.codehaus.janino.Java.Literal;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalClassDeclarationStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalVariable;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalVariableAccess;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalVariableDeclarationStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.LocalVariableSlot;
import com.hazelcast.shaded.org.codehaus.janino.Java.Locatable;
import com.hazelcast.shaded.org.codehaus.janino.Java.Located;
import com.hazelcast.shaded.org.codehaus.janino.Java.Lvalue;
import com.hazelcast.shaded.org.codehaus.janino.Java.MarkerAnnotation;
import com.hazelcast.shaded.org.codehaus.janino.Java.MemberAnnotationTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.MemberClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.MemberEnumDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.MemberInterfaceDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.MemberTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.MethodDeclarator;
import com.hazelcast.shaded.org.codehaus.janino.Java.MethodInvocation;
import com.hazelcast.shaded.org.codehaus.janino.Java.MethodReference;
import com.hazelcast.shaded.org.codehaus.janino.Java.Modifier;
import com.hazelcast.shaded.org.codehaus.janino.Java.ModularCompilationUnit;
import com.hazelcast.shaded.org.codehaus.janino.Java.NamedClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.NamedTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.NewAnonymousClassInstance;
import com.hazelcast.shaded.org.codehaus.janino.Java.NewArray;
import com.hazelcast.shaded.org.codehaus.janino.Java.NewClassInstance;
import com.hazelcast.shaded.org.codehaus.janino.Java.NewInitializedArray;
import com.hazelcast.shaded.org.codehaus.janino.Java.NormalAnnotation;
import com.hazelcast.shaded.org.codehaus.janino.Java.NullLiteral;
import com.hazelcast.shaded.org.codehaus.janino.Java.Package;
import com.hazelcast.shaded.org.codehaus.janino.Java.PackageDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.PackageMemberAnnotationTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.PackageMemberClassDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.PackageMemberEnumDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.PackageMemberInterfaceDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.PackageMemberTypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.Padder;
import com.hazelcast.shaded.org.codehaus.janino.Java.ParameterAccess;
import com.hazelcast.shaded.org.codehaus.janino.Java.ParenthesizedExpression;
import com.hazelcast.shaded.org.codehaus.janino.Java.Primitive;
import com.hazelcast.shaded.org.codehaus.janino.Java.PrimitiveType;
import com.hazelcast.shaded.org.codehaus.janino.Java.QualifiedThisReference;
import com.hazelcast.shaded.org.codehaus.janino.Java.ReferenceType;
import com.hazelcast.shaded.org.codehaus.janino.Java.ReturnStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.Rvalue;
import com.hazelcast.shaded.org.codehaus.janino.Java.RvalueMemberType;
import com.hazelcast.shaded.org.codehaus.janino.Java.Scope;
import com.hazelcast.shaded.org.codehaus.janino.Java.SimpleConstant;
import com.hazelcast.shaded.org.codehaus.janino.Java.SimpleType;
import com.hazelcast.shaded.org.codehaus.janino.Java.SingleElementAnnotation;
import com.hazelcast.shaded.org.codehaus.janino.Java.Statement;
import com.hazelcast.shaded.org.codehaus.janino.Java.StringLiteral;
import com.hazelcast.shaded.org.codehaus.janino.Java.SuperConstructorInvocation;
import com.hazelcast.shaded.org.codehaus.janino.Java.SuperclassFieldAccessExpression;
import com.hazelcast.shaded.org.codehaus.janino.Java.SuperclassMethodInvocation;
import com.hazelcast.shaded.org.codehaus.janino.Java.SwitchStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.SwitchStatement.SwitchBlockStatementGroup;
import com.hazelcast.shaded.org.codehaus.janino.Java.SynchronizedStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.ThisReference;
import com.hazelcast.shaded.org.codehaus.janino.Java.ThrowStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.TryStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.TryStatement.LocalVariableDeclaratorResource;
import com.hazelcast.shaded.org.codehaus.janino.Java.TryStatement.VariableAccessResource;
import com.hazelcast.shaded.org.codehaus.janino.Java.Type;
import com.hazelcast.shaded.org.codehaus.janino.Java.TypeArgument;
import com.hazelcast.shaded.org.codehaus.janino.Java.TypeBodyDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.TypeDeclaration;
import com.hazelcast.shaded.org.codehaus.janino.Java.TypeParameter;
import com.hazelcast.shaded.org.codehaus.janino.Java.UnaryOperation;
import com.hazelcast.shaded.org.codehaus.janino.Java.VariableDeclarator;
import com.hazelcast.shaded.org.codehaus.janino.Java.WhileStatement;
import com.hazelcast.shaded.org.codehaus.janino.Java.Wildcard;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.AbstractCompilationUnitVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.AnnotationVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.ArrayInitializerOrRvalueVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.AtomVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.BlockStatementVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.ElementValueVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.FunctionDeclaratorVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.ImportVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.LvalueVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.RvalueVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.TryStatementResourceVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.TypeDeclarationVisitor;
import com.hazelcast.shaded.org.codehaus.janino.Visitor.TypeVisitor;
import com.hazelcast.shaded.org.codehaus.janino.util.Annotatable;
import com.hazelcast.shaded.org.codehaus.janino.util.ClassFile;
import com.hazelcast.shaded.org.codehaus.janino.util.ClassFile.ClassFileException;
import com.hazelcast.shaded.org.codehaus.janino.util.ClassFile.StackMapTableAttribute;
import com.hazelcast.shaded.org.codehaus.janino.util.ClassFile.StackMapTableAttribute.ObjectVariableInfo;
import com.hazelcast.shaded.org.codehaus.janino.util.ClassFile.StackMapTableAttribute.VerificationTypeInfo;
/**
* This class actually implements the Java compiler. It is associated with exactly one compilation unit which it
* compiles.
*/
public
class UnitCompiler {
private static final Logger LOGGER = Logger.getLogger(UnitCompiler.class.getName());
private static final int defaultTargetVersion = SystemProperties.getIntegerClassProperty(UnitCompiler.class, "defaultTargetVersion", -1);
/**
* This constant determines the number of operands up to which the
*
* a.concat(b).concat(c)
*
*
* strategy is used to implement string concatenation. For more operands, the
*
*
* new StringBuilder(a).append(b).append(c).append(d).toString()
*
* strategy is chosen.
*
* A very good article from
* Tom Gibara analyzes the impact of this decision and recommends a value of three.
*
*/
private static final int STRING_CONCAT_LIMIT = 3;
/**
* Special value for the orientation parameter of the {@link #compileBoolean(Java.Rvalue,
* CodeContext.Offset, boolean)} methods, indicating that the code should be generated such that execution branches
* if the value on top of the operand stack is TRUE.
*/
public static final boolean JUMP_IF_TRUE = true;
/**
* Special value for the orientation parameter of the {@link #compileBoolean(Java.Rvalue,
* CodeContext.Offset, boolean)} methods, indicating that the code should be generated such that execution branches
* if the value on top of the operand stack is FALSE.
*/
public static final boolean JUMP_IF_FALSE = false;
private static final Pattern LOOKS_LIKE_TYPE_PARAMETER = Pattern.compile("\\p{javaUpperCase}+");
private EnumSet options = EnumSet.noneOf(JaninoOption.class);
/**
* Java version to compile for.
*/
private int targetVersion = -1;
public
UnitCompiler(AbstractCompilationUnit abstractCompilationUnit, IClassLoader iClassLoader) {
this.abstractCompilationUnit = abstractCompilationUnit;
this.iClassLoader = iClassLoader;
}
/**
* @return A reference to the currently effective compilation options; changes to it take
* effect immediately
*/
public EnumSet
options() { return this.options; }
/**
* Sets the options for all future compilations.
*/
public UnitCompiler
options(EnumSet options) {
this.options = options;
return this;
}
/**
* Generates class files that target a specified release of the virtual machine, in analogy with JAVAC's {@code
* -target} command line option.
* By default, Java 6 .class files are generated.
*/
public void
setTargetVersion(int version) { this.targetVersion = version; }
/**
* @return The {@link AbstractCompilationUnit} that this {@link UnitCompiler} compiles
*/
public AbstractCompilationUnit
getAbstractCompilationUnit() { return this.abstractCompilationUnit; }
/**
* Generates a set of {@link ClassFile} objects which represent the classes and interfaces declared in the
* compilation unit.
*
* @param generatedClassFiles Adds generated {@link ClassFile}s to this {@link Collection}
*/
public void
compileUnit(
boolean debugSource,
boolean debugLines,
boolean debugVars,
final Collection generatedClassFiles
) throws CompileException {
this.compileUnit(
debugSource,
debugLines,
debugVars,
new ClassFileConsumer() { @Override public void consume(ClassFile classFile) { generatedClassFiles.add(classFile); }
});
}
/**
* Generates a set of {@link ClassFile} objects which represent the classes and interfaces declared in the
* compilation unit.
*
* @param storesClassFiles Consumes each generated {@link ClassFile}
*/
public void
compileUnit(boolean debugSource, boolean debugLines, boolean debugVars, ClassFileConsumer storesClassFiles)
throws CompileException {
this.debugSource = debugSource;
this.debugLines = debugLines;
this.debugVars = debugVars;
if (this.storesClassFiles != null) {
throw new IllegalStateException("\"UnitCompiler.compileUnit()\" is not reentrant");
}
this.storesClassFiles = storesClassFiles;
try {
this.abstractCompilationUnit.accept(new AbstractCompilationUnitVisitor() {
@Override @Nullable public Void visitCompilationUnit(CompilationUnit cu) throws CompileException { UnitCompiler.this.compile2(cu); return null; }
@Override @Nullable public Void visitModularCompilationUnit(ModularCompilationUnit mcu) throws CompileException { UnitCompiler.this.compile2(mcu); return null; }
});
if (this.compileErrorCount > 0) {
throw new CompileException((
this.compileErrorCount
+ " error(s) while compiling unit \""
+ this.abstractCompilationUnit.fileName
+ "\""
), null);
}
} finally {
this.storesClassFiles = null;
}
}
public
interface ClassFileConsumer {
void consume(ClassFile classFile) throws IOException;
}
/**
* Compiles an (ordinary, not modular) compilation unit
*/
private void
compile2(CompilationUnit cu) throws CompileException {
for (PackageMemberTypeDeclaration pmtd : cu.packageMemberTypeDeclarations) {
try {
this.compile(pmtd);
} catch (ClassFileException cfe) {
throw new CompileException(cfe.getMessage(), pmtd.getLocation(), cfe);
} catch (RuntimeException re) {
throw new InternalCompilerException((
"Compiling \""
+ pmtd
+ "\" in "
+ pmtd.getLocation()
+ ": "
+ re.getMessage()
), re);
}
}
}
private void
compile2(ModularCompilationUnit mcu) throws CompileException {
this.compileError("Compilation of modular compilation unit not implemented");
}
// ------------ TypeDeclaration.compile() -------------
private void
compile(TypeDeclaration td) throws CompileException {
td.accept(new TypeDeclarationVisitor() {
@Override @Nullable public Void visitAnonymousClassDeclaration(AnonymousClassDeclaration acd) throws CompileException { UnitCompiler.this.compile2(acd); return null; }
@Override @Nullable public Void visitLocalClassDeclaration(LocalClassDeclaration lcd) throws CompileException { UnitCompiler.this.compile2(lcd); return null; }
@Override @Nullable public Void visitPackageMemberClassDeclaration(PackageMemberClassDeclaration pmcd) throws CompileException { UnitCompiler.this.compile2(pmcd); return null; }
@Override @Nullable public Void visitMemberInterfaceDeclaration(MemberInterfaceDeclaration mid) throws CompileException { UnitCompiler.this.compile2(mid); return null; }
@Override @Nullable public Void visitPackageMemberInterfaceDeclaration(PackageMemberInterfaceDeclaration pmid) throws CompileException { UnitCompiler.this.compile2(pmid); return null; }
@Override @Nullable public Void visitMemberClassDeclaration(MemberClassDeclaration mcd) throws CompileException { UnitCompiler.this.compile2((InnerClassDeclaration) mcd); return null; }
@Override @Nullable public Void visitMemberEnumDeclaration(MemberEnumDeclaration med) throws CompileException { UnitCompiler.this.compile2((InnerClassDeclaration) med); return null; }
@Override @Nullable public Void visitPackageMemberEnumDeclaration(PackageMemberEnumDeclaration pmed) throws CompileException { UnitCompiler.this.compile2(pmed); return null; }
@Override @Nullable public Void visitPackageMemberAnnotationTypeDeclaration(PackageMemberAnnotationTypeDeclaration pmatd) throws CompileException { UnitCompiler.this.compile2(pmatd); return null; }
@Override @Nullable public Void visitEnumConstant(EnumConstant ec) throws CompileException { UnitCompiler.this.compileError("Compilation of enum constant NYI", ec.getLocation()); return null; }
@Override @Nullable public Void visitMemberAnnotationTypeDeclaration(MemberAnnotationTypeDeclaration matd) throws CompileException { UnitCompiler.this.compileError("Compilation of member annotation type declaration NYI", matd.getLocation()); return null; }
});
}
/**
* Compiles a top-level class or enum declaration.
*/
private void
compile2(PackageMemberClassDeclaration pmcd) throws CompileException {
this.checkForConflictWithSingleTypeImport(pmcd.getName(), pmcd.getLocation());
this.checkForNameConflictWithAnotherPackageMemberTypeDeclaration(pmcd);
this.compile2((NamedClassDeclaration) pmcd);
}
/**
* Compiles a top-level interface or annotation type declaration.
*/
private void
compile2(PackageMemberInterfaceDeclaration pmid) throws CompileException {
this.checkForConflictWithSingleTypeImport(pmid.getName(), pmid.getLocation());
this.checkForNameConflictWithAnotherPackageMemberTypeDeclaration(pmid);
this.compile2((InterfaceDeclaration) pmid);
}
/**
* @see JLS8, section 7.6, "Top Level Type Declarations"
*/
private void
checkForNameConflictWithAnotherPackageMemberTypeDeclaration(PackageMemberTypeDeclaration pmtd)
throws CompileException {
CompilationUnit declaringCompilationUnit = pmtd.getDeclaringCompilationUnit();
String name = pmtd.getName();
PackageMemberTypeDeclaration otherPmtd = declaringCompilationUnit.getPackageMemberTypeDeclaration(
name
);
if (otherPmtd != null && otherPmtd != pmtd) {
this.compileError(
"Redeclaration of type \"" + name + "\", previously declared in " + otherPmtd.getLocation()
,
pmtd.getLocation()
);
}
}
/**
* @see JLS8, section 7.6, "Top Level Type Declarations"
*/
private void
checkForConflictWithSingleTypeImport(String name, Location location) throws CompileException {
String[] ss = this.getSingleTypeImport(name, location);
if (ss != null) {
this.compileError((
"Package member type declaration \""
+ name
+ "\" conflicts with single-type-import \""
+ Java.join(ss, ".")
+ "\""
), location);
}
}
private void
compile2(AbstractClassDeclaration cd) throws CompileException {
IClass iClass = this.resolve(cd);
// Check that all methods of the non-abstract class are implemented.
if (!(cd instanceof NamedClassDeclaration && ((NamedClassDeclaration) cd).isAbstract())) {
IMethod[] ms = iClass.getIMethods();
for (IMethod base : ms) {
if (base.isAbstract()) {
if ("".equals(base.getName())) continue;
IMethod override = iClass.findIMethod(base.getName(), base.getParameterTypes());
if (
override == null // It wasn't overridden
|| override.isAbstract() // It was overridden with an abstract method
// The override does not provide a covariant return type
|| !base.getReturnType().isAssignableFrom(override.getReturnType())
) {
this.compileError(
"Non-abstract class \"" + iClass + "\" must implement method \"" + base + "\"",
cd.getLocation()
);
}
}
}
}
short accessFlags = this.accessFlags(cd.getModifiers());
if (cd instanceof PackageMemberTypeDeclaration) accessFlags |= Mod.SUPER;
if (cd instanceof EnumDeclaration) accessFlags |= Mod.ENUM;
// Create "ClassFile" object.
ClassFile cf = this.newClassFile(accessFlags, iClass, iClass.getSuperclass(), iClass.getInterfaces());
// Add class annotations with retention != SOURCE.
this.compileAnnotations(cd.getAnnotations(), cf, cf);
if (cd.getEnclosingScope() instanceof Block) {
// Add an "InnerClasses" attribute entry for this anonymous class declaration on the class file (JLS8,
// section 4.7.6, "The InnerClasses Attribute").
short innerClassInfoIndex = cf.addConstantClassInfo(iClass.getDescriptor());
short innerNameIndex = (
this instanceof NamedTypeDeclaration
? cf.addConstantUtf8Info(((NamedTypeDeclaration) this).getName())
: (short) 0
);
assert cd.getAnnotations().length == 0 : "NYI";
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
(short) 0, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
accessFlags // innerClassAccessFlags
));
} else
if (cd.getEnclosingScope() instanceof TypeDeclaration) {
// Add an "InnerClasses" attribute entry for this nested class declaration on the class file (JLS8,
// section 4.7.6, "The InnerClasses Attribute").
short innerClassInfoIndex = cf.addConstantClassInfo(iClass.getDescriptor());
short outerClassInfoIndex = cf.addConstantClassInfo(
this.resolve(((TypeDeclaration) cd.getEnclosingScope())).getDescriptor()
);
short innerNameIndex = cf.addConstantUtf8Info(((MemberTypeDeclaration) cd).getName());
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
outerClassInfoIndex, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
accessFlags // innerClassAccessFlags
));
}
// Set the "SourceFile" attribute (JVMS8, section 4.7.10, "The SourceFile Attribute") on this class file.
if (this.debugSource) {
String sourceFileName;
{
String s = cd.getLocation().getFileName();
if (s != null) {
sourceFileName = new File(s).getName();
} else if (cd instanceof NamedTypeDeclaration) {
sourceFileName = ((NamedTypeDeclaration) cd).getName() + ".java";
} else {
sourceFileName = "ANONYMOUS.java";
}
}
cf.addSourceFileAttribute(sourceFileName);
}
// Add a "Deprecated" attribute (JVMS8, section 4.7.15, "The Deprecated Attribute") on this class file.
if (cd instanceof DocCommentable && ((DocCommentable) cd).hasDeprecatedDocTag()) {
cf.addDeprecatedAttribute();
}
List classInitializationStatements = new ArrayList<>();
if (cd instanceof EnumDeclaration) {
EnumDeclaration ed = (EnumDeclaration) cd;
// Create field and static initializer for each enum constant.
for (EnumConstant ec : ed.getConstants()) {
// E = new E(, [ optional-constructor-args ]);
VariableDeclarator variableDeclarator = new VariableDeclarator(
ec.getLocation(), // location
ec.name, // name
0, // brackets
new NewClassInstance( // initializer
ec.getLocation(), // location
null, // qualification
iClass, // iClass
ec.arguments != null ? ec.arguments : new Rvalue[0] // arguments
)
);
FieldDeclaration fd = new FieldDeclaration(
ec.getLocation(), // location
ec.getDocComment(), // docComment
UnitCompiler.accessModifiers(ec.getLocation(), "public", "static", "final"), // modifiers
new SimpleType(ec.getLocation(), iClass), // type
new VariableDeclarator[] { variableDeclarator } // variableDeclarators
);
fd.setDeclaringType(ed);
classInitializationStatements.add(fd);
this.addFields(fd, cf);
}
// Create the synthetic "ENUM$VALUES" field:
//
// private static final E[] ENUM$VALUES = new E[];
Location loc = ed.getLocation();
IClass enumIClass = this.resolve(ed);
FieldDeclaration fd = new FieldDeclaration(
loc, // location
null, // docComment
UnitCompiler.accessModifiers(loc, "private", "static", "final"), // modifiers
new SimpleType(loc, enumIClass), // type
new VariableDeclarator[] { // variableDeclarators)
new VariableDeclarator(
loc, // location
"ENUM$VALUES", // name
1, // brackets
new NewArray( // initializer
loc, // location
new SimpleType(loc, enumIClass), // type
new Rvalue[] { // dimExprs
new IntegerLiteral(loc, String.valueOf(ed.getConstants().size())),
},
0 // dims
)
)
}
);
((AbstractClassDeclaration) ed).addFieldDeclaration(fd);
}
// Process static initializers (a.k.a. class initializers).
for (BlockStatement fdoi : cd.fieldDeclarationsAndInitializers) {
if (
(fdoi instanceof FieldDeclaration && ((FieldDeclaration) fdoi).isStatic())
|| (fdoi instanceof Initializer && ((Initializer) fdoi).isStatic())
) classInitializationStatements.add(fdoi);
}
if (cd instanceof EnumDeclaration) {
EnumDeclaration ed = (EnumDeclaration) cd;
IClass enumIClass = this.resolve(ed);
// Initialize the elements of the synthetic "ENUM$VALUES" field:
//
// E.ENUM$VALUES[0] = E.;
// E.ENUM$VALUES[1] = E.;
// ...
int index = 0;
for (EnumConstant ec : ed.getConstants()) {
classInitializationStatements.add(
new ExpressionStatement(
new Assignment(
ec.getLocation(), // location
new ArrayAccessExpression( // lhs
ec.getLocation(), // location
new FieldAccessExpression( // lhs
ec.getLocation(),
new SimpleType(ec.getLocation(), enumIClass),
"ENUM$VALUES"
),
new IntegerLiteral(ec.getLocation(), String.valueOf(index++)) // rhs
),
"=", // operator
new FieldAccessExpression( // rhs
ec.getLocation(),
new SimpleType(ec.getLocation(), enumIClass),
ec.name
)
)
)
);
}
}
// Create class initialization method.
this.maybeCreateInitMethod(cd, cf, classInitializationStatements);
// Generate and compile the "magic" ENUM methods "E[] values()" and "E valueOf(String)".
if (cd instanceof EnumDeclaration) {
EnumDeclaration ed = (EnumDeclaration) cd;
// public static E[] values() {
// E[] tmp = new T[];
// System.arraycopy(T.ENUM$VALUES, 0, tmp, 0, );
// return tmp;
// }
Location loc = ed.getLocation();
int numberOfEnumConstants = ed.getConstants().size();
IClass enumIClass = this.resolve(ed);
// tmp = new T[];
VariableDeclarator vd = new VariableDeclarator(
loc, // location
"tmp", // name
0, // brackets
new NewArray( // optionalinitializer
loc, // location
new SimpleType(loc, enumIClass), // type
new Rvalue[] { // dimExprs
new IntegerLiteral(loc, String.valueOf(numberOfEnumConstants))
},
0 // dims
)
);
// E[] tmp = new E[];
LocalVariableDeclarationStatement lvds = new LocalVariableDeclarationStatement(
loc,
new Modifier[0],
new SimpleType(loc, this.iClassLoader.getArrayIClass(enumIClass)),
new VariableDeclarator[] { vd }
);
// public static E[] values() {
// E[] tmp = new T[];
// System.arraycopy(T.ENUM$VALUES, 0, tmp, 0, );
// return tmp;
// }
{
MethodDeclarator md = new MethodDeclarator(
loc, // location
null, // docComment
UnitCompiler.accessModifiers(loc, "public", "static"), // modifiers
null, // typeParameters
new ArrayType(new SimpleType(loc, enumIClass)), // type
"values", // name
new FormalParameters(loc), // parameters
new Type[0], // thrownExceptions
null, // defaultValue
Arrays.asList( // statements
// E[] tmp = new E[];
lvds,
// System.arraycopy(E.ENUM$VALUES, 0, tmp, 0, );
new ExpressionStatement(new MethodInvocation(
loc, // location
new SimpleType(loc, this.iClassLoader.TYPE_java_lang_System), // target
"arraycopy", // methodName
new Rvalue[] { // arguments
new FieldAccessExpression(loc, new SimpleType(loc, enumIClass), "ENUM$VALUES"), // Argument #1: E.ENUM$VALUES
new IntegerLiteral(loc, "0"), // Argument #2: 0
new LocalVariableAccess(loc, this.getLocalVariable(lvds, vd)), // Argument #3: tmp
new IntegerLiteral(loc, "0"), // Argument #4: 0
new IntegerLiteral(loc, String.valueOf(numberOfEnumConstants)), // Argument #5:
}
)),
// return tmp;
new ReturnStatement(loc, new LocalVariableAccess(loc, this.getLocalVariable(lvds, vd)))
)
);
md.setDeclaringType(ed);
this.compile(md, cf);
}
// public static E valueOf(String s) {
// return (E) Enum.valueOf(E.class, s);
// }
FormalParameter fp = new FormalParameter(
loc, // location
new Modifier[0], // modifiers
new SimpleType(loc, this.iClassLoader.TYPE_java_lang_String), // type
"s" // name
);
{
MethodDeclarator md = new MethodDeclarator(
loc, // location
null, // docComment
UnitCompiler.accessModifiers(loc, "public", "static"), // modifiers
null, // typeParameters
new SimpleType(loc, enumIClass), // type
"valueOf", // name
new FormalParameters( // formalParameters
loc, // location
new FormalParameter[] { fp }, // parameters
false // variableArity
),
new Type[0], // thrownExceptions,
null, // defaultValue
Arrays.asList( // statements
// return (E) Enum.valueOf(E.class, s);
new ReturnStatement(loc, new Cast(
loc, // location
new SimpleType(loc, enumIClass), // targetType
new MethodInvocation( // value
loc, // location
new SimpleType(loc, this.iClassLoader.TYPE_java_lang_Enum), // target
"valueOf", // methodName
new Rvalue[] { // arguments
new ClassLiteral(loc, new SimpleType(loc, enumIClass)),
new ParameterAccess(loc, fp)
}
)
))
)
);
md.setEnclosingScope(ed);
this.compile(md, cf);
}
}
// Compile declared methods.
this.compileDeclaredMethods(cd, cf);
// Compile declared constructors.
// As a side effect of compiling methods and constructors, synthetic "class-dollar" methods (which implement
// class literals) are generated on-the fly. We need to note how many we have here so we can compile the
// extras.
final int declaredMethodCount = cd.getMethodDeclarations().size();
{
ConstructorDeclarator[] ctords = cd.getConstructors();
int syntheticFieldCount = cd.syntheticFields.size();
int methodInfoCount = cf.methodInfos.size();
for (ConstructorDeclarator ctord : ctords) this.compile(ctord, cf);
if (syntheticFieldCount != cd.syntheticFields.size()) {
// New synthetic fields were created while the constructors were compiled -- need to re-compile all
// constructors!
syntheticFieldCount = cd.syntheticFields.size();
while (cf.methodInfos.size() > methodInfoCount) cf.methodInfos.remove(methodInfoCount);
for (ConstructorDeclarator ctord : ctords) {
// Clear the cached IConstructor -- necessary because the synthetic constructor parameters have
// changed!
ctord.iConstructor = null;
this.compile(ctord, cf);
}
// Re-compilation of the constructors cannot possibly create even more synthetic fields!
assert syntheticFieldCount == cd.syntheticFields.size();
}
}
// A side effect of this call may create synthetic functions to access protected parent variables.
this.compileDeclaredMemberTypes(cd, cf);
// Compile the aforementioned extras.
this.compileDeclaredMethods(cd, cf, declaredMethodCount);
// For every method look for bridge methods that need to be supplied. This is used to correctly dispatch into
// covariant return types from existing code.
for (IMethod base : iClass.getIMethods()) {
if (!base.isStatic() && base.getAccess() != Access.PRIVATE) {
IMethod override = iClass.findIMethod(base.getName(), base.getParameterTypes());
// If we overrode the method but with a DIFFERENT return type.
if (
override != null
&& base.getReturnType() != override.getReturnType()
) {
try {
this.generateBridgeMethod(cf, iClass, base, override);
} catch (RuntimeException re) {
throw new InternalCompilerException(cd.getLocation(), (
"Generating bridge method from \""
+ base
+ "\" to \""
+ override
+ "\": "
+ re.getMessage()
), re);
}
}
}
}
// Add class and instance variables as (static and non-static) fields.
for (FieldDeclaration fd : Iterables.filterByClass(
cd.getVariableDeclaratorsAndInitializers(),
FieldDeclaration.class
)) this.addFields(fd, cf);
// Synthetic fields.
for (IField f : cd.getSyntheticFields().values()) {
cf.addFieldInfo(
Mod.PACKAGE, // accessFlags
f.getName(), // fieldName
UnitCompiler.rawTypeOf(f.getType()).getDescriptor(), // fieldTypeFD
null // constantValue
);
}
// Add the generated class file to a thread-local store.
this.addClassFile(cf);
}
/**
* Adds the given {@link ClassFile} to the result set.
*/
private void
addClassFile(ClassFile cf) {
assert this.storesClassFiles != null;
try {
this.storesClassFiles.consume(cf);
} catch (IOException ioe) {
throw new InternalCompilerException(cf.getThisClassName(), ioe);
}
}
/**
* Creates and adds {@link ClassFile.FieldInfo}s to the cf for all fields declared by the fd .
*/
private void
addFields(FieldDeclaration fd, ClassFile cf) throws CompileException {
for (VariableDeclarator vd : fd.variableDeclarators) {
Type type = fd.type;
for (int i = 0; i < vd.brackets; ++i) type = new ArrayType(type);
Object ocv = ( // Optional constant value
fd.isFinal() && vd.initializer instanceof Rvalue
? this.constantAssignmentConversion(
vd.initializer, // locatable
this.getConstantValue((Rvalue) vd.initializer), // value
this.getRawType(type) // targetType
)
: UnitCompiler.NOT_CONSTANT
);
short accessFlags = this.accessFlags(fd.modifiers);
ClassFile.FieldInfo fi;
if (fd.isPrivate()) {
// To make the private field accessible for enclosing types, enclosed types and types enclosed by the
// same type, it is modified as follows:
// + Access is changed from PRIVATE to PACKAGE
accessFlags = UnitCompiler.changeAccessibility(accessFlags, Mod.PACKAGE);
fi = cf.addFieldInfo(
accessFlags, // accessFlags
vd.name, // fieldName
this.getRawType(type).getDescriptor(), // fieldTypeFD
ocv == UnitCompiler.NOT_CONSTANT ? null : ocv // constantValue
);
} else
{
fi = cf.addFieldInfo(
( // accessFlags
fd.getDeclaringType() instanceof InterfaceDeclaration
? (short) (Mod.PUBLIC | Mod.STATIC | Mod.FINAL)
: accessFlags
),
vd.name, // fieldName
this.getRawType(type).getDescriptor(), // fieldTypeFD
ocv == UnitCompiler.NOT_CONSTANT ? null : ocv // constantValue
);
}
// Add field annotations with retention != SOURCE.
this.compileAnnotations(fd.getAnnotations(), fi, cf);
// Add "Deprecated" attribute (JVMS 4.7.10).
if (fd.hasDeprecatedDocTag()) {
fi.addAttribute(new ClassFile.DeprecatedAttribute(cf.addConstantUtf8Info("Deprecated")));
}
}
}
private void
compile2(AnonymousClassDeclaration acd) throws CompileException {
// For classes that enclose surrounding scopes, trawl their field initializers looking for synthetic fields.
// this.fakeCompileVariableDeclaratorsAndInitializers(acd);
this.compile2((InnerClassDeclaration) acd);
}
private void
compile2(LocalClassDeclaration lcd) throws CompileException {
// For classes that enclose surrounding scopes, trawl their field initializers looking for synthetic fields.
this.fakeCompileVariableDeclaratorsAndInitializers(lcd);
this.compile2((InnerClassDeclaration) lcd);
}
private void
compile2(InnerClassDeclaration icd) throws CompileException {
// Define a synthetic "this$n" field if there is an enclosing instance, where "n" designates the number of
// enclosing instances minus one.
{
List ocs = UnitCompiler.getOuterClasses(icd);
final int nesting = ocs.size();
if (nesting >= 2) {
TypeDeclaration immediatelyEnclosingOuterClassDeclaration = (TypeDeclaration) ocs.get(1);
icd.defineSyntheticField(new SimpleIField(
this.resolve(icd),
"this$" + (nesting - 2),
this.resolve(immediatelyEnclosingOuterClassDeclaration)
));
}
}
this.compile2((AbstractClassDeclaration) icd);
}
private void
fakeCompileVariableDeclaratorsAndInitializers(AbstractClassDeclaration cd) throws CompileException {
// Compilation of field declarations can create synthetic variables, so we must not use an iterator.
List fdais = cd.fieldDeclarationsAndInitializers;
for (int i = 0; i < fdais.size(); i++) {
BlockStatement fdoi = (BlockStatement) fdais.get(i);
this.fakeCompile(fdoi);
}
}
private void
compile2(InterfaceDeclaration id) throws CompileException {
final IClass iClass = this.resolve(id);
// Determine extended interfaces.
IClass[] rawInterfaces = UnitCompiler.rawTypesOf((id.interfaces = this.getTypes(id.extendedTypes)));
short accessFlags = this.accessFlags(id.getModifiers());
accessFlags |= Mod.INTERFACE;
accessFlags |= Mod.ABSTRACT;
if (id instanceof AnnotationTypeDeclaration) accessFlags |= Mod.ANNOTATION;
if (id instanceof MemberInterfaceDeclaration) accessFlags |= Mod.STATIC;
ClassFile cf = this.newClassFile(
accessFlags,
iClass,
this.iClassLoader.TYPE_java_lang_Object,
rawInterfaces
);
// Add interface annotations with retention != SOURCE.
this.compileAnnotations(id.getAnnotations(), cf, cf);
// Set "SourceFile" attribute.
if (this.debugSource) {
String sourceFileName;
{
String s = id.getLocation().getFileName();
if (s != null) {
sourceFileName = new File(s).getName();
} else {
sourceFileName = id.getName() + ".java";
}
}
cf.addSourceFileAttribute(sourceFileName);
}
// Add "Deprecated" attribute (JVMS 4.7.10).
if (id.hasDeprecatedDocTag()) cf.addDeprecatedAttribute();
// Interface initialization method.
if (!id.constantDeclarations.isEmpty()) {
List statements = new ArrayList<>();
statements.addAll(id.constantDeclarations);
this.maybeCreateInitMethod(id, cf, statements);
}
this.compileDeclaredMethods(id, cf);
// Class variables.
for (FieldDeclaration constantDeclaration : id.constantDeclarations) this.addFields(constantDeclaration, cf);
this.compileDeclaredMemberTypes(id, cf);
// Add the generated class file to a thread-local store.
this.addClassFile(cf);
}
/**
* @param superclass {@code null} for {@link Object}, {@link Object} for interfaces
*/
private ClassFile
newClassFile(
short accessFlags,
IClass iClass,
@Nullable IClass superclass,
IClass[] interfaces
) throws CompileException {
ClassFile result = new ClassFile(
accessFlags, // accessFlags
iClass.getDescriptor(), // thisClassFD
superclass != null ? superclass.getDescriptor() : null, // superclassFD
IClass.getDescriptors(interfaces) // interfaceFDs
);
int v = this.getTargetVersion();
if (v < 6) throw new CompileException("Cannot generate version " + v + " .class files", Location.NOWHERE);
result.setVersion((short) (44 + v), ClassFile.MINOR_VERSION_JDK_1_6);
return result;
}
/**
* Converts and adds the annotations to the target .
*/
private void
compileAnnotations(Annotation[] annotations, Annotatable target, final ClassFile cf) throws CompileException {
final Set seenAnnotations = new HashSet<>();
ANNOTATIONS: for (final Annotation a : annotations) {
final Type annotationType = a.getType();
final IClass annotationIClass = this.getRawType(annotationType);
final IAnnotation[] annotationAnnotations = annotationIClass.getIAnnotations();
// Check for duplicate annotations.
if (!seenAnnotations.add(annotationIClass)) {
this.compileError("Duplicate annotation \"" + annotationIClass + "\"", annotationType.getLocation());
}
// Determine the retention.
boolean runtimeVisible = false;
for (IAnnotation aa : annotationAnnotations) {
if (aa.getAnnotationType() != this.iClassLoader.TYPE_java_lang_annotation_Retention) continue;
Object rev = aa.getElementValue("value");
String retention = ((IField) rev).getName();
if ("SOURCE".equals(retention)) {
continue ANNOTATIONS;
} else
if ("CLASS".equals(retention)) {
runtimeVisible = false;
break;
} else
if ("RUNTIME".equals(retention)) {
runtimeVisible = true;
break;
} else
{
throw new AssertionError(retention);
}
}
// Compile the annotation's element-value-pairs.
final Map evps = new HashMap<>();
a.accept(new AnnotationVisitor() {
@Override @Nullable public Void
visitSingleElementAnnotation(SingleElementAnnotation sea) throws CompileException {
IMethod[] definitions = annotationIClass.getDeclaredIMethods("value");
assert definitions.length == 1;
boolean isArray = definitions[0].getReturnType().isArray();
evps.put(
cf.addConstantUtf8Info("value"),
UnitCompiler.this.compileElementValue(sea.elementValue, cf, isArray)
);
return null;
}
@Override @Nullable public Void
visitNormalAnnotation(NormalAnnotation na) throws CompileException {
for (ElementValuePair evp : na.elementValuePairs) {
IMethod[] definitions = annotationIClass.getDeclaredIMethods(evp.identifier);
assert definitions.length == 1;
boolean isArray = definitions[0].getReturnType().isArray();
evps.put(
cf.addConstantUtf8Info(evp.identifier),
UnitCompiler.this.compileElementValue(evp.elementValue, cf, isArray)
);
}
return null;
}
@Override @Nullable public Void
visitMarkerAnnotation(MarkerAnnotation ma) {
;
return null;
}
});
// Add the annotation to the target (class/interface, method or field).
target.addAnnotationsAttributeEntry(runtimeVisible, annotationIClass.getDescriptor(), evps);
}
}
private ClassFile.ElementValue
compileElementValue(ElementValue elementValue, final ClassFile cf, boolean compileAsArray) throws CompileException {
ClassFile.ElementValue
result = (ClassFile.ElementValue) elementValue.accept(
new ElementValueVisitor() {
@Override public ClassFile.ElementValue
visitRvalue(Rvalue rv) throws CompileException {
// Enum constant?
ENUM_CONSTANT:
if (rv instanceof AmbiguousName) {
Rvalue enumConstant = UnitCompiler.this.reclassify((AmbiguousName) rv).toRvalue();
if (!(enumConstant instanceof FieldAccess)) break ENUM_CONSTANT; // Not a field access.
FieldAccess enumConstantFieldAccess = (FieldAccess) enumConstant;
Type enumType = enumConstantFieldAccess.lhs.toType();
if (enumType == null) break ENUM_CONSTANT; // LHS is not a type.
IClass enumIClass = UnitCompiler.this.findTypeByName(rv.getLocation(), enumType.toString());
if (enumIClass == null) {
UnitCompiler.this.compileError(
"Cannot find enum \"" + enumType + "\"",
enumType.getLocation()
);
break ENUM_CONSTANT;
}
if (enumIClass.getSuperclass() == UnitCompiler.this.iClassLoader.TYPE_java_lang_Enum) {
return new ClassFile.EnumConstValue(
cf.addConstantUtf8Info(enumIClass.getDescriptor()), // typeNameIndex
cf.addConstantUtf8Info(enumConstantFieldAccess.field.getName()) // constNameIndex
);
}
// We have a constant, but it is not an ENUM constant, so fall through.
}
// Class literal?
if (rv instanceof ClassLiteral) {
return new ClassFile.ClassElementValue(cf.addConstantUtf8Info(
UnitCompiler.this.getRawType(((ClassLiteral) rv).type).getDescriptor()
));
}
// Constant value?
Object cv = UnitCompiler.this.getConstantValue(rv);
if (cv == UnitCompiler.NOT_CONSTANT) {
throw new CompileException(
"\"" + rv + "\" is not a constant expression",
rv.getLocation()
);
}
if (cv == null) {
throw new CompileException(
"Null literal not allowed as element value",
rv.getLocation()
);
}
if (cv instanceof Boolean) { return new ClassFile.BooleanElementValue(cf.addConstantIntegerInfo((Boolean) cv ? 1 : 0)); }
if (cv instanceof Byte) { return new ClassFile.ByteElementValue(cf.addConstantIntegerInfo((Byte) cv)); }
if (cv instanceof Short) { return new ClassFile.ShortElementValue(cf.addConstantIntegerInfo((Short) cv)); }
if (cv instanceof Integer) { return new ClassFile.IntElementValue(cf.addConstantIntegerInfo((Integer) cv)); }
if (cv instanceof Long) { return new ClassFile.LongElementValue(cf.addConstantLongInfo((Long) cv)); }
if (cv instanceof Float) { return new ClassFile.FloatElementValue(cf.addConstantFloatInfo((Float) cv)); }
if (cv instanceof Double) { return new ClassFile.DoubleElementValue(cf.addConstantDoubleInfo((Double) cv)); }
if (cv instanceof Character) { return new ClassFile.CharElementValue(cf.addConstantIntegerInfo((Character) cv)); }
if (cv instanceof String) { return new ClassFile.StringElementValue(cf.addConstantUtf8Info((String) cv)); }
throw new AssertionError(cv);
}
@Override public ClassFile.ElementValue
visitAnnotation(Annotation a) throws CompileException {
final IClass annotationIClass = UnitCompiler.this.getRawType(a.getType());
short annotationTypeIndex = cf.addConstantUtf8Info(annotationIClass.getDescriptor());
final Map evps = new HashMap<>();
a.accept(new AnnotationVisitor() {
@Override @Nullable public Void
visitMarkerAnnotation(MarkerAnnotation ma) {
;
return null;
}
@Override @Nullable public Void
visitSingleElementAnnotation(SingleElementAnnotation sea) throws CompileException {
IMethod[] definitions = annotationIClass.getDeclaredIMethods("value");
assert definitions.length == 1;
boolean expectArray = definitions[0].getReturnType().isArray();
evps.put(
cf.addConstantUtf8Info("value"),
UnitCompiler.this.compileElementValue(sea.elementValue, cf, expectArray)
);
return null;
}
@Override @Nullable public Void
visitNormalAnnotation(NormalAnnotation na) throws CompileException {
for (ElementValuePair evp : na.elementValuePairs) {
IMethod[] definitions = annotationIClass.getDeclaredIMethods(evp.identifier);
assert definitions.length == 1;
boolean expectArray = Descriptor.isArrayReference(definitions[0].getDescriptor().returnFd);
evps.put(
cf.addConstantUtf8Info(evp.identifier),
UnitCompiler.this.compileElementValue(evp.elementValue, cf, expectArray)
);
}
return null;
}
});
return new ClassFile.Annotation(annotationTypeIndex, evps);
}
@Override public ClassFile.ElementValue
visitElementValueArrayInitializer(ElementValueArrayInitializer evai) throws CompileException {
ClassFile.ElementValue[]
evs = new ClassFile.ElementValue[evai.elementValues.length];
for (int i = 0; i < evai.elementValues.length; i++) {
evs[i] = UnitCompiler.this.compileElementValue(evai.elementValues[i], cf, false);
}
return new ClassFile.ArrayElementValue(evs);
}
}
);
if (compileAsArray && result instanceof ClassFile.ConstantElementValue) {
ClassFile.ElementValue[] arrayValues = new ClassFile.ElementValue[1];
arrayValues[0] = result;
result = new ClassFile.ArrayElementValue(arrayValues);
}
assert result != null;
return result;
}
/**
* Creates class/interface initialization method iff there is any initialization code.
*
* @param td The type declaration
* @param cf The class file into which to put the method
* @param statements The statements for the method (possibly empty)
*/
private void
maybeCreateInitMethod(
TypeDeclaration td,
ClassFile cf,
List statements
) throws CompileException {
// Create class/interface initialization method iff there is any initialization code.
if (this.generatesCode2(statements)) {
MethodDeclarator md = new MethodDeclarator(
td.getLocation(), // location
null, // docComment
UnitCompiler.accessModifiers(td.getLocation(), "static", "public"), // modifiers
null, // typeParameters
new PrimitiveType(td.getLocation(), Primitive.VOID), // type
"", // name
new FormalParameters(td.getLocation()), // formalParameters
new ReferenceType[0], // thrownExceptions
null, // defaultValue
statements // statements
);
md.setDeclaringType(td);
this.compile(md, cf);
}
}
/**
* Compiles all of the types for this declaration
*
* NB: as a side effect this will fill in the synthetic field map
*
*/
private void
compileDeclaredMemberTypes(TypeDeclaration decl, ClassFile cf) throws CompileException {
for (MemberTypeDeclaration mtd : decl.getMemberTypeDeclarations()) {
this.compile(mtd);
// Add InnerClasses attribute entry for member type declaration.
short innerClassInfoIndex = cf.addConstantClassInfo(this.resolve(mtd).getDescriptor());
short outerClassInfoIndex = cf.addConstantClassInfo(this.resolve(decl).getDescriptor());
short innerNameIndex = cf.addConstantUtf8Info(mtd.getName());
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
outerClassInfoIndex, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
this.accessFlags(mtd.getModifiers()) // innerClassAccessFlags
));
}
}
/**
* Compiles all of the methods for this declaration
*
* NB: as a side effect this will fill in the synthetic field map
*
startPos.
*
* @param startPos Starting parameter to fill in
*/
private void
compileDeclaredMethods(TypeDeclaration typeDeclaration, ClassFile cf, int startPos) throws CompileException {
// Notice that as a side effect of compiling methods, synthetic "class-dollar" methods (which implement class
// literals) are generated on-the fly. Hence, we must not use an Iterator here.
for (int i = startPos; i < typeDeclaration.getMethodDeclarations().size(); ++i) {
MethodDeclarator md = (MethodDeclarator) typeDeclaration.getMethodDeclarations().get(i);
IMethod m = this.toIMethod(md);
boolean overrides = this.overridesMethodFromSupertype(m, this.resolve(md.getDeclaringType()));
boolean hasOverrideAnnotation = this.hasAnnotation(md, this.iClassLoader.TYPE_java_lang_Override);
if (overrides && !hasOverrideAnnotation && !(typeDeclaration instanceof InterfaceDeclaration)) {
this.warning("MO", "Missing @Override", md.getLocation());
} else
if (!overrides && hasOverrideAnnotation) {
this.compileError("Method does not override a method declared in a supertype", md.getLocation());
}
this.compile(md, cf);
}
}
private boolean
hasAnnotation(FunctionDeclarator fd, IClass annotationType) throws CompileException {
for (Annotation a : Iterables.filterByClass(fd.getModifiers(), Annotation.class)) {
if (this.getType(a.getType()) == annotationType) return true;
}
return false;
}
private boolean
overridesMethodFromSupertype(IMethod m, IClass type) throws CompileException {
// Check whether it overrides a method declared in the superclass (or any of its supertypes).
{
IClass superclass = type.getSuperclass();
if (superclass != null && this.overridesMethod(m, superclass)) return true;
}
// Check whether it overrides a method declared in an interface (or any of its superinterfaces).
IClass[] ifs = type.getInterfaces();
for (IClass i : ifs) {
if (this.overridesMethod(m, i)) return true;
}
// Special handling for interfaces that don't extend other interfaces: JLS7 dictates that these stem from
// 'Object', but 'getSuperclass()' returns NULL for interfaces.
if (ifs.length == 0 && type.isInterface()) {
return this.overridesMethod(m, this.iClassLoader.TYPE_java_lang_Object);
}
return false;
}
/**
* @return Whether method overrides a method of type or any of its supertypes
*/
private boolean
overridesMethod(IMethod method, IClass type) throws CompileException {
// Check whether it overrides a method declared in THIS type.
IMethod[] ms = type.getDeclaredIMethods(method.getName());
for (IMethod m : ms) {
if (Arrays.equals(method.getParameterTypes(), m.getParameterTypes())) return true;
}
// Check whether it overrides a method declared in a supertype.
return this.overridesMethodFromSupertype(method, type);
}
/**
* Generates and compiles a bridge method with signature base that delegates to override .
*/
private void
generateBridgeMethod(ClassFile cf, IClass declaringIClass, IMethod base, IMethod override) throws CompileException {
if (
!base.getReturnType().isAssignableFrom(override.getReturnType())
|| override.getReturnType() == IClass.VOID
) {
this.compileError(
"The return type of \""
+ override
+ "\" is incompatible with that of \""
+ base
+ "\""
);
return;
}
ClassFile.MethodInfo mi = cf.addMethodInfo(
(short) (Mod.PUBLIC | Mod.SYNTHETIC), // accessFlags
base.getName(),
base.getDescriptor()
);
// Add "Exceptions" attribute (JVMS 4.7.4).
IClass[] thrownExceptions = base.getThrownExceptions();
if (thrownExceptions.length > 0) {
final short eani = cf.addConstantUtf8Info("Exceptions");
short[] tecciis = new short[thrownExceptions.length];
for (int i = 0; i < thrownExceptions.length; ++i) {
tecciis[i] = cf.addConstantClassInfo(thrownExceptions[i].getDescriptor());
}
mi.addAttribute(new ClassFile.ExceptionsAttribute(eani, tecciis));
}
final CodeContext codeContext = new CodeContext(mi.getClassFile());
final CodeContext savedCodeContext = this.replaceCodeContext(codeContext);
try {
codeContext.saveLocalVariables();
// Allocate all our local variables.
this.allocateLocalVariableSlotAndMarkAsInitialized(override.getDeclaringIClass(), "this");
IClass[] paramTypes = override.getParameterTypes();
LocalVariableSlot[] locals = new LocalVariableSlot[paramTypes.length];
for (int i = 0; i < paramTypes.length; ++i) {
locals[i] = this.allocateLocalVariableSlotAndMarkAsInitialized(paramTypes[i], "param" + i);
}
this.load(Located.NOWHERE, declaringIClass, 0);
for (LocalVariableSlot l : locals) this.load(Located.NOWHERE, l.getType(), l.getSlotIndex());
this.invokeMethod(Located.NOWHERE, override);
this.xreturn(Located.NOWHERE, base.getReturnType());
} finally {
this.replaceCodeContext(savedCodeContext);
}
// Generate Code attribute and add to the MethodInfo.
mi.addAttribute(codeContext.newCodeAttribute(
1 + override.getParameterTypes().length, // initialLocalsCount
false, // debugLines
false // debugVars
));
}
/**
* @return Whether this statement can complete normally (JLS7 14.1)
*/
private boolean
compile(BlockStatement bs) throws CompileException {
try {
Boolean result = (Boolean) bs.accept(new BlockStatementVisitor() {
@Override public Boolean visitInitializer(Initializer i) throws CompileException { return UnitCompiler.this.compile2(i); }
@Override public Boolean visitFieldDeclaration(FieldDeclaration fd) throws CompileException { return UnitCompiler.this.compile2(fd); }
@Override public Boolean visitLabeledStatement(LabeledStatement ls) throws CompileException { return UnitCompiler.this.compile2(ls); }
@Override public Boolean visitBlock(Block b) throws CompileException { return UnitCompiler.this.compile2(b); }
@Override public Boolean visitExpressionStatement(ExpressionStatement es) throws CompileException { return UnitCompiler.this.compile2(es); }
@Override public Boolean visitIfStatement(IfStatement is) throws CompileException { return UnitCompiler.this.compile2(is); }
@Override public Boolean visitForStatement(ForStatement fs) throws CompileException { return UnitCompiler.this.compile2(fs); }
@Override public Boolean visitForEachStatement(ForEachStatement fes) throws CompileException { return UnitCompiler.this.compile2(fes); }
@Override public Boolean visitWhileStatement(WhileStatement ws) throws CompileException { return UnitCompiler.this.compile2(ws); }
@Override public Boolean visitTryStatement(TryStatement ts) throws CompileException { return UnitCompiler.this.compile2(ts); }
@Override public Boolean visitSwitchStatement(SwitchStatement ss) throws CompileException { return UnitCompiler.this.compile2(ss); }
@Override public Boolean visitSynchronizedStatement(SynchronizedStatement ss) throws CompileException { return UnitCompiler.this.compile2(ss); }
@Override public Boolean visitDoStatement(DoStatement ds) throws CompileException { return UnitCompiler.this.compile2(ds); }
@Override public Boolean visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) throws CompileException { return UnitCompiler.this.compile2(lvds); }
@Override public Boolean visitReturnStatement(ReturnStatement rs) throws CompileException { return UnitCompiler.this.compile2(rs); }
@Override public Boolean visitThrowStatement(ThrowStatement ts) throws CompileException { return UnitCompiler.this.compile2(ts); }
@Override public Boolean visitBreakStatement(BreakStatement bs) throws CompileException { return UnitCompiler.this.compile2(bs); }
@Override public Boolean visitContinueStatement(ContinueStatement cs) throws CompileException { return UnitCompiler.this.compile2(cs); }
@Override public Boolean visitAssertStatement(AssertStatement as) throws CompileException { return UnitCompiler.this.compile2(as); }
@Override public Boolean visitEmptyStatement(EmptyStatement es) { return UnitCompiler.this.compile2(es); }
@Override public Boolean visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) throws CompileException { return UnitCompiler.this.compile2(lcds); }
@Override public Boolean visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) throws CompileException { return UnitCompiler.this.compile2(aci); }
@Override public Boolean visitSuperConstructorInvocation(SuperConstructorInvocation sci) throws CompileException { return UnitCompiler.this.compile2(sci); }
});
assert result != null;
return result;
} catch (RuntimeException re) {
throw new RuntimeException(bs.getLocation().toString(), re);
}
}
/**
* Called to check whether the given {@link BlockStatement} compiles or not.
* Updates the stack map of the current inserter.
*
* @return Whether the block statement can complete normally
*/
private boolean
fakeCompile(BlockStatement bs) throws CompileException {
Offset from = this.getCodeContext().newOffset();
boolean ccn = this.compile(bs);
Offset to = this.getCodeContext().newOffset();
this.getCodeContext().removeCode(from, to);
return ccn;
}
private CodeContext
getCodeContext() {
assert this.codeContext != null;
return this.codeContext;
}
private boolean
compile2(Initializer i) throws CompileException {
this.buildLocalVariableMap(i.block, new HashMap());
return this.compile(i.block);
}
private boolean
compile2(Block b) throws CompileException {
this.getCodeContext().saveLocalVariables();
try {
return this.compileStatements(b.statements);
} finally {
this.getCodeContext().restoreLocalVariables();
}
}
private boolean
compileStatements(List extends BlockStatement> statements) throws CompileException {
boolean previousStatementCanCompleteNormally = true;
for (BlockStatement bs : statements) {
if (!previousStatementCanCompleteNormally && this.generatesCode(bs)) {
this.compileError("Statement is unreachable", bs.getLocation());
break;
}
try {
previousStatementCanCompleteNormally = this.compile(bs);
} catch (RuntimeException re) {
throw new RuntimeException(bs.getLocation().toString(), re);
} catch (AssertionError ae) {
throw new InternalCompilerException(bs.getLocation(), null, ae);
}
}
return previousStatementCanCompleteNormally;
}
private boolean
compile2(DoStatement ds) throws CompileException {
Object cvc = this.getConstantValue(ds.condition);
if (cvc != UnitCompiler.NOT_CONSTANT) {
if (Boolean.TRUE.equals(cvc)) {
this.warning("DSTC", (
"Condition of DO statement is always TRUE; "
+ "the proper way of declaring an unconditional loop is \"for (;;)\""
), ds.getLocation());
return this.compileUnconditionalLoop(ds, ds.body, null);
} else
{
this.warning("DSNR", "DO statement never repeats", ds.getLocation());
}
}
final CodeContext.Offset bodyOffset = this.getCodeContext().newBasicBlock();
// Compile body.
ds.whereToContinue = null;
if (!this.compile(ds.body) && ds.whereToContinue == null) {
this.warning("DSNTC", "\"do\" statement never tests its condition", ds.getLocation());
Offset wtb = ds.whereToBreak;
if (wtb == null) return false;
wtb.set();
ds.whereToBreak = null;
return true;
}
if (ds.whereToContinue != null) {
ds.whereToContinue.set();
ds.whereToContinue = null;
}
// Compile condition.
this.compileBoolean(ds.condition, bodyOffset, UnitCompiler.JUMP_IF_TRUE);
if (ds.whereToBreak != null) {
ds.whereToBreak.set();
ds.whereToBreak = null;
}
return true;
}
private boolean
compile2(ForStatement fs) throws CompileException {
this.getCodeContext().saveLocalVariables();
try {
BlockStatement oi = fs.init;
Rvalue[] ou = fs.update;
Rvalue oc = fs.condition;
// Compile initializer.
if (oi != null) this.compile(oi);
if (oc == null) {
return this.compileUnconditionalLoop(fs, fs.body, ou);
}
Object cvc = this.getConstantValue(oc);
if (cvc != UnitCompiler.NOT_CONSTANT) {
if (Boolean.TRUE.equals(cvc)) {
this.warning("FSTC", (
"Condition of FOR statement is always TRUE; "
+ "the proper way of declaring an unconditional loop is \"for (;;)\""
), fs.getLocation());
return this.compileUnconditionalLoop(fs, fs.body, ou);
} else
{
this.warning("FSNR", "FOR statement never repeats", fs.getLocation());
}
}
CodeContext.Offset toCondition = this.getCodeContext().new BasicBlock();
StackMap smBeforeBody = this.codeContext.currentInserter().getStackMap();
this.gotO(fs, toCondition);
// Compile body.
fs.whereToContinue = null;
this.codeContext.currentInserter().setStackMap(smBeforeBody);
final CodeContext.Offset bodyOffset = this.getCodeContext().newBasicBlock();
boolean bodyCcn = this.compile(fs.body);
if (fs.whereToContinue != null) fs.whereToContinue.set();
// Compile update.
if (ou != null) {
if (!bodyCcn && fs.whereToContinue == null) {
this.warning("FUUR", "For update is unreachable", fs.getLocation());
} else
{
for (Rvalue rv : ou) this.compile(rv);
}
}
fs.whereToContinue = null;
// Compile condition.
toCondition.set();
this.compileBoolean(oc, bodyOffset, UnitCompiler.JUMP_IF_TRUE);
} finally {
this.getCodeContext().restoreLocalVariables();
}
if (fs.whereToBreak != null) {
fs.whereToBreak.set();
fs.whereToBreak = null;
}
return true;
}
private boolean
compile2(ForEachStatement fes) throws CompileException {
IType expressionType = this.getType(fes.expression);
if (UnitCompiler.isArray(expressionType)) {
this.getCodeContext().saveLocalVariables();
try {
StackMap beforeStatement = this.getCodeContext().currentInserter().getStackMap();
// Allocate the local variable for the current element.
LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false);
elementLv.setSlot(this.allocateLocalVariableSlot(elementLv.type, fes.currentElement.name));
// Compile initializer.
this.compileGetValue(fes.expression);
short expressionLv = this.getCodeContext().allocateLocalVariable((short) 1);
this.store(fes.expression, expressionType, expressionLv);
this.consT(fes, 0);
LocalVariable indexLv = this.allocateLocalVariable(false /*finaL*/, IClass.INT);
this.store(fes, indexLv);
StackMap beforeBody = this.getCodeContext().currentInserter().getStackMap();
CodeContext.Offset toCondition = this.getCodeContext().new BasicBlock();
this.gotO(fes, toCondition);
// Get the next array element.
this.codeContext.currentInserter().setStackMap(beforeBody);
fes.whereToContinue = null;
final CodeContext.Offset bodyOffset = this.getCodeContext().newBasicBlock();
this.load(fes, expressionType, expressionLv);
this.load(fes, indexLv);
IType componentType = UnitCompiler.getComponentType(expressionType);
assert componentType != null;
this.xaload(fes.currentElement, componentType);
this.assignmentConversion(fes.currentElement, componentType, elementLv.type, null);
this.store(fes, elementLv);
// Compile the body.
boolean bodyCcn = this.compile(fes.body);
if (fes.whereToContinue != null) fes.whereToContinue.set();
// Compile update.
if (!bodyCcn && fes.whereToContinue == null) {
this.warning("FUUR", "For update is unreachable", fes.getLocation());
} else {
this.iinc(fes, indexLv, "++");
}
fes.whereToContinue = null;
// Compile condition.
toCondition.set();
this.load(fes, indexLv);
this.load(fes, expressionType, expressionLv);
this.arraylength(fes);
this.if_icmpxx(fes, UnitCompiler.LT, bodyOffset);
this.getCodeContext().currentInserter().setStackMap(beforeStatement);
} finally {
this.getCodeContext().restoreLocalVariables();
}
if (fes.whereToBreak != null) {
fes.whereToBreak.set();
fes.whereToBreak = null;
}
} else
if (UnitCompiler.isAssignableFrom(this.iClassLoader.TYPE_java_lang_Iterable, expressionType)) {
this.getCodeContext().saveLocalVariables();
try {
StackMap beforeStatement = this.getCodeContext().currentInserter().getStackMap();
// Compile initializer.
this.compileGetValue(fes.expression);
this.invokeMethod(fes.expression, this.iClassLoader.METH_java_lang_Iterable__iterator);
LocalVariable iteratorLv = this.allocateLocalVariable(false /*finaL*/, this.iClassLoader.TYPE_java_util_Iterator);
this.store(fes, iteratorLv);
CodeContext.Offset toCondition = this.getCodeContext().new BasicBlock();
StackMap smBeforeBody = this.codeContext.currentInserter().getStackMap();
this.gotO(fes, toCondition);
// Compile the body.
this.codeContext.currentInserter().setStackMap(smBeforeBody);
// Allocate the local variable for the current element.
LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false);
elementLv.setSlot(this.allocateLocalVariableSlot(elementLv.type, fes.currentElement.name));
fes.whereToContinue = null;
final CodeContext.Offset bodyOffset = this.getCodeContext().newBasicBlock();
this.load(fes, iteratorLv);
this.invokeMethod(fes.expression, this.iClassLoader.METH_java_util_Iterator__next);
IClass boxedType = this.isBoxingConvertible(elementLv.type);
if (boxedType != null) {
// Fix for Issue #155.
this.checkcast(fes.currentElement, boxedType);
this.unboxingConversion(fes.currentElement, boxedType, (IClass) elementLv.type);
} else
if (this.tryAssignmentConversion(
fes.currentElement,
this.iClassLoader.TYPE_java_lang_Object,
elementLv.type,
null
)) {
;
} else
if (this.tryNarrowingReferenceConversion(
fes.currentElement,
this.iClassLoader.TYPE_java_lang_Object,
elementLv.type
)) {
;
} else
{
throw new InternalCompilerException(fes.getLocation(), "Don't know how to convert to " + elementLv.type);
}
this.store(fes, elementLv);
boolean bodyCcn = this.compile(fes.body);
if (fes.whereToContinue != null) fes.whereToContinue.set();
// Compile update.
if (!bodyCcn && fes.whereToContinue == null) {
this.warning("FUUR", "For update is unreachable", fes.getLocation());
}
fes.whereToContinue = null;
// Compile condition.
toCondition.set();
this.load(fes, iteratorLv);
this.invokeMethod(fes.expression, this.iClassLoader.METH_java_util_Iterator__hasNext);
this.ifxx(fes, UnitCompiler.NE, bodyOffset);
this.getCodeContext().currentInserter().setStackMap(beforeStatement);
} finally {
this.getCodeContext().restoreLocalVariables();
}
if (fes.whereToBreak != null) {
fes.whereToBreak.set();
fes.whereToBreak = null;
}
} else
{
this.compileError("Cannot iterate over \"" + expressionType + "\"", fes.expression.getLocation());
}
return true;
}
private LocalVariable
allocateLocalVariable(boolean finaL, IType localVariableType) {
LocalVariable result = new LocalVariable(finaL, localVariableType);
result.setSlot(this.allocateLocalVariableSlot(localVariableType, null));
return result;
}
private LocalVariable
allocateLocalVariableAndMarkAsInitialized(boolean finaL, IType localVariableType) {
LocalVariable result = new LocalVariable(finaL, localVariableType);
result.setSlot(this.allocateLocalVariableSlotAndMarkAsInitialized(localVariableType, null));
return result;
}
private boolean
compile2(WhileStatement ws) throws CompileException {
Object cvc = this.getConstantValue(ws.condition);
if (cvc != UnitCompiler.NOT_CONSTANT) {
if (Boolean.TRUE.equals(cvc)) {
this.warning("WSTC", (
"Condition of WHILE statement is always TRUE; "
+ "the proper way of declaring an unconditional loop is \"for (;;)\""
), ws.getLocation());
return this.compileUnconditionalLoop(ws, ws.body, null);
} else
{
this.warning("WSNR", "WHILE statement never repeats", ws.getLocation());
}
}
// GOTO condition.
Offset wtc = this.getCodeContext().new BasicBlock();
this.gotO(ws, wtc);
final CodeContext.Offset bodyOffset = this.getCodeContext().newBasicBlock();
Inserter bodyInserter = this.codeContext.newInserter();
wtc.set();
ws.whereToContinue = null;
// Compile the condition first.
// This is necessary because the condition may modify the stack map, e.g.
// String line;
// while ((line = bufferedReader.readLine()) != null) {
// System.out.println(line);
// }
this.compileBoolean(ws.condition, bodyOffset, UnitCompiler.JUMP_IF_TRUE);
// Now compile the body.
try {
StackMap smBeforeBody = this.codeContext.currentInserter().getStackMap();
this.codeContext.pushInserter(bodyInserter);
this.codeContext.currentInserter().setStackMap(smBeforeBody);
this.compile(ws.body); // Return value (CCN) is ignored.
if (ws.whereToContinue != null) {
ws.whereToContinue.set();
ws.whereToContinue = null;
}
} finally {
this.codeContext.popInserter();
}
if (ws.whereToBreak != null) {
ws.whereToBreak.set();
ws.whereToBreak = null;
}
return true;
}
private boolean
compileUnconditionalLoop(ContinuableStatement cs, BlockStatement body, @Nullable Rvalue[] update)
throws CompileException {
if (update != null) return this.compileUnconditionalLoopWithUpdate(cs, body, update);
// Compile body.
Offset wtc = (cs.whereToContinue = this.getCodeContext().newBasicBlock());
if (this.compile(body)) this.gotO(cs, wtc);
cs.whereToContinue = null;
Offset wtb = cs.whereToBreak;
if (wtb == null) return false;
wtb.set();
cs.whereToBreak = null;
return true;
}
private boolean
compileUnconditionalLoopWithUpdate(ContinuableStatement cs, BlockStatement body, Rvalue[] update)
throws CompileException {
// Compile body.
cs.whereToContinue = null;
final CodeContext.Offset bodyOffset = this.getCodeContext().newBasicBlock();
boolean bodyCcn = this.compile(body);
// Compile the "update".
if (cs.whereToContinue != null) cs.whereToContinue.set();
if (!bodyCcn && cs.whereToContinue == null) {
this.warning("LUUR", "Loop update is unreachable", update[0].getLocation());
} else
{
for (Rvalue rv : update) this.compile(rv);
this.gotO(cs, bodyOffset);
this.getCodeContext().currentInserter().setStackMap(null);
}
cs.whereToContinue = null;
Offset wtb = cs.whereToBreak;
if (wtb == null) return false;
wtb.set();
cs.whereToBreak = null;
return true;
}
private boolean
compile2(LabeledStatement ls) throws CompileException {
boolean canCompleteNormally = this.compile(ls.body);
Offset wtb = ls.whereToBreak;
if (wtb == null) return canCompleteNormally;
if (!canCompleteNormally) this.getCodeContext().currentInserter().setStackMap(wtb.getStackMap());
wtb.set();
ls.whereToBreak = null;
return true;
}
private enum SwitchKind { INT, ENUM, STRING }
private boolean
compile2(SwitchStatement ss) throws CompileException {
SwitchKind kind;
short ssvLvIndex = -1; // Only relevant if kind == STRING.
StackMap smBeforeSwitch = this.codeContext.currentInserter().getStackMap();
// Determine SWITCH kind.
IType switchExpressionType = this.getType(ss.condition);
if (this.iClassLoader.TYPE_java_lang_String == switchExpressionType) {
kind = SwitchKind.STRING;
} else
if (UnitCompiler.isAssignableFrom(this.iClassLoader.TYPE_java_lang_Enum, switchExpressionType)) {
kind = SwitchKind.ENUM;
} else
{
kind = SwitchKind.INT;
}
// Prepare the map of case labels to code offsets.
TreeMap caseLabelMap = new TreeMap<>();
CodeContext.Offset defaultLabelOffset = null;
CodeContext.Offset[] sbsgOffsets = new CodeContext.Offset[ss.sbsgs.size()];
for (int i = 0; i < ss.sbsgs.size(); ++i) {
SwitchBlockStatementGroup sbsg = (SwitchBlockStatementGroup) ss.sbsgs.get(i);
sbsgOffsets[i] = this.getCodeContext().new BasicBlock();
for (Rvalue caseLabel : sbsg.caseLabels) {
Integer civ;
switch (kind) {
case ENUM:
CIV: {
if (!(caseLabel instanceof AmbiguousName)) {
this.compileError("Case label must be an enum constant", caseLabel.getLocation());
civ = 99;
break;
}
String[] identifiers = ((AmbiguousName) caseLabel).identifiers;
if (identifiers.length != 1) {
this.compileError("Case label must be a plain enum constant", caseLabel.getLocation());
civ = 99;
break;
}
String constantName = identifiers[0];
int ordinal = 0;
for (IField f : UnitCompiler.rawTypeOf(switchExpressionType).getDeclaredIFields()) {
if (f.getAccess() != Access.PUBLIC || !f.isStatic()) continue;
if (f.getName().equals(constantName)) {
civ = ordinal;
break CIV;
}
ordinal++;
}
this.compileError("Unknown enum constant \"" + constantName + "\"", caseLabel.getLocation());
civ = 99;
}
// Store in case label map.
if (caseLabelMap.containsKey(civ)) {
this.compileError("Duplicate \"case\" switch label value", caseLabel.getLocation());
}
caseLabelMap.put(civ, sbsgOffsets[i]);
break;
case INT:
{
// Verify that case label value is a constant.
Object cv = this.getConstantValue(caseLabel);
if (cv == UnitCompiler.NOT_CONSTANT) {
this.compileError(
"Value of 'case' label does not pose a constant value",
caseLabel.getLocation()
);
civ = 99;
break;
}
// Convert char, byte, short, int to "Integer".
if (cv instanceof Integer) {
civ = (Integer) cv;
} else
if (cv instanceof Number) {
civ = Integer.valueOf(((Number) cv).intValue());
} else
if (cv instanceof Character) {
civ = Integer.valueOf(((Character) cv).charValue());
} else {
this.compileError(
"Value of case label must be a char, byte, short or int constant",
caseLabel.getLocation()
);
civ = Integer.valueOf(99);
}
}
// Store in case label map.
if (caseLabelMap.containsKey(civ)) {
this.compileError("Duplicate \"case\" switch label value", caseLabel.getLocation());
}
caseLabelMap.put(civ, sbsgOffsets[i]);
break;
case STRING:
{
// Verify that the case label value is a string constant.
Object cv = this.getConstantValue(caseLabel);
if (!(cv instanceof String)) {
this.compileError(
"Value of 'case' label is not a string constant",
caseLabel.getLocation()
);
civ = 99;
break;
}
// Use the string constant's hash code as the SWITCH key.
civ = cv.hashCode();
}
// Store in case label map.
if (!caseLabelMap.containsKey(civ)) {
caseLabelMap.put(civ, this.getCodeContext().new BasicBlock());
}
break;
default:
throw new AssertionError(kind);
}
}
if (sbsg.hasDefaultLabel) {
if (defaultLabelOffset != null) {
this.compileError("Duplicate \"default\" switch label", sbsg.getLocation());
}
defaultLabelOffset = sbsgOffsets[i];
}
}
if (defaultLabelOffset == null) defaultLabelOffset = this.getWhereToBreak(ss);
// Compute the condition.
this.compileGetValue(ss.condition);
// Postprocess the condition.
switch (kind) {
case STRING:
// Store the string value in a (hidden) local variable, because after we do the SWITCH
// on the string's hash code, we need to check for string equality with the CASE
// labels.
this.dup(ss);
ssvLvIndex = this.getCodeContext().allocateLocalVariable((short) 1);
this.store(
ss, // locatable
this.iClassLoader.TYPE_java_lang_String, // lvType
ssvLvIndex // lvIndex
);
this.invokeMethod(ss, this.iClassLoader.METH_java_lang_String__hashCode);
break;
case ENUM:
this.invokeMethod(ss, this.iClassLoader.METH_java_lang_Enum__ordinal);
break;
case INT:
this.assignmentConversion(
ss, // locatable
switchExpressionType, // sourceType
IClass.INT, // targetType
null // constantValue
);
break;
default:
throw new AssertionError(kind);
}
// Generate TABLESWITCH or LOOKUPSWITCH instruction.
if (caseLabelMap.isEmpty()) {
// Special case: SWITCH statement without CASE labels (but maybe a DEFAULT label).
this.pop(ss, IClass.INT);
} else
if (
(Integer) caseLabelMap.firstKey() + caseLabelMap.size() // Beware of INT overflow!
>= (Integer) caseLabelMap.lastKey() - caseLabelMap.size()
) {
// The case label values are strictly consecutive or almost consecutive (at most 50%
// 'gaps'), so let's use a TABLESWITCH.
this.tableswitch(ss, caseLabelMap, defaultLabelOffset);
} else
{
// The case label values are not 'consecutive enough', so use a LOOKUPSWITCH.
this.lookupswitch(ss, caseLabelMap, defaultLabelOffset);
}
if (kind == SwitchKind.STRING) {
// For STRING SWITCH, we must generate extra code that checks for string equality --
// the strings' hash codes are not globally unique (as, e.g. MD5).
StackMap smBeforeSbsg = this.codeContext.currentInserter().getStackMap();
for (Entry e : caseLabelMap.entrySet()) {
final Integer caseHashCode = (Integer) e.getKey();
final CodeContext.Offset offset = (CodeContext.Offset) e.getValue();
offset.set();
Set caseLabelValues = new HashSet<>();
for (int i = 0; i < ss.sbsgs.size(); i++) {
SwitchBlockStatementGroup sbsg = (SwitchBlockStatementGroup) ss.sbsgs.get(i);
this.codeContext.currentInserter().setStackMap(smBeforeSbsg);
for (Rvalue caseLabel : sbsg.caseLabels) {
String cv = (String) this.getConstantValue(caseLabel);
assert cv != null;
if (!caseLabelValues.add(cv)) {
this.compileError(
"Duplicate case label \"" + cv + "\"",
caseLabel.getLocation()
);
}
if (cv.hashCode() != caseHashCode) continue;
this.load(sbsg, this.iClassLoader.TYPE_java_lang_String, ssvLvIndex);
this.consT(caseLabel, cv);
this.invokeMethod(caseLabel, this.iClassLoader.METH_java_lang_String__equals__java_lang_Object);
this.ifxx(sbsg, UnitCompiler.NE, sbsgOffsets[i]);
}
}
this.gotO(ss, defaultLabelOffset);
}
}
// Compile statement groups.
boolean canCompleteNormally = true;
for (int i = 0; i < ss.sbsgs.size(); ++i) {
SwitchBlockStatementGroup sbsg = (SwitchBlockStatementGroup) ss.sbsgs.get(i);
sbsgOffsets[i].set();
this.codeContext.currentInserter().setStackMap(smBeforeSwitch);
canCompleteNormally = true;
for (BlockStatement bs : sbsg.blockStatements) {
if (!canCompleteNormally) {
this.compileError("Statement is unreachable", bs.getLocation());
break;
}
canCompleteNormally = this.compile(bs);
}
}
Offset wtb = ss.whereToBreak;
if (wtb == null) return canCompleteNormally;
if (!canCompleteNormally) this.codeContext.currentInserter().setStackMap(wtb.getStackMap());
wtb.set();
ss.whereToBreak = null;
return true;
}
private boolean
compile2(BreakStatement bs) throws CompileException {
// Find the broken statement.
BreakableStatement brokenStatement = null;
if (bs.label == null) {
for (
Scope s = bs.getEnclosingScope();
s instanceof Statement || s instanceof CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof BreakableStatement) {
brokenStatement = (BreakableStatement) s;
break;
}
}
if (brokenStatement == null) {
this.compileError("\"break\" statement is not enclosed by a breakable statement", bs.getLocation());
return false;
}
} else {
for (
Scope s = bs.getEnclosingScope();
s instanceof Statement || s instanceof CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof LabeledStatement) {
LabeledStatement ls = (LabeledStatement) s;
if (ls.label.equals(bs.label)) {
brokenStatement = ls;
break;
}
}
}
if (brokenStatement == null) {
this.compileError((
"Statement \"break "
+ bs.label
+ "\" is not enclosed by a breakable statement with label \""
+ bs.label
+ "\""
), bs.getLocation());
return false;
}
}
this.leaveStatements(
bs.getEnclosingScope(), // from
brokenStatement.getEnclosingScope() // to
);
this.gotO(bs, this.getWhereToBreak(brokenStatement));
return false;
}
private boolean
compile2(ContinueStatement cs) throws CompileException {
// Find the continued statement.
ContinuableStatement continuedStatement = null;
if (cs.label == null) {
for (
Scope s = cs.getEnclosingScope();
s instanceof Statement || s instanceof CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof ContinuableStatement) {
continuedStatement = (ContinuableStatement) s;
break;
}
}
if (continuedStatement == null) {
this.compileError(
"\"continue\" statement is not enclosed by a continuable statement",
cs.getLocation()
);
return false;
}
} else {
for (
Scope s = cs.getEnclosingScope();
s instanceof Statement || s instanceof CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof LabeledStatement) {
LabeledStatement ls = (LabeledStatement) s;
if (ls.label.equals(cs.label)) {
Statement st = ls.body;
while (st instanceof LabeledStatement) st = ((LabeledStatement) st).body;
if (!(st instanceof ContinuableStatement)) {
this.compileError("Labeled statement is not continuable", st.getLocation());
return false;
}
continuedStatement = (ContinuableStatement) st;
break;
}
}
}
if (continuedStatement == null) {
this.compileError((
"Statement \"continue "
+ cs.label
+ "\" is not enclosed by a continuable statement with label \""
+ cs.label
+ "\""
), cs.getLocation());
return false;
}
}
Offset wtc = continuedStatement.whereToContinue;
if (wtc == null) {
wtc = (continuedStatement.whereToContinue = this.getCodeContext().new BasicBlock());
}
this.leaveStatements(
cs.getEnclosingScope(), // from
continuedStatement.getEnclosingScope() // to
);
this.gotO(cs, wtc);
return false;
}
private boolean
compile2(AssertStatement as) throws CompileException {
// assert expression1;
// if (!expression1) throw new AssertionError();
// assert expression1 : expression2;
// if (!expression1) throw new AssertionError(expression2);
CodeContext.Offset end = this.getCodeContext().new BasicBlock();
try {
this.compileBoolean(as.expression1, end, UnitCompiler.JUMP_IF_TRUE);
this.neW(as, this.iClassLoader.TYPE_java_lang_AssertionError);
this.dup(as);
Rvalue[] arguments = (
as.expression2 == null
? new Rvalue[0]
: new Rvalue[] { as.expression2 }
);
this.invokeConstructor(
as, // locatable
as, // scope
null, // enclosingInstance
this.iClassLoader.TYPE_java_lang_AssertionError, // targetClass
arguments // arguments
);
this.getCodeContext().popUninitializedVariableOperand();
this.getCodeContext().pushObjectOperand(Descriptor.JAVA_LANG_ASSERTIONERROR);
this.athrow(as);
} finally {
end.setBasicBlock();
}
return true;
}
@SuppressWarnings("static-method") private boolean
compile2(EmptyStatement es) { return true; }
private boolean
compile2(ExpressionStatement ee) throws CompileException {
try {
this.compile(ee.rvalue);
} catch (InternalCompilerException ice) {
throw new InternalCompilerException(ee.rvalue.getLocation(), null, ice);
}
return true;
}
private boolean
compile2(FieldDeclaration fd) throws CompileException {
final IClass declaringIClass = this.resolve(fd.getDeclaringType());
for (VariableDeclarator vd : fd.variableDeclarators) {
ArrayInitializerOrRvalue initializer = this.getNonConstantFinalInitializer(fd, vd);
if (initializer == null) continue;
// TODO: Compile annotations on fields.
// assert fd.modifiers.annotations.length == 0 : fd.getLocation();
try {
this.addLineNumberOffset(vd);
if (!declaringIClass.isInterface() && !fd.isStatic()) this.load(vd, declaringIClass, 0);
IClass fieldType = this.getRawType(fd.type);
this.compile(initializer, UnitCompiler.this.iClassLoader.getArrayIClass(fieldType, vd.brackets));
// No need to check accessibility here.
;
// TODO: Compile annotations on fields.
// assert fd.modifiers.annotations.length == 0;
IField iField = declaringIClass.getDeclaredIField(vd.name);
assert iField != null : fd.getDeclaringType() + " has no field " + vd.name;
this.putfield(fd, iField);
} catch (InternalCompilerException ice) {
throw new InternalCompilerException(initializer.getLocation(), null, ice);
}
}
return true;
}
private boolean
compile2(IfStatement is) throws CompileException {
final BlockStatement ts = is.thenStatement;
final BlockStatement es = is.elseStatement != null ? is.elseStatement : new EmptyStatement(ts.getLocation());
Object cv = this.getConstantValue(is.condition);
if (cv instanceof Boolean) {
// Constant condition.
this.fakeCompile(is.condition);
BlockStatement seeingStatement, blindStatement;
if (((Boolean) cv).booleanValue()) {
seeingStatement = ts;
blindStatement = es;
} else {
seeingStatement = es;
blindStatement = ts;
}
// Compile the seeing statement.
final CodeContext.Inserter ins = this.getCodeContext().newInserter();
StackMap smBeforeSeeingStatement = this.codeContext.currentInserter().getStackMap();
boolean ssccn = this.compile(seeingStatement);
Offset afterSeeingStatement = this.codeContext.newOffset();
// Fake-compile the blind statement.
this.codeContext.currentInserter().setStackMap(smBeforeSeeingStatement);
boolean bsccn = this.fakeCompile(blindStatement);
afterSeeingStatement.setStackMap(); // Merge stack maps after seeing and after blind statements.
if (ssccn) return true;
if (!bsccn) return false;
// Hm... the "seeing statement" cannot complete normally, but the "blind statement" can. Things are getting
// complicated here! The robust solution is to compile the constant-condition-IF statement as a
// non-constant-condition-IF statement. As an optimization, iff the IF-statement is enclosed ONLY by blocks,
// then the remaining bytecode can be written to a "fake" code context, i.e. be thrown away.
// Compile constant-condition-IF statement as non-constant-condition-IF statement.
CodeContext.Offset off = this.getCodeContext().newBasicBlock();
this.getCodeContext().pushInserter(ins);
try {
this.consT(is, Boolean.FALSE);
this.ifxx(is, UnitCompiler.NE, off);
} finally {
this.getCodeContext().popInserter();
}
// Now return "ccn=true" so that the following statements are not reported as "unreachable".
return true;
}
// Non-constant condition.
if (this.generatesCode(ts)) {
if (this.generatesCode(es)) {
// if () else
CodeContext.Offset eso = this.getCodeContext().new BasicBlock();
CodeContext.Offset end = this.getCodeContext().new BasicBlock();
this.compileBoolean(is.condition, eso, UnitCompiler.JUMP_IF_FALSE);
// Compile "then" statement.
boolean tsccn = this.compile(ts);
if (tsccn) {
this.gotO(is, end);
}
// Compile "else" statment.
eso.setBasicBlock();
boolean esccn = this.compile(es);
if (!tsccn && !esccn) return false;
end.setBasicBlock();
return tsccn || esccn;
} else {
// if () else ;
CodeContext.Offset end = this.getCodeContext().new BasicBlock();
this.compileBoolean(is.condition, end, UnitCompiler.JUMP_IF_FALSE);
this.compile(ts);
end.setBasicBlock();
return true;
}
} else {
if (this.generatesCode(es)) {
// if () ; else
CodeContext.Offset end = this.getCodeContext().new BasicBlock();
this.compileBoolean(is.condition, end, UnitCompiler.JUMP_IF_TRUE);
this.compile(es);
end.setBasicBlock();
return true;
} else {
// if () ; else ;
IType conditionType = this.compileGetValue(is.condition);
if (conditionType != IClass.BOOLEAN) this.compileError("Not a boolean expression", is.getLocation());
this.pop(is, conditionType);
return true;
}
}
}
private boolean
compile2(LocalClassDeclarationStatement lcds) throws CompileException {
// Check for redefinition.
LocalClassDeclaration otherLcd = UnitCompiler.findLocalClassDeclaration(lcds, lcds.lcd.name);
if (otherLcd != null && otherLcd != lcds.lcd) {
this.compileError(
"Redeclaration of local class \""
+ lcds.lcd.name
+ "\"; previously declared in "
+ otherLcd.getLocation()
);
}
this.compile(lcds.lcd);
return true;
}
/**
* Finds a local class declared in any block enclosing the given block statement.
*/
@Nullable private static LocalClassDeclaration
findLocalClassDeclaration(Scope s, String name) {
if (s instanceof CompilationUnit) return null;
for (;;) {
Scope es = s.getEnclosingScope();
if (es instanceof CompilationUnit) break;
if (
s instanceof BlockStatement
&& (es instanceof Block || es instanceof FunctionDeclarator)
) {
BlockStatement bs = (BlockStatement) s;
List extends BlockStatement> statements = (
es instanceof BlockStatement
? ((Block) es).statements
: ((FunctionDeclarator) es).statements
);
if (statements != null) {
for (BlockStatement bs2 : statements) {
if (bs2 instanceof LocalClassDeclarationStatement) {
LocalClassDeclarationStatement lcds = ((LocalClassDeclarationStatement) bs2);
if (lcds.lcd.name.equals(name)) return lcds.lcd;
}
if (bs2 == bs) break;
}
}
}
s = es;
}
return null;
}
private boolean
compile2(LocalVariableDeclarationStatement lvds) throws CompileException {
// Ignore annotations here.
for (VariableDeclarator vd : lvds.variableDeclarators) {
try {
LocalVariable lv = this.getLocalVariable(lvds, vd);
lv.setSlot(this.allocateLocalVariableSlot(lv.type, vd.name));
ArrayInitializerOrRvalue initializer = vd.initializer;
if (initializer != null) {
this.compile(initializer, lv.type);
this.store(lvds, lv);
}
} catch (RuntimeException re) {
throw new RuntimeException(vd.getLocation().toString(), re);
}
}
return true;
}
private void
compile(final ArrayInitializerOrRvalue aiorv, final IType arrayType) throws CompileException {
aiorv.accept(new ArrayInitializerOrRvalueVisitor() {
@Override @Nullable public Void
visitArrayInitializer(ArrayInitializer ai) throws CompileException {
UnitCompiler.this.compileGetValue(ai, arrayType);
return null;
}
@Override @Nullable public Void
visitRvalue(Rvalue rhs) throws CompileException {
UnitCompiler.this.assignmentConversion(
aiorv, // locatable
UnitCompiler.this.compileGetValue(rhs), // sourceType
arrayType, // targetType
UnitCompiler.this.getConstantValue(rhs) // constantValue
);
return null;
}
});
}
private VerificationTypeInfo
verificationTypeInfo(@Nullable IType type) {
if (type == null) return ClassFile.StackMapTableAttribute.NULL_VARIABLE_INFO; // TODO Is that the right thing to do?
String fd = UnitCompiler.rawTypeOf(type).getDescriptor();
if (
Descriptor.BOOLEAN.equals(fd)
|| Descriptor.BYTE.equals(fd)
|| Descriptor.CHAR.equals(fd)
|| Descriptor.INT.equals(fd)
|| Descriptor.SHORT.equals(fd)
) return ClassFile.StackMapTableAttribute.INTEGER_VARIABLE_INFO;
if (Descriptor.LONG.equals(fd)) return ClassFile.StackMapTableAttribute.LONG_VARIABLE_INFO;
if (Descriptor.FLOAT.equals(fd)) return ClassFile.StackMapTableAttribute.FLOAT_VARIABLE_INFO;
if (Descriptor.DOUBLE.equals(fd)) return ClassFile.StackMapTableAttribute.DOUBLE_VARIABLE_INFO;
if (
Descriptor.isClassOrInterfaceReference(fd)
|| Descriptor.isArrayReference(fd)
) return new ObjectVariableInfo(this.getCodeContext().getClassFile().addConstantClassInfo(fd), fd);
throw new InternalCompilerException("Cannot make VerificationTypeInfo from \"" + fd + "\"");
}
/**
* @return The {@link LocalVariable} corresponding with the local variable declaration/declarator
*/
public LocalVariable
getLocalVariable(LocalVariableDeclarationStatement lvds, VariableDeclarator vd) throws CompileException {
if (vd.localVariable != null) return vd.localVariable;
// Determine variable type.
Type variableType = lvds.type;
for (int k = 0; k < vd.brackets; ++k) variableType = new ArrayType(variableType);
// Ignore "lvds.modifiers.annotations".
return (vd.localVariable = new LocalVariable(
lvds.isFinal(), // finaL
this.getType(variableType) // type
));
}
private boolean
compile2(ReturnStatement rs) throws CompileException {
// Determine enclosing block, function and compilation Unit.
FunctionDeclarator enclosingFunction = null;
{
Scope s = rs.getEnclosingScope();
while (s instanceof Statement || s instanceof CatchClause) s = s.getEnclosingScope();
enclosingFunction = (FunctionDeclarator) s;
}
Rvalue orv = rs.returnValue;
IType returnType = this.getReturnType(enclosingFunction);
if (returnType == IClass.VOID) {
if (orv != null) this.compileError("Method must not return a value", rs.getLocation());
this.leaveStatements(
rs.getEnclosingScope(), // from
enclosingFunction // to
);
this.returN(rs);
return false;
}
if (orv == null) {
this.compileError("Method must return a value", rs.getLocation());
return false;
}
IType type = this.compileGetValue(orv);
this.assignmentConversion(
rs, // locatable
type, // sourceType
returnType, // targetType
this.getConstantValue(orv) // constantValue
);
this.leaveStatements(
rs.getEnclosingScope(), // from
enclosingFunction // to
);
this.xreturn(rs, returnType);
return false;
}
private boolean
compile2(SynchronizedStatement ss) throws CompileException {
// Evaluate monitor object expression.
if (!UnitCompiler.isAssignableFrom(this.iClassLoader.TYPE_java_lang_Object, this.compileGetValue(ss.expression))) {
this.compileError(
"Monitor object of \"synchronized\" statement is not a subclass of \"Object\"",
ss.getLocation()
);
}
this.getCodeContext().saveLocalVariables();
boolean canCompleteNormally = false;
try {
// Allocate a local variable for the monitor object.
ss.monitorLvIndex = this.getCodeContext().allocateLocalVariable((short) 1);
// Store the monitor object.
this.dup(ss);
this.store(ss, this.iClassLoader.TYPE_java_lang_Object, ss.monitorLvIndex);
// Create lock on the monitor object.
this.monitorenter(ss);
// Compile the statement body.
final CodeContext.Offset monitorExitOffset = this.getCodeContext().new BasicBlock();
final CodeContext.Offset beginningOfBody = this.getCodeContext().newOffset();
StackMap smBeforeBody = this.codeContext.currentInserter().getStackMap();
canCompleteNormally = this.compile(ss.body);
if (canCompleteNormally) {
this.gotO(ss, monitorExitOffset);
}
StackMap save = this.codeContext.currentInserter().getStackMap();
try {
this.codeContext.currentInserter().setStackMap(smBeforeBody);
this.getCodeContext().pushObjectOperand(Descriptor.JAVA_LANG_THROWABLE);
// Generate the exception handler.
CodeContext.Offset here = this.getCodeContext().newBasicBlock();
this.getCodeContext().addExceptionTableEntry(
beginningOfBody, // startPC
here, // endPC
here, // handlerPC
null // catchTypeFD
);
this.leave(ss);
this.athrow(ss);
} finally {
this.codeContext.currentInserter().setStackMap(save);
}
// Unlock monitor object.
if (canCompleteNormally) {
monitorExitOffset.set();
this.leave(ss);
}
} finally {
this.getCodeContext().restoreLocalVariables();
}
return canCompleteNormally;
}
private boolean
compile2(ThrowStatement ts) throws CompileException {
IType expressionType = this.compileGetValue(ts.expression);
this.checkThrownException(
ts, // locatable
expressionType, // type
ts.getEnclosingScope() // scope
);
this.athrow(ts);
return false;
}
/**
* Interface for delayed code generation.
*/
interface Compilable2 { boolean compile() throws CompileException; }
private boolean
compile2(final TryStatement ts) throws CompileException {
return this.compileTryCatchFinallyWithResources(
ts, // tryStatement
ts.resources, // resources
new Compilable2() { // compileBody
@Override public boolean
compile() throws CompileException { return UnitCompiler.this.compile(ts.body); }
},
ts.finallY // finallY
);
}
/**
* Generates code for a TRY statement with (possibly zero) resources and an (optional) FINALLY clause.
*
* @return Whether the code can complete normally
*/
private boolean
compileTryCatchFinallyWithResources(
final TryStatement ts,
List resources,
final Compilable2 compileBody,
@Nullable final Block finallY
) throws CompileException {
// Short-circuit for zero resources.
if (resources.isEmpty()) {
return this.compileTryCatchFinally(ts, compileBody, finallY);
}
// Prepare recursion for all declared resources.
TryStatement.Resource firstResource = (TryStatement.Resource) resources.get(0);
final List followingResources = resources.subList(1, resources.size());
final Location loc = firstResource.getLocation();
final IClass tt = this.iClassLoader.TYPE_java_lang_Throwable;
this.getCodeContext().saveLocalVariables();
try {
LocalVariable
identifier = (LocalVariable) firstResource.accept(
new TryStatementResourceVisitor() {
@Override @Nullable public LocalVariable
visitLocalVariableDeclaratorResource(LocalVariableDeclaratorResource lvdr) throws CompileException {
// final {VariableModifierNoFinal} R Identifier = Expression
IType lvType = UnitCompiler.this.getType(lvdr.type);
LocalVariable result = UnitCompiler.this.allocateLocalVariable(true /*finaL*/, lvType);
ArrayInitializerOrRvalue initializer = lvdr.variableDeclarator.initializer;
assert initializer != null;
UnitCompiler.this.compile(initializer, lvType);
UnitCompiler.this.store(ts, result);
return result;
}
@Override @Nullable public LocalVariable
visitVariableAccessResource(VariableAccessResource var) throws CompileException {
// Expression
if (
!UnitCompiler.this.options.contains(
JaninoOption.EXPRESSIONS_IN_TRY_WITH_RESOURCES_ALLOWED
)
&& !(var.variableAccess instanceof AmbiguousName)
&& !(var.variableAccess instanceof FieldAccessExpression)
&& !(var.variableAccess instanceof SuperclassFieldAccessExpression)
) {
throw new CompileException(
var.variableAccess.getClass().getSimpleName() + " rvalue not allowed as a resource",
var.getLocation()
);
}
LocalVariable result = UnitCompiler.this.allocateLocalVariable(
true, // finaL
UnitCompiler.this.compileGetValue(var.variableAccess) // localVariableType
);
UnitCompiler.this.store(ts, result);
return result;
}
}
);
assert identifier != null;
// Throwable #primaryExc = null;
LocalVariable primaryExc = this.allocateLocalVariable(true /*finaL*/, tt);
this.consT(ts, (Object) null);
this.store(ts, primaryExc);
CatchParameter suppressedException = new CatchParameter(
loc, // location
false, // finaL
new Type[] { new SimpleType(loc, tt) }, // types
"___" // name
);
// Generate the FINALLY clause for the TRY-with-resources statement; see JLS9 14.20.3.1.
// if (Identifier != null) {
// if (#primaryExc != null) {
// try {
// Identifier.close();
// } catch (Throwable #suppressedExc) {
// // Only iff Java >= 7:
// #primaryExc.addSuppressed(#suppressedExc);
// }
// } else {
// Identifier.close();
// }
// }
BlockStatement afterClose = (
this.iClassLoader.METH_java_lang_Throwable__addSuppressed == null
? new EmptyStatement(loc)
: new ExpressionStatement(
new MethodInvocation(
loc, // location
new LocalVariableAccess(loc, primaryExc), // target
"addSuppressed", // methodName
new Rvalue[] { // arguments
new LocalVariableAccess(loc, this.getLocalVariable(suppressedException)),
}
)
)
);
BlockStatement f = new IfStatement(
loc, // location
new BinaryOperation( // condition
loc, // location
new LocalVariableAccess(loc, identifier), // lhs
"!=", // operator
new NullLiteral(loc) // rhs
),
new IfStatement( // thenStatement
loc, // location
new BinaryOperation( // condition
loc, // location
new LocalVariableAccess(loc, primaryExc), // lhs
"!=", // operator
new NullLiteral(loc) // rhs
),
new TryStatement( // thenStatement
loc, // location
new ExpressionStatement( // body
new MethodInvocation(loc, new LocalVariableAccess(loc, identifier), "close", new Rvalue[0])
),
Collections.singletonList(new CatchClause( // catchClauses
loc, // location
suppressedException, // caughtException
afterClose // body
))
),
new ExpressionStatement( // elseStatement
new MethodInvocation(loc, new LocalVariableAccess(loc, identifier), "close", new Rvalue[0])
)
)
);
f.setEnclosingScope(ts);
// Recurse with one resource less.
return this.compileTryCatchFinally(
ts, // tryStatement
new Compilable2() { // compileBody
@Override public boolean
compile() throws CompileException {
return UnitCompiler.this.compileTryCatchFinallyWithResources(
ts,
followingResources,
compileBody,
finallY
);
}
},
f // finallY
);
} finally {
this.getCodeContext().restoreLocalVariables();
}
}
/**
* Generates code for a TRY statement without resources, but with an (optional) FINALLY clause.
*
* @return Whether the code can complete normally
*/
private boolean
compileTryCatchFinally(
final TryStatement ts,
final Compilable2 compileBody,
@Nullable final BlockStatement finallY
) throws CompileException {
if (finallY == null) {
final CodeContext.Offset beginningOfBody = this.getCodeContext().newOffset();
final CodeContext.Offset afterStatement = this.getCodeContext().new BasicBlock();
boolean canCompleteNormally = this.compileTryCatch(ts, compileBody, beginningOfBody, afterStatement);
afterStatement.set();
return canCompleteNormally;
}
// Compile a TRY statement *with* a FINALLY clause.
final CodeContext.Offset afterStatement = this.getCodeContext().new BasicBlock();
boolean canCompleteNormally;
this.getCodeContext().saveLocalVariables();
try {
StackMap smBeforeBody = this.getCodeContext().currentInserter().getStackMap();
final CodeContext.Offset beginningOfBody = this.getCodeContext().newOffset();
canCompleteNormally = this.compileTryCatch(ts, compileBody, beginningOfBody, afterStatement);
StackMap smAfterBody = this.getCodeContext().currentInserter().getStackMap();
// Generate the "catch (any) {" clause that invokes the FINALLY subroutine.
this.getCodeContext().saveLocalVariables();
try {
this.getCodeContext().currentInserter().setStackMap(smBeforeBody);
// Push the exception on the operand stack.
this.getCodeContext().pushObjectOperand(Descriptor.JAVA_LANG_THROWABLE);
CodeContext.Offset here = this.getCodeContext().newBasicBlock();
this.getCodeContext().addExceptionTableEntry(
beginningOfBody, // startPC
here, // endPC
here, // handlerPC
null // catchTypeFD
);
// Save the exception object in an anonymous local variable.
short evi = this.getCodeContext().allocateLocalVariable((short) 1);
this.store(
finallY, // locatable
this.iClassLoader.TYPE_java_lang_Throwable, // lvType
evi // lvIndex
);
if (this.compile(finallY)) {
this.load(
finallY, // locatable
this.iClassLoader.TYPE_java_lang_Throwable, // type
evi // index
);
this.athrow(finallY);
}
this.getCodeContext().currentInserter().setStackMap(smAfterBody);
} finally {
// The exception object local variable allocated above MUST NOT BE RELEASED until after the FINALLY
// block is compiled, for otherwise you get
// java.lang.VerifyError: ... Accessing value from uninitialized register 7
this.getCodeContext().restoreLocalVariables();
}
} finally {
this.getCodeContext().restoreLocalVariables();
}
afterStatement.set();
if (canCompleteNormally) canCompleteNormally = UnitCompiler.this.compile(finallY);
return canCompleteNormally;
}
/**
* Generates code for a TRY statement without resources and without a FINALLY clause.
*
* @return Whether the code can complete normally
*/
private boolean
compileTryCatch(
TryStatement tryStatement,
Compilable2 compileBody,
final CodeContext.Offset beginningOfBody,
final CodeContext.Offset afterStatement
) throws CompileException {
// Initialize all catch clauses as "unreachable" only to check later that they ARE indeed reachable.
for (CatchClause catchClause : tryStatement.catchClauses) {
catchClause.reachable = false;
for (Type t : catchClause.catchParameter.types) {
IType caughtExceptionType = this.getType(t);
catchClause.reachable |= (
// Superclass or subclass of "java.lang.Error"?
UnitCompiler.isAssignableFrom(this.iClassLoader.TYPE_java_lang_Error, caughtExceptionType)
|| UnitCompiler.isAssignableFrom(caughtExceptionType, this.iClassLoader.TYPE_java_lang_Error)
// Superclass or subclass of "java.lang.RuntimeException"?
|| UnitCompiler.isAssignableFrom(this.iClassLoader.TYPE_java_lang_RuntimeException, caughtExceptionType)
|| UnitCompiler.isAssignableFrom(caughtExceptionType, this.iClassLoader.TYPE_java_lang_RuntimeException)
);
}
}
StackMap smBeforeBody = this.getCodeContext().currentInserter().getStackMap();
boolean bodyCcn = compileBody.compile();
CodeContext.Offset afterBody = this.getCodeContext().newOffset();
StackMap smAfterBody = this.getCodeContext().currentInserter().getStackMap();
if (bodyCcn) {
this.gotO(tryStatement, afterStatement);
}
boolean catchCcn = false; // "At least one catch clause can complete normally"
if (beginningOfBody.offset != afterBody.offset) { // Avoid zero-length exception table entries.
for (int i = 0; i < tryStatement.catchClauses.size(); ++i) {
this.getCodeContext().currentInserter().setStackMap(smBeforeBody);
this.getCodeContext().saveLocalVariables();
try {
CatchClause catchClause = (CatchClause) tryStatement.catchClauses.get(i);
if (catchClause.catchParameter.types.length != 1) {
throw UnitCompiler.compileException(catchClause, "Multi-type CATCH parameter NYI");
}
IClass caughtExceptionType = this.getRawType(catchClause.catchParameter.types[0]);
// Verify that the CATCH clause is reachable.
if (!catchClause.reachable) {
this.compileError("Catch clause is unreachable", catchClause.getLocation());
}
// Push the exception on the operand stack.
this.getCodeContext().pushObjectOperand(caughtExceptionType.getDescriptor());
// Allocate the "exception variable".
LocalVariableSlot
exceptionVarSlot = this.allocateLocalVariableSlot(caughtExceptionType, catchClause.catchParameter.name);
// Kludge: Treat the exception variable like a local variable of the catch clause body.
this.getLocalVariable(catchClause.catchParameter).setSlot(exceptionVarSlot);
this.getCodeContext().addExceptionTableEntry(
beginningOfBody, // startPC
afterBody, // endPC
this.getCodeContext().newBasicBlock(), // handlerPC
caughtExceptionType.getDescriptor() // catchTypeFD
);
this.store(
catchClause, // locatable
caughtExceptionType, // lvType
exceptionVarSlot.getSlotIndex() // lvIndex
);
if (this.compile(catchClause.body)) {
if (tryStatement.finallY == null || this.compile(tryStatement.finallY)) {
catchCcn = true;
this.gotO(catchClause, afterStatement);
afterStatement.setStackMap();
}
}
} finally {
this.getCodeContext().restoreLocalVariables();
}
}
}
this.getCodeContext().currentInserter().setStackMap(smAfterBody);
return bodyCcn | catchCcn;
}
// ------------ FunctionDeclarator.compile() -------------
private void
compile(FunctionDeclarator fd, final ClassFile classFile) throws CompileException {
try {
this.compile2(fd, classFile);
} catch (ClassFileException cfe) {
throw new ClassFileException("Compiling \"" + fd + "\": " + cfe.getMessage(), cfe);
} catch (RuntimeException re) {
throw new InternalCompilerException(fd.getLocation(), "Compiling \"" + fd + "\"", re);
}
}
private void
compile2(FunctionDeclarator fd, final ClassFile classFile) throws CompileException {
ClassFile.MethodInfo mi;
if (this.getTargetVersion() < 8 && fd instanceof MethodDeclarator && ((MethodDeclarator) fd).isDefault()) {
this.compileError((
""
+ "Default interface methods only available for target version 8+. "
+ "Either use \"setTargetVersion(8)\", or \"-DdefaultTargetVersion=8\"."
), fd.getLocation());
}
if (fd.getAccess() == Access.PRIVATE) {
if (
fd instanceof MethodDeclarator
&& !((MethodDeclarator) fd).isStatic()
&& !(fd.getDeclaringType() instanceof InterfaceDeclaration)
) {
// To make the non-static private method invocable for enclosing types, enclosed types and types
// enclosed by the same type, it is modified as follows:
// + Access is changed from PRIVATE to PACKAGE
// + The name is appended with "$"
// + It is made static
// + A parameter of type "declaring class" is prepended to the signature
short accessFlags = UnitCompiler.changeAccessibility(this.accessFlags(fd.getModifiers()), Mod.PACKAGE);
accessFlags |= Mod.STATIC;
mi = classFile.addMethodInfo(
accessFlags, // accessFlags
fd.name + '$', // methodName
( // methodMd
this.toIMethod((MethodDeclarator) fd)
.getDescriptor()
.prependParameter(this.resolve(fd.getDeclaringType()).getDescriptor())
)
);
} else
{
// TODO: Compile annotations on functions.
// assert fd.modifiers.annotations.length == 0 : "NYI";
short accessFlags = this.accessFlags(fd.getModifiers());
// To make the static private class method or private constructor invocable for enclosing types,
// enclosed types and types enclosed by the same type, it is modified as follows:
// + Access is changed from PRIVATE to PACKAGE
if (!(fd.getDeclaringType() instanceof InterfaceDeclaration)) {
accessFlags = UnitCompiler.changeAccessibility(accessFlags, Mod.PACKAGE);
}
if (fd.formalParameters.variableArity) accessFlags |= Mod.VARARGS;
mi = classFile.addMethodInfo(
accessFlags, // accessFlags
fd.name, // methodName
this.toIInvocable(fd).getDescriptor() // methodMD
);
}
} else {
// Non-PRIVATE function.
short accessFlags = this.accessFlags(fd.getModifiers());
if (fd.formalParameters.variableArity) accessFlags |= Mod.VARARGS;
if (fd.getDeclaringType() instanceof InterfaceDeclaration) {
accessFlags |= Mod.PUBLIC;
if (Mod.isStatic(accessFlags) && !"".equals(fd.name)) {
if (this.getTargetVersion() < 8) {
this.compileError("Static interface methods only available for target version 8+", fd.getLocation());
}
} else
if (fd instanceof MethodDeclarator && ((MethodDeclarator) fd).isDefault()) {
if (this.getTargetVersion() < 8) {
this.compileError("Default methods only available for target version 8+", fd.getLocation());
}
} else
{
accessFlags |= Mod.ABSTRACT;
}
}
mi = classFile.addMethodInfo(
accessFlags, // accessFlags
fd.name, // methodName
this.toIInvocable(fd).getDescriptor() // methodMD
);
}
// Add method annotations with retention != SOURCE.
this.compileAnnotations(fd.getAnnotations(), mi, classFile);
// Add "Exceptions" attribute (JVMS 4.7.4).
{
if (fd.thrownExceptions.length > 0) {
final short eani = classFile.addConstantUtf8Info("Exceptions");
List tecciis = new ArrayList<>(); // new short[fd.thrownExceptions.length];
for (int i = 0; i < fd.thrownExceptions.length; ++i) {
final Type te = fd.thrownExceptions[i];
if (te instanceof ReferenceType) {
ReferenceType rt = (ReferenceType) te;
// Don't include thrown exceptions that are parameterized, e.g.
// void meth() throws EX {...}
// , because we don't generate "Signature" attributes for methods, and "throws Throwable"
// would cause compilation problems when the class is loaded later, e.g. by ClassFileIClass.
if (
rt.identifiers.length == 1
&& UnitCompiler.LOOKS_LIKE_TYPE_PARAMETER.matcher(rt.identifiers[0]).matches()
) continue;
}
tecciis.add(classFile.addConstantClassInfo(this.getRawType(te).getDescriptor()));
}
short[] sa = new short[tecciis.size()];
for (int i = 0; i < tecciis.size(); i++) sa[i] = (Short) tecciis.get(i);
mi.addAttribute(new ClassFile.ExceptionsAttribute(eani, sa));
}
}
// Add "Deprecated" attribute (JVMS 4.7.10)
if (fd.hasDeprecatedDocTag()) {
mi.addAttribute(new ClassFile.DeprecatedAttribute(classFile.addConstantUtf8Info("Deprecated")));
}
// Add "AnnotationDefault" attribute (JVMS8 4.7.22)
if (fd instanceof MethodDeclarator) {
ElementValue defaultValue = ((MethodDeclarator) fd).defaultValue;
if (defaultValue != null) {
mi.addAttribute(
new ClassFile.AnnotationDefaultAttribute(
classFile.addConstantUtf8Info("AnnotationDefault"),
UnitCompiler.this.compileElementValue(defaultValue, classFile, fd.type instanceof ArrayType)
)
);
}
}
if (fd.getDeclaringType() instanceof InterfaceDeclaration) {
MethodDeclarator md = (MethodDeclarator) fd;
if (md.getAccess() == Access.PRIVATE && this.getTargetVersion() < 9) {
this.compileError("Private interface methods only available for target version 9+", fd.getLocation());
return;
}
if (md.isStrictfp() && !md.isDefault() && !md.isStatic()) {
this.compileError(
"Modifier strictfp only allowed for interface default methods and static interface methods",
fd.getLocation()
);
return;
}
}
if (
(
fd.getDeclaringType() instanceof InterfaceDeclaration
&& !((MethodDeclarator) fd).isStatic()
&& ((MethodDeclarator) fd).getAccess() != Access.PRIVATE
)
|| (fd instanceof MethodDeclarator && ((MethodDeclarator) fd).isAbstract())
|| (fd instanceof MethodDeclarator && ((MethodDeclarator) fd).isNative())
) {
if (((MethodDeclarator) fd).isDefault()) {
if (!(fd.getDeclaringType() instanceof InterfaceDeclaration)) {
this.compileError("Only interface method declarations may have the \"default\" modifier", fd.getLocation());
} else
if (((MethodDeclarator) fd).isStatic()) {
this.compileError("Static interface method declarations must not have the \"default\" modifier", fd.getLocation());
} else
if (fd.statements == null) {
this.compileError("Default method declarations must have a body", fd.getLocation());
}
} else {
if (fd.statements != null) this.compileError("Method must not declare a body", fd.getLocation());
return;
}
}
// Create CodeContext.
final CodeContext codeContext = new CodeContext(mi.getClassFile());
CodeContext savedCodeContext = this.replaceCodeContext(codeContext);
try {
this.getCodeContext().saveLocalVariables();
if (fd instanceof MethodDeclarator) {
MethodDeclarator md = (MethodDeclarator) fd;
if (!md.isStatic()) {
// Define special parameter "this".
this.allocateLocalVariableSlotAndMarkAsInitialized(this.resolve(fd.getDeclaringType()), "this");
}
}
if (fd instanceof ConstructorDeclarator) {
// Define special parameter "unititialized this".
{
IClass rawThisType = this.resolve(fd.getDeclaringType());
LocalVariableSlot result = this.getCodeContext().allocateLocalVariable(
(short) 1,
"this",
rawThisType
);
this.updateLocalVariableInCurrentStackMap(
result.getSlotIndex(),
StackMapTableAttribute.UNINITIALIZED_THIS_VARIABLE_INFO
);
}
ConstructorDeclarator constructorDeclarator = (ConstructorDeclarator) fd;
if (fd.getDeclaringType() instanceof EnumDeclaration) {
// Define special constructor parameters "String $name" and "int $ordinal" for enums.
LocalVariable lv1 = this.allocateLocalVariableAndMarkAsInitialized(true /*finaL*/, this.iClassLoader.TYPE_java_lang_String);
constructorDeclarator.syntheticParameters.put("$name", lv1);
LocalVariable lv2 = this.allocateLocalVariableAndMarkAsInitialized(true /*finaL*/, IClass.INT);
constructorDeclarator.syntheticParameters.put("$ordinal", lv2);
}
// Define synthetic parameters for inner classes ("this$...", "val$...").
for (IField sf : constructorDeclarator.getDeclaringClass().syntheticFields.values()) {
LocalVariable lv = this.allocateLocalVariableAndMarkAsInitialized(true /*finaL*/, sf.getType());
constructorDeclarator.syntheticParameters.put(sf.getName(), lv);
}
}
this.buildLocalVariableMap(fd);
this.codeContext.newOffset();
// Compile the constructor preamble.
if (fd instanceof ConstructorDeclarator) {
ConstructorDeclarator cd = (ConstructorDeclarator) fd;
ConstructorInvocation ci = cd.constructorInvocation;
if (ci != null) {
if (ci instanceof SuperConstructorInvocation) {
this.assignSyntheticParametersToSyntheticFields(cd);
}
this.compile(ci);
// Object initialization is complete; change the verification type info of "this" from
// "uninitializedThis" to "object".
this.updateLocalVariableInCurrentStackMap((short) 0, this.verificationTypeInfo(this.resolve(fd.getDeclaringType())));
if (ci instanceof SuperConstructorInvocation) {
this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd);
}
} else {
// Determine qualification for superconstructor invocation.
IClass superclass = this.resolve(cd.getDeclaringClass()).getSuperclass();
if (superclass == null) {
throw new CompileException("\"" + cd + "\" has no superclass", cd.getLocation());
}
IClass outerClassOfSuperclass = superclass.getOuterIClass();
QualifiedThisReference qualification = null;
if (outerClassOfSuperclass != null) {
qualification = new QualifiedThisReference(
cd.getLocation(), // location
new SimpleType(cd.getLocation(), outerClassOfSuperclass) // qualification
);
}
// Initialize "this.this$0" and friends.
this.assignSyntheticParametersToSyntheticFields(cd);
// Invoke the superconstructor.
Rvalue[] arguments;
if (fd.getDeclaringType() instanceof EnumDeclaration) {
LocalVariableAccess nameAccess = new LocalVariableAccess(
cd.getLocation(),
(LocalVariable) cd.syntheticParameters.get("$name")
);
assert nameAccess != null;
LocalVariableAccess ordinalAccess = new LocalVariableAccess(
cd.getLocation(),
(LocalVariable) cd.syntheticParameters.get("$ordinal")
);
assert ordinalAccess != null;
arguments = new Rvalue[] { nameAccess, ordinalAccess };
} else {
arguments = new Rvalue[0];
}
SuperConstructorInvocation sci = new SuperConstructorInvocation(
cd.getLocation(), // location
qualification, // qualification
arguments // arguments
);
sci.setEnclosingScope(fd);
this.compile(sci);
// Object initialization is complete; change the verification type info of "this" from
// "uninitializedThis" to "object".
this.updateLocalVariableInCurrentStackMap((short) 0, this.verificationTypeInfo(this.resolve(fd.getDeclaringType())));
// Initialize "this.x = y".
this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd);
}
}
// Compile the function body.
List extends BlockStatement> oss = fd.statements;
if (oss == null) {
this.compileError("Method must have a body", fd.getLocation());
return;
}
if (this.compileStatements(oss)) {
if (this.getReturnType(fd) != IClass.VOID) {
this.compileError("Method must return a value", fd.getLocation());
}
this.returN(fd);
}
} finally {
this.getCodeContext().restoreLocalVariables();
this.replaceCodeContext(savedCodeContext);
}
// Don't continue code attribute generation if we had compile errors.
if (this.compileErrorCount > 0) return;
// Fix up and reallocate as needed.
codeContext.fixUpAndRelocate();
if (this.debugVars) {
UnitCompiler.makeLocalVariableNames(codeContext, mi);
}
// Add the code context as a code attribute to the MethodInfo.
boolean hasThis = (Boolean) fd.accept(new FunctionDeclaratorVisitor() {
@Override @Nullable public Boolean
visitConstructorDeclarator(ConstructorDeclarator cd) { return true; }
@Override @Nullable public Boolean
visitMethodDeclarator(MethodDeclarator md) { return !md.isStatic(); }
});
try {
mi.addAttribute(codeContext.newCodeAttribute((
(hasThis ? 1 : 0)
+ (fd instanceof ConstructorDeclarator ? ((ConstructorDeclarator) fd).syntheticParameters.size() : 0)
+ fd.formalParameters.parameters.length
), this.debugLines, this.debugVars));
} catch (Error e) {
throw new InternalCompilerException(fd.getLocation(), null, e);
}
}
private int getTargetVersion() {
if (this.targetVersion == -1) {
this.targetVersion = UnitCompiler.defaultTargetVersion;
if (this.targetVersion == -1) {
// // System property Description Example values
// // ------------------------------------------------------------------------------------------------
// // java.vm.specification.version Java Virtual Machine specification version "1.8", "11"
// // java.specification.version Java Runtime Environment specification version "1.8", "11"
// // java.version Java Runtime Environment version "1.8.0_45", "11-ea"
// // java.class.version Java class format version number "52.0", "55.0"
// String jsv = System.getProperty("java.specification.version");
// jsv = jsv.substring(jsv.indexOf('.') + 1);
// this.targetVersion = Integer.parseInt(jsv);
// Because the generation of the StackMapTable attribute is still experimental, we still produce
// only Java 6 .class files by default:
this.targetVersion = 6;
}
}
return this.targetVersion;
}
/**
* Makes the variable name and class name Constant Pool names used by local variables.
*/
private static void
makeLocalVariableNames(final CodeContext cc, final ClassFile.MethodInfo mi) {
ClassFile cf = mi.getClassFile();
cf.addConstantUtf8Info("LocalVariableTable");
for (LocalVariableSlot slot : cc.getAllLocalVars()) {
String localVariableName = slot.getName();
if (localVariableName != null) {
String typeName = UnitCompiler.rawTypeOf(slot.getType()).getDescriptor();
cf.addConstantUtf8Info(typeName);
cf.addConstantUtf8Info(localVariableName);
}
}
}
private void
buildLocalVariableMap(FunctionDeclarator fd) throws CompileException {
Map localVars = new HashMap<>();
// Add function parameters.
for (int i = 0; i < fd.formalParameters.parameters.length; ++i) {
FormalParameter fp = fd.formalParameters.parameters[i];
LocalVariable lv = this.getLocalVariable(
fp,
i == fd.formalParameters.parameters.length - 1 && fd.formalParameters.variableArity
);
lv.setSlot(this.allocateLocalVariableSlotAndMarkAsInitialized(lv.type, fp.name));
if (localVars.put(fp.name, lv) != null) {
this.compileError("Redefinition of parameter \"" + fp.name + "\"", fd.getLocation());
}
}
fd.localVariables = localVars;
if (fd instanceof ConstructorDeclarator) {
ConstructorDeclarator cd = (ConstructorDeclarator) fd;
ConstructorInvocation ci = cd.constructorInvocation;
if (ci != null) {
UnitCompiler.buildLocalVariableMap(ci, localVars);
}
}
if (fd.statements != null) {
for (BlockStatement bs : fd.statements) localVars = this.buildLocalVariableMap(bs, localVars);
}
}
private LocalVariableSlot
allocateLocalVariableSlot(IType localVariableType, @Nullable String localVariableName) {
LocalVariableSlot result = this.getCodeContext().allocateLocalVariable(
Descriptor.size(UnitCompiler.rawTypeOf(localVariableType).getDescriptor()),
localVariableName,
localVariableType
);
return result;
}
private LocalVariableSlot
allocateLocalVariableSlotAndMarkAsInitialized(IType localVariableType, @Nullable String localVariableName) {
LocalVariableSlot result = this.allocateLocalVariableSlot(localVariableType, localVariableName);
this.updateLocalVariableInCurrentStackMap(result.getSlotIndex(), this.verificationTypeInfo(localVariableType));
return result;
}
/**
* Computes and fills in the 'local variable map' for the given blockStatement .
*/
private Map
buildLocalVariableMap(BlockStatement blockStatement, final Map localVars)
throws CompileException {
Map result = (Map) blockStatement.accept(
new BlockStatementVisitor, CompileException>() {
// Basic statements that use the default handlers.
@Override public Map visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { UnitCompiler.buildLocalVariableMap(aci, localVars); return localVars; }
@Override public Map visitBreakStatement(BreakStatement bs) { UnitCompiler.buildLocalVariableMap(bs, localVars); return localVars; }
@Override public Map visitContinueStatement(ContinueStatement cs) { UnitCompiler.buildLocalVariableMap(cs, localVars); return localVars; }
@Override public Map visitAssertStatement(AssertStatement as) { UnitCompiler.buildLocalVariableMap(as, localVars); return localVars; }
@Override public Map visitEmptyStatement(EmptyStatement es) { UnitCompiler.buildLocalVariableMap(es, localVars); return localVars; }
@Override public Map visitExpressionStatement(ExpressionStatement es) { UnitCompiler.buildLocalVariableMap(es, localVars); return localVars; }
@Override public Map visitFieldDeclaration(FieldDeclaration fd) { UnitCompiler.buildLocalVariableMap(fd, localVars); return localVars; }
@Override public Map visitReturnStatement(ReturnStatement rs) { UnitCompiler.buildLocalVariableMap(rs, localVars); return localVars; }
@Override public Map visitSuperConstructorInvocation(SuperConstructorInvocation sci) { UnitCompiler.buildLocalVariableMap(sci, localVars); return localVars; }
@Override public Map visitThrowStatement(ThrowStatement ts) { UnitCompiler.buildLocalVariableMap(ts, localVars); return localVars; }
@Override public Map visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { UnitCompiler.buildLocalVariableMap(lcds, localVars); return localVars; }
// More complicated statements with specialized handlers, but don't add new variables in this scope.
@Override public Map visitBlock(Block b) throws CompileException { UnitCompiler.this.buildLocalVariableMap(b, localVars); return localVars; }
@Override public Map visitDoStatement(DoStatement ds) throws CompileException { UnitCompiler.this.buildLocalVariableMap(ds, localVars); return localVars; }
@Override public Map visitForStatement(ForStatement fs) throws CompileException { UnitCompiler.this.buildLocalVariableMap(fs, localVars); return localVars; }
@Override public Map visitForEachStatement(ForEachStatement fes) throws CompileException { UnitCompiler.this.buildLocalVariableMap(fes, localVars); return localVars; }
@Override public Map visitIfStatement(IfStatement is) throws CompileException { UnitCompiler.this.buildLocalVariableMap(is, localVars); return localVars; }
@Override public Map visitInitializer(Initializer i) throws CompileException { UnitCompiler.this.buildLocalVariableMap(i, localVars); return localVars; }
@Override public Map visitSwitchStatement(SwitchStatement ss) throws CompileException { UnitCompiler.this.buildLocalVariableMap(ss, localVars); return localVars; }
@Override public Map visitSynchronizedStatement(SynchronizedStatement ss) throws CompileException { UnitCompiler.this.buildLocalVariableMap(ss, localVars); return localVars; }
@Override public Map visitTryStatement(TryStatement ts) throws CompileException { UnitCompiler.this.buildLocalVariableMap(ts, localVars); return localVars; }
@Override public Map visitWhileStatement(WhileStatement ws) throws CompileException { UnitCompiler.this.buildLocalVariableMap(ws, localVars); return localVars; }
// More complicated statements with specialized handlers, that can add variables in this scope.
@Override public Map visitLabeledStatement(LabeledStatement ls) throws CompileException { return UnitCompiler.this.buildLocalVariableMap(ls, localVars); }
@Override public Map visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) throws CompileException { return UnitCompiler.this.buildLocalVariableMap(lvds, localVars); }
}
);
assert result != null;
return result;
}
// Default handlers.
private static Map
buildLocalVariableMap(Statement s, final Map localVars) {
return (s.localVariables = localVars);
}
private static Map
buildLocalVariableMap(ConstructorInvocation ci, final Map localVars) {
return (ci.localVariables = localVars);
}
// Specialized handlers.
private void
buildLocalVariableMap(Block block, Map localVars) throws CompileException {
block.localVariables = localVars;
for (BlockStatement bs : block.statements) localVars = this.buildLocalVariableMap(bs, localVars);
}
private void
buildLocalVariableMap(DoStatement ds, final Map localVars) throws CompileException {
ds.localVariables = localVars;
this.buildLocalVariableMap(ds.body, localVars);
}
private void
buildLocalVariableMap(ForStatement fs, final Map localVars)
throws CompileException {
Map inner = localVars;
if (fs.init != null) {
inner = this.buildLocalVariableMap(fs.init, localVars);
}
fs.localVariables = inner;
this.buildLocalVariableMap(fs.body, inner);
}
private void
buildLocalVariableMap(ForEachStatement fes, final Map localVars)
throws CompileException {
Map vars = new HashMap<>();
vars.putAll(localVars);
LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false);
vars.put(fes.currentElement.name, elementLv);
fes.localVariables = vars;
this.buildLocalVariableMap(fes.body, vars);
}
private void
buildLocalVariableMap(IfStatement is, final Map localVars) throws CompileException {
is.localVariables = localVars;
this.buildLocalVariableMap(is.thenStatement, localVars);
if (is.elseStatement != null) {
this.buildLocalVariableMap(is.elseStatement, localVars);
}
}
private void
buildLocalVariableMap(Initializer i, final Map localVars) throws CompileException {
this.buildLocalVariableMap(i.block, localVars);
}
private void
buildLocalVariableMap(SwitchStatement ss, final Map localVars)
throws CompileException {
ss.localVariables = localVars;
Map vars = localVars;
for (SwitchBlockStatementGroup sbsg : ss.sbsgs) {
for (BlockStatement bs : sbsg.blockStatements) vars = this.buildLocalVariableMap(bs, vars);
}
}
private void
buildLocalVariableMap(SynchronizedStatement ss, final Map localVars)
throws CompileException {
ss.localVariables = localVars;
this.buildLocalVariableMap(ss.body, localVars);
}
private void
buildLocalVariableMap(TryStatement ts, final Map localVars)
throws CompileException {
ts.localVariables = localVars;
this.buildLocalVariableMap(ts.body, localVars);
for (CatchClause cc : ts.catchClauses) this.buildLocalVariableMap(cc, localVars);
if (ts.finallY != null) {
this.buildLocalVariableMap(ts.finallY, localVars);
}
}
private void
buildLocalVariableMap(WhileStatement ws, final Map localVars)
throws CompileException {
ws.localVariables = localVars;
this.buildLocalVariableMap(ws.body, localVars);
}
private Map
buildLocalVariableMap(LabeledStatement ls, final Map localVars)
throws CompileException {
ls.localVariables = localVars;
return this.buildLocalVariableMap((BlockStatement) ls.body, localVars);
}
private Map
buildLocalVariableMap(LocalVariableDeclarationStatement lvds, final Map localVars)
throws CompileException {
Map newVars = new HashMap<>();
newVars.putAll(localVars);
for (VariableDeclarator vd : lvds.variableDeclarators) {
LocalVariable lv = this.getLocalVariable(lvds, vd);
if (newVars.put(vd.name, lv) != null) {
this.compileError("Redefinition of local variable \"" + vd.name + "\" ", vd.getLocation());
}
}
lvds.localVariables = newVars;
return newVars;
}
/**
* Adds the given localVars to the 'local variable map' of the given catchClause .
*/
protected void
buildLocalVariableMap(CatchClause catchClause, Map localVars) throws CompileException {
Map vars = new HashMap<>();
vars.putAll(localVars);
LocalVariable lv = this.getLocalVariable(catchClause.catchParameter);
vars.put(catchClause.catchParameter.name, lv);
this.buildLocalVariableMap(catchClause.body, vars);
}
/**
* @return The {@link LocalVariable} corresponding with the parameter
*/
public LocalVariable
getLocalVariable(FormalParameter parameter) throws CompileException {
return this.getLocalVariable(parameter, false);
}
/**
* @param isVariableArityParameter Whether the parameter is the last parameter of a 'variable arity'
* (a.k.a. 'varargs') method declaration
* @return The {@link LocalVariable} corresponding with the parameter
*/
public LocalVariable
getLocalVariable(FormalParameter parameter, boolean isVariableArityParameter) throws CompileException {
if (parameter.localVariable != null) return parameter.localVariable;
assert parameter.type != null;
IType parameterType = this.getType(parameter.type);
if (isVariableArityParameter) {
parameterType = this.iClassLoader.getArrayIClass(UnitCompiler.rawTypeOf(parameterType));
}
return (parameter.localVariable = new LocalVariable(parameter.isFinal(), parameterType));
}
/**
* @return The {@link LocalVariable} corresponding with the parameter
*/
public LocalVariable
getLocalVariable(CatchParameter parameter) throws CompileException {
if (parameter.localVariable != null) return parameter.localVariable;
if (parameter.types.length != 1) {
throw UnitCompiler.compileException(parameter, "Multi-type CATCH parameters NYI");
}
IType parameterType = this.getType(parameter.types[0]);
return (parameter.localVariable = new LocalVariable(parameter.finaL, parameterType));
}
// ------------------ Rvalue.compile() ----------------
/**
* Called to check whether the given {@link Rvalue} compiles or not.
*/
private void
fakeCompile(Rvalue rv) throws CompileException {
final Offset from = this.getCodeContext().newOffset();
StackMap savedStackMap = this.getCodeContext().currentInserter().getStackMap();
this.compileContext(rv);
this.compileGet(rv);
Offset to = this.getCodeContext().newOffset();
this.getCodeContext().removeCode(from, to.next);
this.getCodeContext().currentInserter().setStackMap(savedStackMap);
}
/**
* Some {@link Rvalue}s compile more efficiently when their value is not needed, e.g. "i++".
*/
private void
compile(Rvalue rv) throws CompileException {
rv.accept(new RvalueVisitor() {
@Override @Nullable public Void
visitLvalue(Lvalue lv) throws CompileException {
lv.accept(new LvalueVisitor() {
@Override @Nullable public Void visitAmbiguousName(AmbiguousName an) throws CompileException { UnitCompiler.this.compile2(an); return null; }
@Override @Nullable public Void visitArrayAccessExpression(ArrayAccessExpression aae) throws CompileException { UnitCompiler.this.compile2(aae); return null; }
@Override @Nullable public Void visitFieldAccess(FieldAccess fa) throws CompileException { UnitCompiler.this.compile2(fa); return null; }
@Override @Nullable public Void visitFieldAccessExpression(FieldAccessExpression fae) throws CompileException { UnitCompiler.this.compile2(fae); return null; }
@Override @Nullable public Void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) throws CompileException { UnitCompiler.this.compile2(scfae); return null; }
@Override @Nullable public Void visitLocalVariableAccess(LocalVariableAccess lva) throws CompileException { UnitCompiler.this.compile2(lva); return null; }
@Override @Nullable public Void visitParenthesizedExpression(ParenthesizedExpression pe) throws CompileException { UnitCompiler.this.compile2(pe); return null; }
});
return null;
}
@Override @Nullable public Void visitArrayLength(ArrayLength al) throws CompileException { UnitCompiler.this.compile2(al); return null; }
@Override @Nullable public Void visitAssignment(Assignment a) throws CompileException { UnitCompiler.this.compile2(a); return null; }
@Override @Nullable public Void visitUnaryOperation(UnaryOperation uo) throws CompileException { UnitCompiler.this.compile2(uo); return null; }
@Override @Nullable public Void visitBinaryOperation(BinaryOperation bo) throws CompileException { UnitCompiler.this.compile2(bo); return null; }
@Override @Nullable public Void visitCast(Cast c) throws CompileException { UnitCompiler.this.compile2(c); return null; }
@Override @Nullable public Void visitClassLiteral(ClassLiteral cl) throws CompileException { UnitCompiler.this.compile2(cl); return null; }
@Override @Nullable public Void visitConditionalExpression(ConditionalExpression ce) throws CompileException { UnitCompiler.this.compile2(ce); return null; }
@Override @Nullable public Void visitCrement(Crement c) throws CompileException { UnitCompiler.this.compile2(c); return null; }
@Override @Nullable public Void visitInstanceof(Instanceof io) throws CompileException { UnitCompiler.this.compile2(io); return null; }
@Override @Nullable public Void visitMethodInvocation(MethodInvocation mi) throws CompileException { UnitCompiler.this.compile2(mi); return null; }
@Override @Nullable public Void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) throws CompileException { UnitCompiler.this.compile2(smi); return null; }
@Override @Nullable public Void visitIntegerLiteral(IntegerLiteral il) throws CompileException { UnitCompiler.this.compile2(il); return null; }
@Override @Nullable public Void visitFloatingPointLiteral(FloatingPointLiteral fpl) throws CompileException { UnitCompiler.this.compile2(fpl); return null; }
@Override @Nullable public Void visitBooleanLiteral(BooleanLiteral bl) throws CompileException { UnitCompiler.this.compile2(bl); return null; }
@Override @Nullable public Void visitCharacterLiteral(CharacterLiteral cl) throws CompileException { UnitCompiler.this.compile2(cl); return null; }
@Override @Nullable public Void visitStringLiteral(StringLiteral sl) throws CompileException { UnitCompiler.this.compile2(sl); return null; }
@Override @Nullable public Void visitNullLiteral(NullLiteral nl) throws CompileException { UnitCompiler.this.compile2(nl); return null; }
@Override @Nullable public Void visitSimpleConstant(SimpleConstant sl) throws CompileException { UnitCompiler.this.compile2(sl); return null; }
@Override @Nullable public Void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) throws CompileException { UnitCompiler.this.compile2(naci); return null; }
@Override @Nullable public Void visitNewArray(NewArray na) throws CompileException { UnitCompiler.this.compile2(na); return null; }
@Override @Nullable public Void visitNewInitializedArray(NewInitializedArray nia) throws CompileException { UnitCompiler.this.compile2(nia); return null; }
@Override @Nullable public Void visitNewClassInstance(NewClassInstance nci) throws CompileException { UnitCompiler.this.compile2(nci); return null; }
@Override @Nullable public Void visitParameterAccess(ParameterAccess pa) throws CompileException { UnitCompiler.this.compile2(pa); return null; }
@Override @Nullable public Void visitQualifiedThisReference(QualifiedThisReference qtr) throws CompileException { UnitCompiler.this.compile2(qtr); return null; }
@Override @Nullable public Void visitThisReference(ThisReference tr) throws CompileException { UnitCompiler.this.compile2(tr); return null; }
@Override @Nullable public Void visitLambdaExpression(LambdaExpression le) throws CompileException { UnitCompiler.this.compile2(le); return null; }
@Override @Nullable public Void visitMethodReference(MethodReference mr) throws CompileException { UnitCompiler.this.compile2(mr); return null; }
@Override @Nullable public Void visitInstanceCreationReference(ClassInstanceCreationReference cicr) throws CompileException { UnitCompiler.this.compile2(cicr); return null; }
@Override @Nullable public Void visitArrayCreationReference(ArrayCreationReference acr) throws CompileException { UnitCompiler.this.compile2(acr); return null; }
});
}
private void
compile2(Rvalue rv) throws CompileException {
this.pop(rv, this.compileGetValue(rv));
}
private void
compile2(Assignment a) throws CompileException {
// "Simple" assignment ("=")?
if (a.operator == "=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileContext(a.lhs);
this.assignmentConversion(
a, // locatable
this.compileGetValue(a.rhs), // sourceType
this.getType(a.lhs), // targetType
this.getConstantValue(a.rhs) // constantValue
);
this.compileSet(a.lhs);
return;
}
// Implement "|= ^= &= *= /= %= += -= <<= >>= >>>=".
// Compile LHS context.
int lhsCs = this.compileContext(a.lhs);
// Duplicate the LHS context.
this.dupn(a.lhs, lhsCs);
// Convert RHS value to LHS type (JLS7 15.26.2).
IType lhsType = this.compileGet(a.lhs);
IType resultType = this.compileArithmeticBinaryOperation(
a, // locatable
lhsType, // lhsType
a.operator.substring( // operator
0,
a.operator.length() - 1
).intern(), /* <= IMPORTANT!
*/
a.rhs // rhs
);
if (
!this.tryIdentityConversion(resultType, lhsType)
&& !this.tryNarrowingPrimitiveConversion(a, resultType, lhsType)
&& !this.tryBoxingConversion(a, resultType, lhsType) // Java 5
) this.compileError("Operand types unsuitable for \"" + a.operator + "\"", a.getLocation());
// Assign converted RHS value to LHS.
this.compileSet(a.lhs);
}
private void
compile2(Crement c) throws CompileException {
// Optimized crement of "int" local variable.
{
LocalVariable lv = this.isIntLv(c);
if (lv != null) {
this.iinc(c, lv, c.operator);
return;
}
}
// Compile operand context.
int operandCs = this.compileContext(c.operand);
// DUP operand context.
this.dupn(c, operandCs);
// Get operand value.
IType type = this.compileGet(c.operand);
{
// Apply "unary numeric promotion".
IClass promotedType = this.unaryNumericPromotion(c, type);
// Crement.
this.consT(c, promotedType, 1);
if (c.operator == "++") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.add(c);
} else
if (c.operator == "--") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.sub(c);
} else {
this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation());
}
this.reverseUnaryNumericPromotion(c, promotedType, type);
}
// Set operand.
this.compileSet(c.operand);
}
private void
compile2(ParenthesizedExpression pe) throws CompileException {
this.compile(pe.value);
}
private boolean
compile2(AlternateConstructorInvocation aci) throws CompileException {
ConstructorDeclarator declaringConstructor = (ConstructorDeclarator) aci.getEnclosingScope();
IClass declaringIClass = this.resolve(declaringConstructor.getDeclaringClass());
this.load(aci, declaringIClass, 0);
if (declaringIClass.getOuterIClass() != null) this.load(aci, declaringIClass.getOuterIClass(), 1);
this.invokeConstructor(
aci, // locatable
declaringConstructor, // scope
(Rvalue) null, // enclosingInstance
declaringIClass, // targetClass
aci.arguments // arguments
);
return true;
}
private boolean
compile2(SuperConstructorInvocation sci) throws CompileException {
ConstructorDeclarator declaringConstructor = (ConstructorDeclarator) sci.getEnclosingScope();
AbstractClassDeclaration declaringClass = declaringConstructor.getDeclaringClass();
IClass declaringIClass = this.resolve(declaringClass);
IClass superclass = declaringIClass.getSuperclass();
this.load(sci, declaringIClass, 0);
// // Fix up the operand stack entry: The loaded object is still unitializied!
// this.getCodeContext().popObjectOperand();
// this.getCodeContext().pushUninitializedThisOperand();
if (superclass == null) throw new CompileException("Class has no superclass", sci.getLocation());
Rvalue enclosingInstance;
if (sci.qualification != null) {
enclosingInstance = sci.qualification;
} else {
IClass outerIClassOfSuperclass = superclass.getOuterIClass();
if (outerIClassOfSuperclass == null) {
enclosingInstance = null;
} else {
enclosingInstance = new QualifiedThisReference(
sci.getLocation(), // location
new SimpleType(sci.getLocation(), outerIClassOfSuperclass) // qualification
);
enclosingInstance.setEnclosingScope(sci);
}
}
this.invokeConstructor(
sci, // locatable
declaringConstructor, // scope
enclosingInstance, // enclosingInstance
superclass, // targetClass
sci.arguments // arguments
);
return true;
}
/**
* Compiles an {@link Rvalue} and branches, depending on the value.
*
* Many {@link Rvalue}s compile more efficiently when their value is the condition for a branch, thus
* this method generally produces more efficient bytecode than {@link #compile(Rvalue)} followed by {@link
* #branch(Locatable, int, Offset)}.
*
*
* Notice that if rv is a constant, then either dst is never branched to, or it is
* unconditionally branched to; "Unexamined code" errors may result during bytecode validation.
*
*
* @param rv The value that determines whether to branch or not
* @param dst Where to jump
* @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE}
*/
private void
compileBoolean(Rvalue rv, final CodeContext.Offset dst, final boolean orientation) throws CompileException {
rv.accept(new RvalueVisitor() {
@Override @Nullable public Void
visitLvalue(Lvalue lv) throws CompileException {
lv.accept(new LvalueVisitor() {
@Override @Nullable public Void visitAmbiguousName(AmbiguousName an) throws CompileException { UnitCompiler.this.compileBoolean2(an, dst, orientation); return null; }
@Override @Nullable public Void visitArrayAccessExpression(ArrayAccessExpression aae) throws CompileException { UnitCompiler.this.compileBoolean2(aae, dst, orientation); return null; }
@Override @Nullable public Void visitFieldAccess(FieldAccess fa) throws CompileException { UnitCompiler.this.compileBoolean2(fa, dst, orientation); return null; }
@Override @Nullable public Void visitFieldAccessExpression(FieldAccessExpression fae) throws CompileException { UnitCompiler.this.compileBoolean2(fae, dst, orientation); return null; }
@Override @Nullable public Void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) throws CompileException { UnitCompiler.this.compileBoolean2(scfae, dst, orientation); return null; }
@Override @Nullable public Void visitLocalVariableAccess(LocalVariableAccess lva) throws CompileException { UnitCompiler.this.compileBoolean2(lva, dst, orientation); return null; }
@Override @Nullable public Void visitParenthesizedExpression(ParenthesizedExpression pe) throws CompileException { UnitCompiler.this.compileBoolean2(pe, dst, orientation); return null; }
});
return null;
}
@Override @Nullable public Void visitArrayLength(ArrayLength al) throws CompileException { UnitCompiler.this.compileBoolean2(al, dst, orientation); return null; }
@Override @Nullable public Void visitAssignment(Assignment a) throws CompileException { UnitCompiler.this.compileBoolean2(a, dst, orientation); return null; }
@Override @Nullable public Void visitUnaryOperation(UnaryOperation uo) throws CompileException { UnitCompiler.this.compileBoolean2(uo, dst, orientation); return null; }
@Override @Nullable public Void visitBinaryOperation(BinaryOperation bo) throws CompileException { UnitCompiler.this.compileBoolean2(bo, dst, orientation); return null; }
@Override @Nullable public Void visitCast(Cast c) throws CompileException { UnitCompiler.this.compileBoolean2(c, dst, orientation); return null; }
@Override @Nullable public Void visitClassLiteral(ClassLiteral cl) throws CompileException { UnitCompiler.this.compileBoolean2(cl, dst, orientation); return null; }
@Override @Nullable public Void visitConditionalExpression(ConditionalExpression ce) throws CompileException { UnitCompiler.this.compileBoolean2(ce, dst, orientation); return null; }
@Override @Nullable public Void visitCrement(Crement c) throws CompileException { UnitCompiler.this.compileBoolean2(c, dst, orientation); return null; }
@Override @Nullable public Void visitInstanceof(Instanceof io) throws CompileException { UnitCompiler.this.compileBoolean2(io, dst, orientation); return null; }
@Override @Nullable public Void visitMethodInvocation(MethodInvocation mi) throws CompileException { UnitCompiler.this.compileBoolean2(mi, dst, orientation); return null; }
@Override @Nullable public Void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) throws CompileException { UnitCompiler.this.compileBoolean2(smi, dst, orientation); return null; }
@Override @Nullable public Void visitIntegerLiteral(IntegerLiteral il) throws CompileException { UnitCompiler.this.compileBoolean2(il, dst, orientation); return null; }
@Override @Nullable public Void visitFloatingPointLiteral(FloatingPointLiteral fpl) throws CompileException { UnitCompiler.this.compileBoolean2(fpl, dst, orientation); return null; }
@Override @Nullable public Void visitBooleanLiteral(BooleanLiteral bl) throws CompileException { UnitCompiler.this.compileBoolean2(bl, dst, orientation); return null; }
@Override @Nullable public Void visitCharacterLiteral(CharacterLiteral cl) throws CompileException { UnitCompiler.this.compileBoolean2(cl, dst, orientation); return null; }
@Override @Nullable public Void visitStringLiteral(StringLiteral sl) throws CompileException { UnitCompiler.this.compileBoolean2(sl, dst, orientation); return null; }
@Override @Nullable public Void visitNullLiteral(NullLiteral nl) throws CompileException { UnitCompiler.this.compileBoolean2(nl, dst, orientation); return null; }
@Override @Nullable public Void visitSimpleConstant(SimpleConstant sl) throws CompileException { UnitCompiler.this.compileBoolean2(sl, dst, orientation); return null; }
@Override @Nullable public Void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) throws CompileException { UnitCompiler.this.compileBoolean2(naci, dst, orientation); return null; }
@Override @Nullable public Void visitNewArray(NewArray na) throws CompileException { UnitCompiler.this.compileBoolean2(na, dst, orientation); return null; }
@Override @Nullable public Void visitNewInitializedArray(NewInitializedArray nia) throws CompileException { UnitCompiler.this.compileBoolean2(nia, dst, orientation); return null; }
@Override @Nullable public Void visitNewClassInstance(NewClassInstance nci) throws CompileException { UnitCompiler.this.compileBoolean2(nci, dst, orientation); return null; }
@Override @Nullable public Void visitParameterAccess(ParameterAccess pa) throws CompileException { UnitCompiler.this.compileBoolean2(pa, dst, orientation); return null; }
@Override @Nullable public Void visitQualifiedThisReference(QualifiedThisReference qtr) throws CompileException { UnitCompiler.this.compileBoolean2(qtr, dst, orientation); return null; }
@Override @Nullable public Void visitThisReference(ThisReference tr) throws CompileException { UnitCompiler.this.compileBoolean2(tr, dst, orientation); return null; }
@Override @Nullable public Void visitLambdaExpression(LambdaExpression le) throws CompileException { UnitCompiler.this.compileBoolean2(le, dst, orientation); return null; }
@Override @Nullable public Void visitMethodReference(MethodReference mr) throws CompileException { UnitCompiler.this.compileBoolean2(mr, dst, orientation); return null; }
@Override @Nullable public Void visitInstanceCreationReference(ClassInstanceCreationReference cicr) throws CompileException { UnitCompiler.this.compileBoolean2(cicr, dst, orientation); return null; }
@Override @Nullable public Void visitArrayCreationReference(ArrayCreationReference acr) throws CompileException { UnitCompiler.this.compileBoolean2(acr, dst, orientation); return null; }
});
}
/**
* @param dst Where to jump
* @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE}
*/
private void
compileBoolean2(Rvalue rv, CodeContext.Offset dst, boolean orientation) throws CompileException {
IType type = this.compileGetValue(rv);
IClassLoader icl = this.iClassLoader;
if (type == icl.TYPE_java_lang_Boolean) {
this.unboxingConversion(rv, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN);
} else
if (type != IClass.BOOLEAN) {
this.compileError("Not a boolean expression", rv.getLocation());
}
this.ifxx(rv, orientation == UnitCompiler.JUMP_IF_TRUE ? UnitCompiler.NE : UnitCompiler.EQ, dst);
}
/**
* @param dst Where to jump
* @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE}
*/
private void
compileBoolean2(UnaryOperation ue, CodeContext.Offset dst, boolean orientation) throws CompileException {
if (ue.operator == "!") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileBoolean(ue.operand, dst, !orientation);
return;
}
this.compileError("Boolean expression expected", ue.getLocation());
}
/**
* @param dst Where to jump
* @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE}
*/
private void
compileBoolean2(BinaryOperation bo, CodeContext.Offset dst, boolean orientation) throws CompileException {
if (bo.operator == "|" || bo.operator == "^" || bo.operator == "&") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileBoolean2((Rvalue) bo, dst, orientation);
return;
}
if (bo.operator == "||" || bo.operator == "&&") { // SUPPRESS CHECKSTYLE StringLiteralEquality
Object lhsCv = this.getConstantValue(bo.lhs);
if (lhsCv instanceof Boolean) {
if (((Boolean) lhsCv).booleanValue() ^ bo.operator == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality
// "true && a", "false || a"
this.compileBoolean(
bo.rhs,
dst,
orientation
);
} else {
// "false && a", "true || a"
this.compileBoolean(
bo.lhs,
dst,
orientation
);
this.fakeCompile(bo.rhs);
}
return;
}
Object rhsCv = this.getConstantValue(bo.rhs);
if (rhsCv instanceof Boolean) {
if (((Boolean) rhsCv).booleanValue() ^ bo.operator == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality
// "a && true", "a || false"
this.compileBoolean(
bo.lhs,
dst,
orientation
);
} else {
// "a && false", "a || true"
// Compile the LHS ("a"), and discard the result.
this.pop(bo.lhs, this.compileGetValue(bo.lhs));
// Compile the RHS and branch conditionally (although the RHS is a constant). This prevents
// trouble with "unreachable code".
this.compileBoolean(
bo.rhs,
dst,
orientation
);
}
return;
}
// SUPPRESS CHECKSTYLE StringLiteralEquality
if (bo.operator == "||" ^ orientation == UnitCompiler.JUMP_IF_FALSE) {
this.compileBoolean(bo.lhs, dst, orientation);
this.compileBoolean(bo.rhs, dst, orientation);
} else {
CodeContext.Offset end = this.getCodeContext().new BasicBlock();
this.compileBoolean(bo.lhs, end, !orientation);
this.compileBoolean(bo.rhs, dst, orientation);
end.set();
}
return;
}
COMPARISON:
{
final int opIdx = (
bo.operator == "==" ? UnitCompiler.EQ : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.operator == "!=" ? UnitCompiler.NE : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.operator == "<" ? UnitCompiler.LT : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.operator == ">=" ? UnitCompiler.GE : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.operator == ">" ? UnitCompiler.GT : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.operator == "<=" ? UnitCompiler.LE : // SUPPRESS CHECKSTYLE StringLiteralEquality
Integer.MIN_VALUE
);
if (opIdx == Integer.MIN_VALUE) break COMPARISON;
// Comparison with "null".
{
boolean lhsIsNull = this.getConstantValue(bo.lhs) == null;
boolean rhsIsNull = this.getConstantValue(bo.rhs) == null;
if (lhsIsNull || rhsIsNull) {
if (bo.operator != "==" && bo.operator != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileError(
"Operator \"" + bo.operator + "\" not allowed on operand \"null\"",
bo.getLocation()
);
}
if (!lhsIsNull) {
// x == null
IType lhsType = this.compileGetValue(bo.lhs);
if (UnitCompiler.rawTypeOf(lhsType).isPrimitive()) {
this.compileError(
"Cannot compare primitive type \"" + lhsType.toString() + "\" with \"null\"",
bo.getLocation()
);
}
} else
if (!rhsIsNull) {
// null == x
IType rhsType = this.compileGetValue(bo.rhs);
if (UnitCompiler.rawTypeOf(rhsType).isPrimitive()) {
this.compileError(
"Cannot compare \"null\" with primitive type \"" + rhsType.toString() + "\"",
bo.getLocation()
);
}
} else
{
// null == null
this.consT(bo, (Object) null);
}
switch (orientation == UnitCompiler.JUMP_IF_FALSE ? opIdx ^ 1 : opIdx) {
case EQ:
this.ifnull(bo, dst);
break;
case NE:
this.ifnonnull(bo, dst);
break;
default:
throw new AssertionError(opIdx);
}
return;
}
}
IType lhsType = this.compileGetValue(bo.lhs);
IType rhsType = this.getType(bo.rhs);
// 15.20.1 Numerical comparison.
if (
UnitCompiler.rawTypeOf(this.getUnboxedType(lhsType)).isPrimitiveNumeric()
&& UnitCompiler.rawTypeOf(this.getUnboxedType(rhsType)).isPrimitiveNumeric()
&& !(
(bo.operator == "==" || bo.operator == "!=") // SUPPRESS CHECKSTYLE StringLiteralEquality
&& !UnitCompiler.rawTypeOf(lhsType).isPrimitive()
&& !UnitCompiler.rawTypeOf(rhsType).isPrimitive()
)
) {
IClass
promotedType = this.binaryNumericPromotionType(bo, this.getUnboxedType(lhsType), this.getUnboxedType(rhsType));
this.numericPromotion(bo.lhs, this.convertToPrimitiveNumericType(bo.lhs, lhsType), promotedType);
this.compileGetValue(bo.rhs);
this.numericPromotion(bo.rhs, this.convertToPrimitiveNumericType(bo.rhs, rhsType), promotedType);
this.ifNumeric(bo, opIdx, dst, orientation);
return;
}
// JLS7 15.21.2 Boolean Equality Operators == and !=.
if (
(lhsType == IClass.BOOLEAN && this.getUnboxedType(rhsType) == IClass.BOOLEAN)
|| (rhsType == IClass.BOOLEAN && this.getUnboxedType(lhsType) == IClass.BOOLEAN)
) {
if (bo.operator != "==" && bo.operator != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileError(
"Operator \"" + bo.operator + "\" not allowed on boolean operands",
bo.getLocation()
);
}
IClassLoader icl = this.iClassLoader;
// Unbox LHS if necessary.
if (lhsType == icl.TYPE_java_lang_Boolean) {
this.unboxingConversion(bo, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN);
}
this.compileGetValue(bo.rhs);
// Unbox RHS if necessary.
if (rhsType == icl.TYPE_java_lang_Boolean) {
this.unboxingConversion(bo, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN);
}
this.if_icmpxx(bo, orientation == UnitCompiler.JUMP_IF_FALSE ? opIdx ^ 1 : opIdx, dst);
return;
}
// Reference comparison.
// Note: Comparison with "null" is already handled above.
if (!UnitCompiler.rawTypeOf(lhsType).isPrimitive() && !UnitCompiler.rawTypeOf(rhsType).isPrimitive()) {
if (bo.operator != "==" && bo.operator != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileError("Operator \"" + bo.operator + "\" not allowed on reference operands", bo.getLocation());
}
if (
!this.isCastReferenceConvertible(lhsType, rhsType)
|| !this.isCastReferenceConvertible(rhsType, lhsType)
) this.compileError("Incomparable types \"" + lhsType + "\" and \"" + rhsType + "\"", bo.getLocation());
this.compileGetValue(bo.rhs);
this.if_acmpxx(bo, orientation == UnitCompiler.JUMP_IF_FALSE ? opIdx ^ 1 : opIdx, dst);
return;
}
this.compileError("Cannot compare types \"" + lhsType + "\" and \"" + rhsType + "\"", bo.getLocation());
}
this.compileError("Boolean expression expected", bo.getLocation());
}
/**
* @param dst Where to jump
* @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE}
*/
private void
compileBoolean2(ParenthesizedExpression pe, CodeContext.Offset dst, boolean orientation) throws CompileException {
this.compileBoolean(pe.value, dst, orientation);
}
/**
* Generates code that determines the context of the {@link Rvalue} and puts it on the operand stack. Most
* expressions do not have a "context", but some do. E.g. for "x[y]", the context is "x, y". The bottom line is
* that for statements like "x[y] += 3" the context is only evaluated once.
*
* @return The number of operands that is pushed on the operand stack (0, 1 or 2)
*/
private int
compileContext(Rvalue rv) throws CompileException {
Integer result = (Integer) rv.accept(new RvalueVisitor() {
@Override @Nullable public Integer
visitLvalue(Lvalue lv) throws CompileException {
return (Integer) lv.accept(new LvalueVisitor() {
@Override public Integer visitAmbiguousName(AmbiguousName an) throws CompileException { return UnitCompiler.this.compileContext2(an); }
@Override public Integer visitArrayAccessExpression(ArrayAccessExpression aae) throws CompileException { return UnitCompiler.this.compileContext2(aae); }
@Override public Integer visitFieldAccess(FieldAccess fa) throws CompileException { return UnitCompiler.this.compileContext2(fa); }
@Override public Integer visitFieldAccessExpression(FieldAccessExpression fae) throws CompileException { return UnitCompiler.this.compileContext2(fae); }
@Override public Integer visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) throws CompileException { return UnitCompiler.this.compileContext2(scfae); }
@Override public Integer visitLocalVariableAccess(LocalVariableAccess lva) { return UnitCompiler.this.compileContext2(lva); }
@Override public Integer visitParenthesizedExpression(ParenthesizedExpression pe) throws CompileException { return UnitCompiler.this.compileContext2(pe); }
});
}
@Override public Integer visitArrayLength(ArrayLength al) throws CompileException { return UnitCompiler.this.compileContext2(al); }
@Override public Integer visitAssignment(Assignment a) { return UnitCompiler.this.compileContext2(a); }
@Override public Integer visitUnaryOperation(UnaryOperation uo) { return UnitCompiler.this.compileContext2(uo); }
@Override public Integer visitBinaryOperation(BinaryOperation bo) { return UnitCompiler.this.compileContext2(bo); }
@Override public Integer visitCast(Cast c) { return UnitCompiler.this.compileContext2(c); }
@Override public Integer visitClassLiteral(ClassLiteral cl) { return UnitCompiler.this.compileContext2(cl); }
@Override public Integer visitConditionalExpression(ConditionalExpression ce) { return UnitCompiler.this.compileContext2(ce); }
@Override public Integer visitCrement(Crement c) { return UnitCompiler.this.compileContext2(c); }
@Override public Integer visitInstanceof(Instanceof io) { return UnitCompiler.this.compileContext2(io); }
@Override public Integer visitMethodInvocation(MethodInvocation mi) { return UnitCompiler.this.compileContext2(mi); }
@Override public Integer visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { return UnitCompiler.this.compileContext2(smi); }
@Override public Integer visitIntegerLiteral(IntegerLiteral il) { return UnitCompiler.this.compileContext2(il); }
@Override public Integer visitFloatingPointLiteral(FloatingPointLiteral fpl) { return UnitCompiler.this.compileContext2(fpl); }
@Override public Integer visitBooleanLiteral(BooleanLiteral bl) { return UnitCompiler.this.compileContext2(bl); }
@Override public Integer visitCharacterLiteral(CharacterLiteral cl) { return UnitCompiler.this.compileContext2(cl); }
@Override public Integer visitStringLiteral(StringLiteral sl) { return UnitCompiler.this.compileContext2(sl); }
@Override public Integer visitNullLiteral(NullLiteral nl) { return UnitCompiler.this.compileContext2(nl); }
@Override public Integer visitSimpleConstant(SimpleConstant sl) { return UnitCompiler.this.compileContext2(sl); }
@Override public Integer visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { return UnitCompiler.this.compileContext2(naci); }
@Override public Integer visitNewArray(NewArray na) { return UnitCompiler.this.compileContext2(na); }
@Override public Integer visitNewInitializedArray(NewInitializedArray nia) { return UnitCompiler.this.compileContext2(nia); }
@Override public Integer visitNewClassInstance(NewClassInstance nci) { return UnitCompiler.this.compileContext2(nci); }
@Override public Integer visitParameterAccess(ParameterAccess pa) { return UnitCompiler.this.compileContext2(pa); }
@Override public Integer visitQualifiedThisReference(QualifiedThisReference qtr) { return UnitCompiler.this.compileContext2(qtr); }
@Override public Integer visitThisReference(ThisReference tr) { return UnitCompiler.this.compileContext2(tr); }
@Override public Integer visitLambdaExpression(LambdaExpression le) { return UnitCompiler.this.compileContext2(le); }
@Override public Integer visitMethodReference(MethodReference mr) { return UnitCompiler.this.compileContext2(mr); }
@Override public Integer visitInstanceCreationReference(ClassInstanceCreationReference cicr) { return UnitCompiler.this.compileContext2(cicr); }
@Override public Integer visitArrayCreationReference(ArrayCreationReference acr) { return UnitCompiler.this.compileContext2(acr); }
});
assert result != null;
return result;
}
@SuppressWarnings("static-method") private int
compileContext2(Rvalue rv) { return 0; }
private int
compileContext2(AmbiguousName an) throws CompileException {
return this.compileContext(this.toRvalueOrCompileException(this.reclassify(an)));
}
private int
compileContext2(FieldAccess fa) throws CompileException {
if (fa.field.isStatic()) {
Rvalue rv = fa.lhs.toRvalue();
if (rv != null) {
this.warning(
"CNSFA",
"Left-hand side of static field access should be a type, not an rvalue",
fa.lhs.getLocation()
);
// JLS7 15.11.1.3.1.1:
this.pop(fa.lhs, this.compileGetValue(rv));
}
return 0;
} else {
this.compileGetValue(this.toRvalueOrCompileException(fa.lhs));
return 1;
}
}
private int
compileContext2(ArrayLength al) throws CompileException {
if (!UnitCompiler.rawTypeOf(this.compileGetValue(al.lhs)).isArray()) {
this.compileError("Cannot determine length of non-array type", al.getLocation());
}
return 1;
}
/**
* Array access expression; see JLS7 15.13 / JLS8+ 15.10.3.
*/
private int
compileContext2(ArrayAccessExpression aae) throws CompileException {
IType lhsType = this.compileGetValue(aae.lhs);
if (!UnitCompiler.rawTypeOf(lhsType).isArray()) {
this.compileError(
"Subscript not allowed on non-array type \"" + lhsType.toString() + "\"",
aae.getLocation()
);
}
IType indexType = this.compileGetValue(aae.index);
if (this.unaryNumericPromotion(aae.index, indexType) != IClass.INT) {
this.compileError(
"Index expression of type \"" + indexType + "\" cannot be promoted to \"int\"",
aae.getLocation()
);
}
return 2;
}
private int
compileContext2(FieldAccessExpression fae) throws CompileException {
return this.compileContext(this.determineValue(fae));
}
private int
compileContext2(SuperclassFieldAccessExpression scfae) throws CompileException {
return this.compileContext(this.determineValue(scfae));
}
private int
compileContext2(ParenthesizedExpression pe) throws CompileException {
return this.compileContext(pe.value);
}
/**
* Generates code that determines the value of the {@link Rvalue} and puts it on the operand stack. This method
* relies on that the "context" of the {@link Rvalue} is on top of the operand stack (see {@link
* #compileContext(Rvalue)}).
*
* @return The type of the {@link Rvalue}
*/
private IType
compileGet(Rvalue rv) throws CompileException {
IType result = (IType) rv.accept(new RvalueVisitor() {
@Override @Nullable public IType
visitLvalue(Lvalue lv) throws CompileException {
return (IType) lv.accept(new LvalueVisitor() {
@Override public IType visitAmbiguousName(AmbiguousName an) throws CompileException { return UnitCompiler.this.compileGet2(an); }
@Override public IType visitArrayAccessExpression(ArrayAccessExpression aae) throws CompileException { return UnitCompiler.this.compileGet2(aae); }
@Override public IType visitFieldAccess(FieldAccess fa) throws CompileException { return UnitCompiler.this.compileGet2(fa); }
@Override public IType visitFieldAccessExpression(FieldAccessExpression fae) throws CompileException { return UnitCompiler.this.compileGet2(fae); }
@Override public IType visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) throws CompileException { return UnitCompiler.this.compileGet2(scfae); }
@Override public IType visitLocalVariableAccess(LocalVariableAccess lva) { return UnitCompiler.this.compileGet2(lva); }
@Override public IType visitParenthesizedExpression(ParenthesizedExpression pe) throws CompileException { return UnitCompiler.this.compileGet2(pe); }
});
}
@Override public IType visitArrayLength(ArrayLength al) { return UnitCompiler.this.compileGet2(al); }
@Override public IType visitAssignment(Assignment a) throws CompileException { return UnitCompiler.this.compileGet2(a); }
@Override public IType visitUnaryOperation(UnaryOperation uo) throws CompileException { return UnitCompiler.this.compileGet2(uo); }
@Override public IType visitBinaryOperation(BinaryOperation bo) throws CompileException { return UnitCompiler.this.compileGet2(bo); }
@Override public IType visitCast(Cast c) throws CompileException { return UnitCompiler.this.compileGet2(c); }
@Override public IType visitClassLiteral(ClassLiteral cl) throws CompileException { return UnitCompiler.this.compileGet2(cl); }
@Override public IType visitConditionalExpression(ConditionalExpression ce) throws CompileException { return UnitCompiler.this.compileGet2(ce); }
@Override public IType visitCrement(Crement c) throws CompileException { return UnitCompiler.this.compileGet2(c); }
@Override public IType visitInstanceof(Instanceof io) throws CompileException { return UnitCompiler.this.compileGet2(io); }
@Override public IType visitMethodInvocation(MethodInvocation mi) throws CompileException { return UnitCompiler.this.compileGet2(mi); }
@Override public IType visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) throws CompileException { return UnitCompiler.this.compileGet2(smi); }
@Override public IType visitIntegerLiteral(IntegerLiteral il) throws CompileException { return UnitCompiler.this.compileGet2(il); }
@Override public IType visitFloatingPointLiteral(FloatingPointLiteral fpl) throws CompileException { return UnitCompiler.this.compileGet2(fpl); }
@Override public IType visitBooleanLiteral(BooleanLiteral bl) throws CompileException { return UnitCompiler.this.compileGet2(bl); }
@Override public IType visitCharacterLiteral(CharacterLiteral cl) throws CompileException { return UnitCompiler.this.compileGet2(cl); }
@Override public IType visitStringLiteral(StringLiteral sl) throws CompileException { return UnitCompiler.this.compileGet2(sl); }
@Override public IType visitNullLiteral(NullLiteral nl) throws CompileException { return UnitCompiler.this.compileGet2(nl); }
@Override public IType visitSimpleConstant(SimpleConstant sl) throws CompileException { return UnitCompiler.this.compileGet2(sl); }
@Override public IType visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) throws CompileException { return UnitCompiler.this.compileGet2(naci); }
@Override public IType visitNewArray(NewArray na) throws CompileException { return UnitCompiler.this.compileGet2(na); }
@Override public IType visitNewInitializedArray(NewInitializedArray nia) throws CompileException { return UnitCompiler.this.compileGet2(nia); }
@Override public IType visitNewClassInstance(NewClassInstance nci) throws CompileException { return UnitCompiler.this.compileGet2(nci); }
@Override public IType visitParameterAccess(ParameterAccess pa) throws CompileException { return UnitCompiler.this.compileGet2(pa); }
@Override public IType visitQualifiedThisReference(QualifiedThisReference qtr) throws CompileException { return UnitCompiler.this.compileGet2(qtr); }
@Override public IType visitThisReference(ThisReference tr) throws CompileException { return UnitCompiler.this.compileGet2(tr); }
@Override public IType visitLambdaExpression(LambdaExpression le) throws CompileException { return UnitCompiler.this.compileGet2(le); }
@Override public IType visitMethodReference(MethodReference mr) throws CompileException { return UnitCompiler.this.compileGet2(mr); }
@Override public IType visitInstanceCreationReference(ClassInstanceCreationReference cicr) throws CompileException { return UnitCompiler.this.compileGet2(cicr); }
@Override public IType visitArrayCreationReference(ArrayCreationReference acr) throws CompileException { return UnitCompiler.this.compileGet2(acr); }
});
assert result != null;
return result;
}
private IClass
compileGet2(BooleanRvalue brv) throws CompileException {
CodeContext.Offset isTrue = this.getCodeContext().new BasicBlock();
isTrue.setStackMap(this.getCodeContext().currentInserter().getStackMap());
this.compileBoolean(brv, isTrue, UnitCompiler.JUMP_IF_TRUE);
this.consT(brv, 0);
CodeContext.Offset end = this.getCodeContext().new BasicBlock();
this.gotO(brv, end);
isTrue.setBasicBlock();
this.consT(brv, 1);
end.set();
return IClass.BOOLEAN;
}
private IType
compileGet2(AmbiguousName an) throws CompileException {
return this.compileGet(this.toRvalueOrCompileException(this.reclassify(an)));
}
private IType
compileGet2(LocalVariableAccess lva) { return this.load(lva, lva.localVariable); }
private IType
compileGet2(FieldAccess fa) throws CompileException {
this.checkAccessible(fa.field, fa.getEnclosingScope(), fa.getLocation());
this.getfield(fa, fa.field);
return fa.field.getType();
}
private IClass
compileGet2(ArrayLength al) {
this.arraylength(al);
return IClass.INT;
}
private IClass
compileGet2(ThisReference tr) throws CompileException {
final IClass currentIClass = this.getIClass(tr);
this.referenceThis(tr, currentIClass);
return currentIClass;
}
@SuppressWarnings("static-method") private IClass
compileGet2(LambdaExpression le) throws CompileException {
throw UnitCompiler.compileException(le, "Compilation of lambda expression NYI");
}
@SuppressWarnings("static-method") private IClass
compileGet2(MethodReference mr) throws CompileException {
throw UnitCompiler.compileException(mr, "Compilation of method reference NYI");
}
@SuppressWarnings("static-method") private IClass
compileGet2(ClassInstanceCreationReference cicr) throws CompileException {
throw UnitCompiler.compileException(cicr, "Compilation of class instance creation reference NYI");
}
@SuppressWarnings("static-method") private IClass
compileGet2(ArrayCreationReference acr) throws CompileException {
throw UnitCompiler.compileException(acr, "Compilation of array creation reference NYI");
}
private IType
compileGet2(QualifiedThisReference qtr) throws CompileException {
this.referenceThis(
qtr, // locatable
this.getDeclaringClass(qtr), // declaringClass
this.getDeclaringTypeBodyDeclaration(qtr), // declaringTypeBodyDeclaration
this.getTargetIType(qtr) // targetIClass
);
return this.getTargetIType(qtr);
}
private IClass
compileGet2(ClassLiteral cl) throws CompileException {
IType type = this.getType(cl.type);
if (type instanceof IParameterizedType) {
this.compileError("LHS of class literal must not be a parameterized type", cl.getLocation());
}
assert type instanceof IClass;
IClass iClass = (IClass) type;
if (iClass.isPrimitive()) {
// Primitive class literal.
IClass wrapperIClass = (
iClass == IClass.VOID ? this.iClassLoader.TYPE_java_lang_Void :
iClass == IClass.BYTE ? this.iClassLoader.TYPE_java_lang_Byte :
iClass == IClass.CHAR ? this.iClassLoader.TYPE_java_lang_Character :
iClass == IClass.DOUBLE ? this.iClassLoader.TYPE_java_lang_Double :
iClass == IClass.FLOAT ? this.iClassLoader.TYPE_java_lang_Float :
iClass == IClass.INT ? this.iClassLoader.TYPE_java_lang_Integer :
iClass == IClass.LONG ? this.iClassLoader.TYPE_java_lang_Long :
iClass == IClass.SHORT ? this.iClassLoader.TYPE_java_lang_Short :
iClass == IClass.BOOLEAN ? this.iClassLoader.TYPE_java_lang_Boolean :
null
);
assert wrapperIClass != null;
this.getfield(cl, wrapperIClass, "TYPE", this.iClassLoader.TYPE_java_lang_Class, true);
} else {
// Non-primitive class literal.
this.consT(cl, iClass);
}
return this.iClassLoader.TYPE_java_lang_Class;
}
private IType
compileGet2(Assignment a) throws CompileException {
if (a.operator == "=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
// Compile LHS context.
int lhsCs = this.compileContext(a.lhs);
// Convert RHS value to LHS type.
IType lhsType = this.getType(a.lhs);
IType rhsType = this.compileGetValue(a.rhs);
this.assignmentConversion(a, rhsType, lhsType, this.getConstantValue(a.rhs));
// Duplicate RHS value below LHS context.
this.dupxx(a, lhsCs);
// Set LHS.
this.compileSet(a.lhs);
return lhsType;
}
// Implement "|= ^= &= *= /= %= += -= <<= >>= >>>=".
int lhsCs = this.compileContext(a.lhs);
this.dupn(a, lhsCs);
IType lhsType = this.compileGet(a.lhs);
IType resultType = this.compileArithmeticBinaryOperation(
a, // locatable
lhsType, // lhsType
a.operator.substring( // operator
0,
a.operator.length() - 1
).intern(), /* <= IMPORTANT! */
a.rhs // rhs
);
// Convert the result to LHS type (JLS7 15.26.2).
if (
!this.tryIdentityConversion(resultType, lhsType)
&& !this.tryNarrowingPrimitiveConversion(a, resultType, lhsType)
) throw new InternalCompilerException(a.getLocation(), "SNO: \"" + a.operator + "\" reconversion failed");
this.dupx(a);
this.compileSet(a.lhs);
return lhsType;
}
private IType
compileGet2(ConditionalExpression ce) throws CompileException {
IType expressionType = this.getType2(ce);
IType mhsType = this.getType(ce.mhs);
IType rhsType = this.getType(ce.rhs);
{
Object lhsCv = this.getConstantValue(ce.lhs);
if (lhsCv instanceof Boolean) {
// LHS is a constant expression.
if (((Boolean) lhsCv).booleanValue()) {
this.compileGetValue(ce.mhs);
this.castConversion(ce.mhs, mhsType, expressionType, UnitCompiler.NOT_CONSTANT);
} else {
this.compileGetValue(ce.rhs);
this.castConversion(ce.rhs, rhsType, expressionType, UnitCompiler.NOT_CONSTANT);
}
return expressionType;
}
}
// Non-constant LHS.
{
final CodeContext.Offset toEnd = this.getCodeContext().new BasicBlock();
final CodeContext.Offset toRhs = this.getCodeContext().new BasicBlock();
StackMap sm = this.getCodeContext().currentInserter().getStackMap();
this.compileBoolean(ce.lhs, toRhs, UnitCompiler.JUMP_IF_FALSE);
this.compileGetValue(ce.mhs);
this.assignmentConversion(ce.mhs, mhsType, expressionType, UnitCompiler.NOT_CONSTANT);
this.gotO(ce, toEnd);
this.getCodeContext().currentInserter().setStackMap(sm);
toRhs.setBasicBlock();
this.compileGetValue(ce.rhs);
this.assignmentConversion(ce.mhs, rhsType, expressionType, UnitCompiler.NOT_CONSTANT);
toEnd.set();
}
return expressionType;
}
private IType
commonSupertype(IType t1, IType t2) throws CompileException {
if (UnitCompiler.isAssignableFrom(t2, t1)) return t2;
return this.commonSupertype2(t1, t2);
}
private IType
commonSupertype2(IType t1, IType t2) throws CompileException {
if (UnitCompiler.isAssignableFrom(t1, t2)) return t1;
{
IClass sc = UnitCompiler.rawTypeOf(t1).getSuperclass();
if (sc != null) {
IType result = this.commonSupertype2(sc, t2);
if (result != this.iClassLoader.TYPE_java_lang_Object) return result;
}
}
for (IType i : UnitCompiler.getInterfaces(t1)) {
IType result = this.commonSupertype2(i, t2);
if (result != this.iClassLoader.TYPE_java_lang_Object) return result;
}
return this.iClassLoader.TYPE_java_lang_Object;
}
@Nullable private static Byte
isByteConstant(@Nullable Object o) {
if (o instanceof Integer) {
int v = (Integer) o;
return v >= Byte.MIN_VALUE && v <= Byte.MAX_VALUE ? Byte.valueOf((byte) v) : null;
}
return null;
}
private IType
compileGet2(Crement c) throws CompileException {
// Optimized crement of "int" local variable.
LocalVariable lv = this.isIntLv(c);
if (lv != null) {
if (!c.pre) this.load(c, lv);
this.iinc(c, lv, c.operator);
if (c.pre) this.load(c, lv);
return IClass.INT;
}
// Compile operand context.
int operandCs = this.compileContext(c.operand);
// DUP operand context.
this.dupn(c, operandCs);
// Get operand value.
IType type = this.compileGet(c.operand);
// If postincrement: DUPX the operand value.
if (!c.pre) this.dupxx(c, operandCs);
{
// Apply "unary numeric promotion".
IClass promotedType = this.unaryNumericPromotion(c, type);
// Crement.
this.consT(c, promotedType, 1);
if (c.operator == "++") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.add(c);
} else
if (c.operator == "--") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.sub(c);
} else {
this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation());
}
this.reverseUnaryNumericPromotion(c, promotedType, type);
}
// If preincrement: DUPX the cremented operand value.
if (c.pre) this.dupxx(c, operandCs);
// Set operand.
this.compileSet(c.operand);
return type;
}
private IType
compileGet2(ArrayAccessExpression aae) throws CompileException {
IType lhsComponentType = this.getType(aae);
this.xaload(aae, lhsComponentType);
return lhsComponentType;
}
private IType
compileGet2(FieldAccessExpression fae) throws CompileException {
return this.compileGet(this.determineValue(fae));
}
private IType
compileGet2(SuperclassFieldAccessExpression scfae) throws CompileException {
return this.compileGet(this.determineValue(scfae));
}
private IClass
compileGet2(UnaryOperation uo) throws CompileException {
if (uo.operator == "!") { // SUPPRESS CHECKSTYLE StringLiteralEquality
return this.compileGet2((BooleanRvalue) uo);
}
if (uo.operator == "+") { // SUPPRESS CHECKSTYLE StringLiteralEquality
return this.unaryNumericPromotion(
uo,
this.convertToPrimitiveNumericType(uo, this.compileGetValue(uo.operand))
);
}
if (uo.operator == "-") { // SUPPRESS CHECKSTYLE StringLiteralEquality
{
Object ncv = this.getConstantValue2(uo);
if (ncv != UnitCompiler.NOT_CONSTANT) {
return this.unaryNumericPromotion(uo, this.consT(uo, ncv));
}
}
IClass promotedType = this.unaryNumericPromotion(
uo,
this.convertToPrimitiveNumericType(uo, this.compileGetValue(uo.operand))
);
this.neg(uo, promotedType);
return promotedType;
}
if (uo.operator == "~") { // SUPPRESS CHECKSTYLE StringLiteralEquality
IType operandType = this.compileGetValue(uo.operand);
IClass promotedType = this.unaryNumericPromotion(uo, operandType);
if (promotedType == IClass.INT) {
this.consT(uo, -1);
this.xor(uo, Opcode.IXOR);
return IClass.INT;
}
if (promotedType == IClass.LONG) {
this.consT(uo, -1L);
this.xor(uo, Opcode.LXOR);
return IClass.LONG;
}
this.compileError("Operator \"~\" not applicable to type \"" + promotedType + "\"", uo.getLocation());
}
this.compileError("Unexpected operator \"" + uo.operator + "\"", uo.getLocation());
return this.iClassLoader.TYPE_java_lang_Object;
}
private IClass
compileGet2(Instanceof io) throws CompileException {
IType lhsType = this.compileGetValue(io.lhs);
IType rhsType = this.getType(io.rhs);
if (rhsType instanceof IParameterizedType) {
this.compileError("Cannot check against parameterized type", io.getLocation());
return IClass.BOOLEAN;
}
if (
UnitCompiler.isInterface(lhsType) || UnitCompiler.isInterface(rhsType)
// We cannot precompute the result from type information as the value might be null, but we should detect
// when the instanceof is statically impossible.
|| UnitCompiler.isAssignableFrom(lhsType, rhsType)
|| UnitCompiler.isAssignableFrom(rhsType, lhsType)
) {
this.instanceoF(io, rhsType);
} else {
this.compileError("\"" + lhsType + "\" can never be an instance of \"" + rhsType + "\"", io.getLocation());
}
return IClass.BOOLEAN;
}
@Nullable private static IType
getComponentType(IType expressionType) { return UnitCompiler.rawTypeOf(expressionType).getComponentType(); }
private static boolean
isPrimitive(IType type) { return UnitCompiler.rawTypeOf(type).isPrimitive(); }
@Nullable private static IType
getSuperclass(IType type) throws CompileException { return UnitCompiler.rawTypeOf(type).getSuperclass(); }
private static boolean
isInterface(IType type) { return UnitCompiler.rawTypeOf(type).isInterface(); }
private static IType[]
getInterfaces(IType t1) throws CompileException {
IClass[] rawInterfaces = UnitCompiler.rawTypeOf(t1).getInterfaces();
IType[] result = new IType[rawInterfaces.length];
for (int i = 0; i < result.length; i++) result[i] = rawInterfaces[i];
return result;
}
private static boolean
isArray(IType type) { return UnitCompiler.rawTypeOf(type).isArray(); }
private static boolean
isAssignableFrom(IType targetType, IType sourceType) throws CompileException {
return UnitCompiler.rawTypeOf(targetType).isAssignableFrom(UnitCompiler.rawTypeOf(sourceType));
}
private IType
compileGet2(BinaryOperation bo) throws CompileException {
if (
bo.operator == "||" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.operator == "&&" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.operator == "==" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.operator == "!=" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.operator == "<" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.operator == ">" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.operator == "<=" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.operator == ">=" // SUPPRESS CHECKSTYLE StringLiteralEquality
) {
// Eventually calls "compileBoolean()".
return this.compileGet2((BooleanRvalue) bo);
}
// Implements "| ^ & * / % + - << >> >>>".
return this.compileArithmeticOperation(
bo, // locatable
null, // type
bo.unrollLeftAssociation(), // operands
bo.operator // operator
);
}
private IType
compileGet2(Cast c) throws CompileException {
// JLS7 5.5 Casting Conversion.
IType tt = this.getType(c.targetType);
IType vt = this.compileGetValue(c.value);
if (this.tryCastConversion(c, vt, tt, this.getConstantValue2(c.value))) return tt;
// JAVAC obviously also permits 'boxing conversion followed by widening reference conversion' and 'unboxing
// conversion followed by widening primitive conversion', although these are not described in JLS7 5.5. For the
// sake of compatibility, we implement them.
// See also http://jira.codehaus.org/browse/JANINO-153
{
IClass boxedType = this.isBoxingConvertible(vt);
if (boxedType != null && this.isWideningReferenceConvertible(boxedType, tt)) {
this.boxingConversion(c, vt, boxedType);
return tt;
}
IClass unboxedType = this.isUnboxingConvertible(vt);
if (unboxedType != null && this.isWideningPrimitiveConvertible(unboxedType, tt)) {
this.unboxingConversion(c, vt, unboxedType);
this.tryWideningPrimitiveConversion(c, unboxedType, tt);
return tt;
}
}
this.compileError("Cannot cast \"" + vt + "\" to \"" + tt + "\"", c.getLocation());
return tt;
}
private IType
compileGet2(ParenthesizedExpression pe) throws CompileException {
return this.compileGet(pe.value);
}
private IClass
compileGet2(MethodInvocation mi) throws CompileException {
IClass.IMethod iMethod = this.findIMethod(mi);
// Compute the objectref for an instance method.
Atom ot = mi.target;
if (ot == null) {
// JLS7 6.5.7.1, 15.12.4.1.1.1
TypeBodyDeclaration scopeTbd;
AbstractTypeDeclaration scopeTypeDeclaration;
{
Scope s;
for (
s = mi.getEnclosingScope();
!(s instanceof TypeBodyDeclaration);
s = s.getEnclosingScope()
);
scopeTbd = (TypeBodyDeclaration) s;
if (!(s instanceof AbstractTypeDeclaration)) s = s.getEnclosingScope();
scopeTypeDeclaration = (AbstractTypeDeclaration) s;
}
if (iMethod.isStatic()) {
this.warning(
"IASM",
"Implicit access to static method \"" + iMethod.toString() + "\"",
mi.getLocation()
);
// JLS7 15.12.4.1.1.1.1
;
} else {
this.warning(
"IANSM",
"Implicit access to non-static method \"" + iMethod.toString() + "\"",
mi.getLocation()
);
// JLS7 15.12.4.1.1.1.2
if (UnitCompiler.isStaticContext(scopeTbd)) {
this.compileError(
"Instance method \"" + iMethod.toString() + "\" cannot be invoked in static context",
mi.getLocation()
);
}
this.referenceThis(
mi, // locatable
scopeTypeDeclaration, // declaringType
scopeTbd, // declaringTypeBodyDeclaration
iMethod.getDeclaringIClass() // targetIClass
);
}
} else {
// 6.5.7.2
if (this.isType(ot)) {
// The target is a type; thus the method must be static.
// JLS9 15.12.4.1.2:
this.getType(this.toTypeOrCompileException(ot));
if (!iMethod.isStatic()) {
this.compileError(
"Instance method \"" + mi.methodName + "\" cannot be invoked in static context",
mi.getLocation()
);
}
} else
{
// The target is an rvalue.
Rvalue rot = this.toRvalueOrCompileException(ot);
if (iMethod.isStatic()) {
// JLS9 15.12.4.1.3.1 and .4.1:
if (!UnitCompiler.mayHaveSideEffects(rot)) {
// The rvalue is guaranteed to have no side effects, so we can save the code to evaluate it
// and then discard the result.
;
} else {
// Evaluate the target expression and then discard the result.
this.pop(ot, this.compileGetValue(rot));
}
} else {
// JLS9 15.12.4.1.3.2 and .4.2
this.compileGetValue(rot);
if (this.getCodeContext().peekNullOperand()) {
this.compileError("Method invocation target is always null");
this.getCodeContext().popOperand();
this.getCodeContext().pushObjectOperand(iMethod.getDeclaringIClass().getDescriptor());
}
}
}
}
// Evaluate method parameters (JLS7 15.12.4.2).
// If this method is vararg, rewritten all args starting from lastParamIndex to the end as if they were elements
// of an array.
IClass[] parameterTypes = iMethod.getParameterTypes();
Rvalue[] adjustedArgs = null;
final int actualSize = mi.arguments.length;
if (iMethod.isVarargs() && iMethod.argsNeedAdjust()) {
adjustedArgs = new Rvalue[parameterTypes.length];
Rvalue[] lastArgs = new Rvalue[actualSize - parameterTypes.length + 1];
final Location loc = mi.getLocation();
if (lastArgs.length > 0) {
for (int i = 0, j = parameterTypes.length - 1; i < lastArgs.length; ++i, ++j) {
lastArgs[i] = mi.arguments[j];
}
}
for (int i = parameterTypes.length - 2; i >= 0; --i) {
adjustedArgs[i] = mi.arguments[i];
}
adjustedArgs[adjustedArgs.length - 1] = new NewInitializedArray(
loc, // location
parameterTypes[parameterTypes.length - 1], // arrayIClass
new ArrayInitializer(loc, lastArgs) // arrayInitializer
);
} else {
adjustedArgs = mi.arguments;
}
for (int i = 0; i < adjustedArgs.length; ++i) {
this.assignmentConversion(
mi, // location
this.compileGetValue(adjustedArgs[i]), // sourceType
parameterTypes[i], // targetType
this.getConstantValue(adjustedArgs[i]) // constantValue
);
}
// Invoke!
this.checkAccessible(iMethod, mi.getEnclosingScope(), mi.getLocation());
if (!iMethod.getDeclaringIClass().isInterface() && !iMethod.isStatic() && iMethod.getAccess() == Access.PRIVATE) {
// In order to make a non-static private method invocable for enclosing types, enclosed types and types
// enclosed by the same type, "compile(FunctionDeclarator)" modifies it on-the-fly as follows:
// + Access is changed from PRIVATE to PACKAGE
// + The name is appended with "$"
// + It is made static
// + A parameter of type "declaring class" is prepended to the signature
// Hence, the invocation of such a method must be modified accordingly.
this.invoke(
mi, // locatable
Opcode.INVOKESTATIC, // opcode
iMethod.getDeclaringIClass(), // declaringIClass
iMethod.getName() + '$', // methodName
iMethod.getDescriptor().prependParameter( // methodMd
iMethod.getDeclaringIClass().getDescriptor()
),
false // useInterfaceMethodref
);
} else
{
this.invokeMethod(mi, iMethod);
}
return iMethod.getReturnType();
}
private static boolean
isStaticContext(TypeBodyDeclaration tbd) {
if (tbd instanceof FieldDeclaration) return ((FieldDeclaration) tbd).isStatic() || ((FieldDeclaration) tbd).getDeclaringType() instanceof InterfaceDeclaration;
if (tbd instanceof MethodDeclarator) return ((MethodDeclarator) tbd).isStatic();
if (tbd instanceof Initializer) return ((Initializer) tbd).isStatic();
if (tbd instanceof MemberClassDeclaration) return ((MemberClassDeclaration) tbd).isStatic();
return false;
}
private static boolean
mayHaveSideEffects(ArrayInitializerOrRvalue... arrayInitializersOrRvalues) {
for (ArrayInitializerOrRvalue aiorv : arrayInitializersOrRvalues) {
if (UnitCompiler.mayHaveSideEffects(aiorv)) return true;
}
return false;
}
private static boolean
mayHaveSideEffects(ArrayInitializerOrRvalue arrayInitializerOrRvalue) {
Boolean result = (Boolean) arrayInitializerOrRvalue.accept(UnitCompiler.MAY_HAVE_SIDE_EFFECTS_VISITOR);
assert result != null;
return result;
}
private static final ArrayInitializerOrRvalueVisitor
MAY_HAVE_SIDE_EFFECTS_VISITOR = new ArrayInitializerOrRvalueVisitor() {
@Override @Nullable public Boolean visitRvalue(Rvalue rvalue) { return (Boolean) rvalue.accept(this.rvalueVisitor); }
@Override @Nullable public Boolean visitArrayInitializer(ArrayInitializer ai) { return UnitCompiler.mayHaveSideEffects(ai.values); }
final LvalueVisitor
lvalueVisitor = new LvalueVisitor() {
@Override @Nullable public Boolean visitAmbiguousName(AmbiguousName an) { return false; }
@Override @Nullable public Boolean visitArrayAccessExpression(ArrayAccessExpression aae) { return UnitCompiler.mayHaveSideEffects(aae.lhs, aae.index); }
@Override @Nullable public Boolean visitFieldAccess(FieldAccess fa) { return false; }
@Override @Nullable public Boolean visitFieldAccessExpression(FieldAccessExpression fae) { return false; }
@Override @Nullable public Boolean visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { return false; }
@Override @Nullable public Boolean visitLocalVariableAccess(LocalVariableAccess lva) { return false; }
@Override @Nullable public Boolean visitParenthesizedExpression(ParenthesizedExpression pe) { return UnitCompiler.mayHaveSideEffects(pe.value); }
};
final RvalueVisitor
rvalueVisitor = new RvalueVisitor() {
@SuppressWarnings("unqualified-field-access") @Override @Nullable public Boolean
visitLvalue(Lvalue lv) { return (Boolean) lv.accept(lvalueVisitor); }
@Override @Nullable public Boolean visitArrayLength(ArrayLength al) { return UnitCompiler.mayHaveSideEffects(al.lhs); }
@Override @Nullable public Boolean visitAssignment(Assignment a) { return true; }
@Override @Nullable public Boolean visitUnaryOperation(UnaryOperation uo) { return UnitCompiler.mayHaveSideEffects(uo.operand); }
@Override @Nullable public Boolean visitBinaryOperation(BinaryOperation bo) { return UnitCompiler.mayHaveSideEffects(bo.lhs, bo.rhs); }
@Override @Nullable public Boolean visitCast(Cast c) { return UnitCompiler.mayHaveSideEffects(c.value); }
@Override @Nullable public Boolean visitClassLiteral(ClassLiteral cl) { return false; }
@Override @Nullable public Boolean visitConditionalExpression(ConditionalExpression ce) { return UnitCompiler.mayHaveSideEffects(ce.lhs, ce.mhs, ce.rhs); }
@Override @Nullable public Boolean visitCrement(Crement c) { return true; }
@Override @Nullable public Boolean visitInstanceof(Instanceof io) { return false; }
@Override @Nullable public Boolean visitMethodInvocation(MethodInvocation mi) { return true; }
@Override @Nullable public Boolean visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { return true; }
@Override @Nullable public Boolean visitIntegerLiteral(IntegerLiteral il) { return false; }
@Override @Nullable public Boolean visitFloatingPointLiteral(FloatingPointLiteral fpl) { return false; }
@Override @Nullable public Boolean visitBooleanLiteral(BooleanLiteral bl) { return false; }
@Override @Nullable public Boolean visitCharacterLiteral(CharacterLiteral cl) { return false; }
@Override @Nullable public Boolean visitStringLiteral(StringLiteral sl) { return false; }
@Override @Nullable public Boolean visitNullLiteral(NullLiteral nl) { return false; }
@Override @Nullable public Boolean visitSimpleConstant(SimpleConstant sl) { return false; }
@Override @Nullable public Boolean visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { return true; }
@Override @Nullable public Boolean visitNewArray(NewArray na) { return UnitCompiler.mayHaveSideEffects(na.dimExprs); }
@Override @Nullable public Boolean visitNewInitializedArray(NewInitializedArray nia) { return UnitCompiler.mayHaveSideEffects(nia.arrayInitializer); }
@Override @Nullable public Boolean visitNewClassInstance(NewClassInstance nci) { return true; }
@Override @Nullable public Boolean visitParameterAccess(ParameterAccess pa) { return false; }
@Override @Nullable public Boolean visitQualifiedThisReference(QualifiedThisReference qtr) { return false; }
@Override @Nullable public Boolean visitThisReference(ThisReference tr) { return false; }
@Override @Nullable public Boolean visitLambdaExpression(LambdaExpression le) { return true; }
@Override @Nullable public Boolean visitMethodReference(MethodReference mr) { return true; }
@Override @Nullable public Boolean visitInstanceCreationReference(ClassInstanceCreationReference cicr) { return true; }
@Override @Nullable public Boolean visitArrayCreationReference(ArrayCreationReference acr) { return false; }
};
};
private IClass
compileGet2(SuperclassMethodInvocation scmi) throws CompileException {
final IClass.IMethod iMethod = this.findIMethod(scmi);
Scope s;
for (
s = scmi.getEnclosingScope();
s instanceof Statement || s instanceof CatchClause;
s = s.getEnclosingScope()
);
FunctionDeclarator fd = s instanceof FunctionDeclarator ? (FunctionDeclarator) s : null;
if (fd == null) {
this.compileError("Cannot invoke superclass method in non-method scope", scmi.getLocation());
return IClass.INT;
}
if (fd instanceof MethodDeclarator && ((MethodDeclarator) fd).isStatic()) {
this.compileError("Cannot invoke superclass method in static context", scmi.getLocation());
}
this.load(scmi, this.resolve(fd.getDeclaringType()), 0);
// Evaluate method parameters.
// TODO: adjust args
IClass[] parameterTypes = iMethod.getParameterTypes();
for (int i = 0; i < scmi.arguments.length; ++i) {
this.assignmentConversion(
scmi, // locatable
this.compileGetValue(scmi.arguments[i]), // sourceType
parameterTypes[i], // targetType
this.getConstantValue(scmi.arguments[i]) // constantValue
);
}
// Invoke!
this.invoke(
scmi, // locatable
Opcode.INVOKESPECIAL, // opcode
iMethod.getDeclaringIClass(), // declaringIClass
iMethod.getName(), // methodName
iMethod.getDescriptor(), // methodMd
false // useInterfaceMethodref
);
return iMethod.getReturnType();
}
private IType
compileGet2(NewClassInstance nci) throws CompileException {
IType iType;
if (nci.iType != null) {
iType = nci.iType;
} else {
assert nci.type != null;
iType = (nci.iType = this.getType(nci.type));
}
IClass rawType = UnitCompiler.rawTypeOf(iType);
if (rawType.isInterface()) {
this.compileError("Cannot instantiate \"" + iType + "\"", nci.getLocation());
}
this.checkAccessible(rawType, nci.getEnclosingScope(), nci.getLocation());
if (rawType.isAbstract()) {
this.compileError("Cannot instantiate abstract \"" + iType + "\"", nci.getLocation());
}
// Determine the enclosing instance for the new object.
Rvalue enclosingInstance;
if (nci.qualification != null) {
if (rawType.getOuterIClass() == null) {
this.compileError("Static member class cannot be instantiated with qualified NEW");
}
// Enclosing instance defined by qualification (JLS7 15.9.2.BL1.B3.B2).
enclosingInstance = nci.qualification;
} else {
Scope s = nci.getEnclosingScope();
for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope());
TypeBodyDeclaration enclosingTypeBodyDeclaration = (TypeBodyDeclaration) s;
TypeDeclaration enclosingTypeDeclaration = (TypeDeclaration) s.getEnclosingScope();
if (
!(enclosingTypeDeclaration instanceof AbstractClassDeclaration)
|| (
enclosingTypeBodyDeclaration instanceof MemberClassDeclaration
&& ((MemberClassDeclaration) enclosingTypeBodyDeclaration).isStatic()
)
|| (
enclosingTypeBodyDeclaration instanceof PackageMemberClassDeclaration
&& ((PackageMemberClassDeclaration) enclosingTypeBodyDeclaration).isStatic()
)
) {
// No enclosing instance in
// + interface method declaration or
// + static type body declaration (here: method or initializer or field declarator)
// context (JLS7 15.9.2.BL1.B3.B1.B1).
if (rawType.getOuterIClass() != null) {
this.compileError((
"Instantiation of \""
+ (nci.type != null ? nci.type.toString() : String.valueOf(nci.iType))
+ "\" requires an enclosing instance"
), nci.getLocation());
}
enclosingInstance = null;
} else
{
// Determine the type of the enclosing instance for the new object.
IClass outerIClass = rawType.getDeclaringIClass();
if (outerIClass == null) {
// No enclosing instance needed for a top-level class object.
enclosingInstance = null;
} else {
// Find an appropriate enclosing instance for the new inner class object among the enclosing
// instances of the current object (JLS7 15.9.2.BL1.B3.B1.B2).
enclosingInstance = new QualifiedThisReference(
nci.getLocation(), // location
new SimpleType( // qualification
nci.getLocation(),
outerIClass
)
);
enclosingInstance.setEnclosingScope(nci.getEnclosingScope());
}
}
}
this.neW(nci, iType);
this.dup(nci);
this.invokeConstructor(
nci, // l
nci.getEnclosingScope(), // scope
enclosingInstance, // enclosingInstance
iType, // targetClass
nci.arguments // arguments
);
this.getCodeContext().popUninitializedVariableOperand();
this.getCodeContext().pushObjectOperand(rawType.getDescriptor());
return iType;
}
private IClass
compileGet2(NewAnonymousClassInstance naci) throws CompileException {
AnonymousClassDeclaration acd = naci.anonymousClassDeclaration;
// Find constructors of superclass.
IClass sc = this.resolve(acd).getSuperclass();
assert sc != null;
IClass.IConstructor[] superclassIConstructors = sc.getDeclaredIConstructors();
if (superclassIConstructors.length == 0) {
throw new InternalCompilerException(naci.getLocation(), "SNO: Superclass has no constructors");
}
// Determine the most specific constructor of the superclass.
IClass.IConstructor superclassIConstructor = (IClass.IConstructor) this.findMostSpecificIInvocable(
naci, // locatable
superclassIConstructors, // iInvocables
naci.arguments, // arguments
acd // contextScope
);
Location loc = naci.getLocation();
Rvalue qualification = naci.qualification;
// Determine the formal parameters of the anonymous constructor.
IClass[] scpts = superclassIConstructor.getParameterTypes();
FormalParameters parameters;
{
List l = new ArrayList<>();
// Pass the enclosing instance of the base class as parameter #1.
if (qualification != null) l.add(new FormalParameter(
loc, // location
UnitCompiler.accessModifiers(loc, "final"), // modifiers
new SimpleType(loc, this.getType(qualification)), // type
"this$base" // name
));
for (int i = 0; i < scpts.length; ++i) l.add(new FormalParameter(
loc, // location
UnitCompiler.accessModifiers(loc, "final"), // modifiers
new SimpleType(loc, scpts[i]), // type
"p" + i // name
));
parameters = new FormalParameters(
loc,
(FormalParameter[]) l.toArray(new FormalParameter[l.size()]),
false
);
}
// Determine the declared exceptions of the anonymous constructor.
Type[] thrownExceptions;
{
IClass[] tes = superclassIConstructor.getThrownExceptions();
thrownExceptions = new Type[tes.length];
for (int i = 0; i < tes.length; ++i) thrownExceptions[i] = new SimpleType(loc, tes[i]);
}
// The anonymous constructor merely invokes the constructor of its superclass.
int j = 0;
Rvalue qualificationAccess;
if (qualification == null) {
qualificationAccess = null;
} else
{
qualificationAccess = new ParameterAccess(loc, parameters.parameters[j++]);
}
Rvalue[] parameterAccesses = new Rvalue[scpts.length];
for (int i = 0; i < scpts.length; ++i) {
parameterAccesses[i] = new ParameterAccess(loc, parameters.parameters[j++]);
}
// Generate the anonymous constructor for the anonymous class (JLS7 15.9.5.1).
acd.addConstructor(new ConstructorDeclarator(
loc, // location
null, // docComment
new Modifier[0], // modifiers
parameters, // parameters
thrownExceptions, // thrownExceptions
new SuperConstructorInvocation( // constructorInvocation
loc, // location
qualificationAccess, // qualification
parameterAccesses // arguments
),
Collections.emptyList() // statements
));
// Compile the anonymous class.
final IClass iClass = this.resolve(naci.anonymousClassDeclaration);
try {
this.compile(acd);
// Instantiate the anonymous class.
IClass anonymousIClass = this.resolve(acd);
this.neW(naci, anonymousIClass);
// TODO: adjust argument (for varargs case ?)
// Invoke the anonymous constructor.
this.dup(naci);
Rvalue[] arguments2;
if (qualification == null) {
arguments2 = naci.arguments;
} else {
arguments2 = new Rvalue[naci.arguments.length + 1];
arguments2[0] = qualification;
System.arraycopy(naci.arguments, 0, arguments2, 1, naci.arguments.length);
}
// Adjust if needed.
// TODO: Not doing this now because we don't need vararg-anonymous class (yet).
// Rvalue[] adjustedArgs = null;
// final int paramsTypeLength = iConstructor.getParameterTypes().length;
// if (argsNeedAdjusting[0]) {
// adjustedArgs = new Rvalue[paramsTypeLength];
// }
// Notice: The enclosing instance of the anonymous class is "this", not the qualification of the
// NewAnonymousClassInstance.
Scope s;
for (s = naci.getEnclosingScope(); !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope());
ThisReference oei;
if (UnitCompiler.isStaticContext((TypeBodyDeclaration) s)) {
oei = null;
} else
{
oei = new ThisReference(loc);
oei.setEnclosingScope(naci.getEnclosingScope());
}
this.invokeConstructor(
naci, // locatable
naci.getEnclosingScope(), // scope
oei, // enclosingInstance
iClass, // targetClass
arguments2 // arguments
);
this.getCodeContext().popUninitializedVariableOperand();
this.getCodeContext().pushObjectOperand(iClass.getDescriptor());
} finally {
// Remove the synthetic constructor that was temporarily added. This is necessary because this NACI
// expression (and all other expressions) are sometimes compiled more than once (see "fakeCompile()"), and
// we'd end up with TWO synthetic constructors. See JANINO-143.
acd.constructors.remove(acd.constructors.size() - 1);
}
return iClass;
}
private IType
compileGet2(ParameterAccess pa) throws CompileException {
LocalVariable lv = this.getLocalVariable(pa.formalParameter);
this.load(pa, lv);
return lv.type;
}
private IClass
compileGet2(NewArray na) throws CompileException {
for (Rvalue dimExpr : na.dimExprs) {
IType dimType = this.compileGetValue(dimExpr);
if (dimType != IClass.INT && this.unaryNumericPromotion(
na, // locatable
dimType // type
) != IClass.INT) this.compileError("Invalid array size expression type", na.getLocation());
}
return this.newArray(
na, // locatable
na.dimExprs.length, // dimExprCount
na.dims, // dims
this.getType(na.type) // componentType
);
}
private IType
compileGet2(NewInitializedArray nia) throws CompileException {
IType at = this.getType2(nia);
this.compileGetValue(nia.arrayInitializer, at);
return at;
}
private void
compileGetValue(ArrayInitializer ai, IType arrayType) throws CompileException {
if (!(arrayType instanceof IClass) || !((IClass) arrayType).isArray()) {
this.compileError("Array initializer not allowed for non-array type \"" + arrayType.toString() + "\"");
}
IClass componentType = ((IClass) arrayType).getComponentType();
assert componentType != null;
this.consT(ai, Integer.valueOf(ai.values.length));
this.newArray(
ai, // locatable
1, // dimExprCount
0, // dims
componentType // componentType
);
for (int index = 0; index < ai.values.length; index++) {
ArrayInitializerOrRvalue componentInitializer = ai.values[index];
this.dup(componentInitializer);
this.consT(ai, index);
this.compile(componentInitializer, componentType);
this.arraystore(componentInitializer, componentType);
}
}
private IClass
compileGet2(Literal l) throws CompileException {
return this.consT(l, this.getConstantValue(l));
}
private IClass
compileGet2(SimpleConstant sl) throws CompileException {
return this.consT(sl, sl.value);
}
/**
* Convenience function that calls {@link #compileContext(Rvalue)} and {@link #compileGet(Rvalue)}.
*
* @return The type of the Rvalue
*/
private IType
compileGetValue(Rvalue rv) throws CompileException {
Object cv = this.getConstantValue(rv);
if (cv != UnitCompiler.NOT_CONSTANT) {
this.fakeCompile(rv); // To check that, e.g., "a" compiles in "true || a".
this.consT(rv, cv);
return this.getType(rv);
}
try {
this.compileContext(rv);
return this.compileGet(rv);
} catch (RuntimeException re) {
throw new InternalCompilerException(rv.getLocation(), "Compiling \"" + rv + "\"", re);
}
}
// -------------------- Rvalue.getConstantValue() -----------------
/**
* Special return value for the {@link #getConstantValue(Java.Rvalue)} method family indicating that the given
* {@link Java.Rvalue} does not evaluate to a constant value.
*/
public static final Object NOT_CONSTANT = IClass.NOT_CONSTANT;
/**
* Attempts to evaluate as a constant expression. The result is one of the following: {@link Boolean}, {@link
* Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double}, {@link Character}, {@link
* String}, {@code null} (representing the {@code null} literal.
*
* This method cannot be STATIC, because the constant value may refer to a constant declaration in this
* compilation unit.
*
*
* @return {@link #NOT_CONSTANT} iff the rvalue is not a constant value
*/
@Nullable public final Object
getConstantValue(ArrayInitializerOrRvalue rv) throws CompileException {
return rv.accept(new ArrayInitializerOrRvalueVisitor() {
@Override @Nullable public Object
visitArrayInitializer(ArrayInitializer ai) { return UnitCompiler.NOT_CONSTANT; }
@Override @Nullable public Object
visitRvalue(Rvalue rvalue) throws CompileException { return UnitCompiler.this.getConstantValue(rvalue); }
});
}
/**
* Attempts to evaluate as a constant expression. The result is one of the following: {@link Boolean}, {@link
* Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double}, {@link Character}, {@link
* String}, {@code null} (representing the {@code null} literal.
*
* This method cannot be STATIC, because the constant value may refer to a constant declaration in this
* compilation unit.
*
*
* @return {@link #NOT_CONSTANT} iff the rvalue is not a constant value
*/
@Nullable public final Object
getConstantValue(Rvalue rv) throws CompileException {
if (rv.constantValue != Rvalue.CONSTANT_VALUE_UNKNOWN) return rv.constantValue;
return (rv.constantValue = rv.accept(new RvalueVisitor() {
@Override @Nullable public Object
visitLvalue(Lvalue lv) throws CompileException {
return lv.accept(new LvalueVisitor() {
@Override @Nullable public Object visitAmbiguousName(AmbiguousName an) throws CompileException { return UnitCompiler.this.getConstantValue2(an); }
@Override @Nullable public Object visitArrayAccessExpression(ArrayAccessExpression aae) { return UnitCompiler.this.getConstantValue2(aae); }
@Override @Nullable public Object visitFieldAccess(FieldAccess fa) throws CompileException { return UnitCompiler.this.getConstantValue2(fa); }
@Override @Nullable public Object visitFieldAccessExpression(FieldAccessExpression fae) { return UnitCompiler.this.getConstantValue2(fae); }
@Override @Nullable public Object visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { return UnitCompiler.this.getConstantValue2(scfae); }
@Override @Nullable public Object visitLocalVariableAccess(LocalVariableAccess lva) throws CompileException { return UnitCompiler.this.getConstantValue2(lva); }
@Override @Nullable public Object visitParenthesizedExpression(ParenthesizedExpression pe) throws CompileException { return UnitCompiler.this.getConstantValue2(pe); }
});
}
@Override @Nullable public Object visitArrayLength(ArrayLength al) { return UnitCompiler.this.getConstantValue2(al); }
@Override @Nullable public Object visitAssignment(Assignment a) { return UnitCompiler.this.getConstantValue2(a); }
@Override @Nullable public Object visitUnaryOperation(UnaryOperation uo) throws CompileException { return UnitCompiler.this.getConstantValue2(uo); }
@Override @Nullable public Object visitBinaryOperation(BinaryOperation bo) throws CompileException { return UnitCompiler.this.getConstantValue2(bo); }
@Override @Nullable public Object visitCast(Cast c) throws CompileException { return UnitCompiler.this.getConstantValue2(c); }
@Override @Nullable public Object visitClassLiteral(ClassLiteral cl) { return UnitCompiler.this.getConstantValue2(cl); }
@Override @Nullable public Object visitConditionalExpression(ConditionalExpression ce) throws CompileException { return UnitCompiler.this.getConstantValue2(ce); }
@Override @Nullable public Object visitCrement(Crement c) { return UnitCompiler.this.getConstantValue2(c); }
@Override @Nullable public Object visitInstanceof(Instanceof io) { return UnitCompiler.this.getConstantValue2(io); }
@Override @Nullable public Object visitMethodInvocation(MethodInvocation mi) { return UnitCompiler.this.getConstantValue2(mi); }
@Override @Nullable public Object visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { return UnitCompiler.this.getConstantValue2(smi); }
@Override @Nullable public Object visitIntegerLiteral(IntegerLiteral il) throws CompileException { return UnitCompiler.this.getConstantValue2(il); }
@Override @Nullable public Object visitFloatingPointLiteral(FloatingPointLiteral fpl) throws CompileException { return UnitCompiler.this.getConstantValue2(fpl); }
@Override @Nullable public Object visitBooleanLiteral(BooleanLiteral bl) { return UnitCompiler.this.getConstantValue2(bl); }
@Override @Nullable public Object visitCharacterLiteral(CharacterLiteral cl) throws CompileException { return UnitCompiler.this.getConstantValue2(cl); }
@Override @Nullable public Object visitStringLiteral(StringLiteral sl) throws CompileException { return UnitCompiler.this.getConstantValue2(sl); }
@Override @Nullable public Object visitNullLiteral(NullLiteral nl) { return UnitCompiler.this.getConstantValue2(nl); }
@Override @Nullable public Object visitSimpleConstant(SimpleConstant sl) { return UnitCompiler.this.getConstantValue2(sl); }
@Override @Nullable public Object visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { return UnitCompiler.this.getConstantValue2(naci); }
@Override @Nullable public Object visitNewArray(NewArray na) { return UnitCompiler.this.getConstantValue2(na); }
@Override @Nullable public Object visitNewInitializedArray(NewInitializedArray nia) { return UnitCompiler.this.getConstantValue2(nia); }
@Override @Nullable public Object visitNewClassInstance(NewClassInstance nci) { return UnitCompiler.this.getConstantValue2(nci); }
@Override @Nullable public Object visitParameterAccess(ParameterAccess pa) { return UnitCompiler.this.getConstantValue2(pa); }
@Override @Nullable public Object visitQualifiedThisReference(QualifiedThisReference qtr) { return UnitCompiler.this.getConstantValue2(qtr); }
@Override @Nullable public Object visitThisReference(ThisReference tr) { return UnitCompiler.this.getConstantValue2(tr); }
@Override @Nullable public Object visitLambdaExpression(LambdaExpression le) { return UnitCompiler.this.getConstantValue2(le); }
@Override @Nullable public Object visitMethodReference(MethodReference mr) { return UnitCompiler.this.getConstantValue2(mr); }
@Override @Nullable public Object visitInstanceCreationReference(ClassInstanceCreationReference cicr) { return UnitCompiler.this.getConstantValue2(cicr); }
@Override @Nullable public Object visitArrayCreationReference(ArrayCreationReference acr) { return UnitCompiler.this.getConstantValue2(acr); }
}));
}
@SuppressWarnings("static-method")
@Nullable private Object
getConstantValue2(Rvalue rv) { return UnitCompiler.NOT_CONSTANT; }
@Nullable private Object
getConstantValue2(AmbiguousName an) throws CompileException {
return this.getConstantValue(this.toRvalueOrCompileException(this.reclassify(an)));
}
@SuppressWarnings("static-method")
@Nullable private Object
getConstantValue2(FieldAccess fa) throws CompileException {
return fa.field.getConstantValue();
}
@Nullable private Object
getConstantValue2(UnaryOperation uo) throws CompileException {
if (uo.operator == "+") { // SUPPRESS CHECKSTYLE StringLiteralEquality
return this.getConstantValue(uo.operand);
}
if (uo.operator == "-") { // SUPPRESS CHECKSTYLE StringLiteralEquality
// Handle the super special cases "-2147483648" and "-9223372036854775808L" (JLS9 3.10.1).
if (uo.operand instanceof IntegerLiteral) {
String v = ((Literal) uo.operand).value;
if (UnitCompiler.TWO_E_31_INTEGER.matcher(v).matches()) return Integer.valueOf(Integer.MIN_VALUE);
if (UnitCompiler.TWO_E_63_LONG.matcher(v).matches()) return Long.valueOf(Long.MIN_VALUE);
}
Object cv = this.getConstantValue(uo.operand);
if (cv == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT;
// SUPPRESS CHECKSTYLE DOT__SELECTOR|L_PAREN__METH_INVOCATION:6
if (cv instanceof Byte) return Byte .valueOf((byte) -((Byte) cv));
if (cv instanceof Short) return Short .valueOf((short) -((Short) cv));
if (cv instanceof Integer) return Integer.valueOf( -((Integer) cv));
if (cv instanceof Long) return Long .valueOf( -((Long) cv));
if (cv instanceof Float) return Float .valueOf( -((Float) cv));
if (cv instanceof Double) return Double .valueOf( -((Double) cv));
return UnitCompiler.NOT_CONSTANT;
}
if (uo.operator == "!") { // SUPPRESS CHECKSTYLE StringLiteralEquality
Object cv = this.getConstantValue(uo.operand);
return (
cv == Boolean.TRUE ? Boolean.FALSE :
cv == Boolean.FALSE ? Boolean.TRUE :
UnitCompiler.NOT_CONSTANT
);
}
return UnitCompiler.NOT_CONSTANT;
}
/**
* 2147483648 is the special value that can not be stored in an INT, but its negated value
* (-2147483648) can.
*/
private static final Pattern
TWO_E_31_INTEGER = Pattern.compile("2_*1_*4_*7_*4_*8_*3_*6_*4_*8");
/**
* 9223372036854775808 is the special value that can not be stored in a LONG, but its negated
* value (-9223372036854775808) can.
*/
private static final Pattern
TWO_E_63_LONG = Pattern.compile("9_*2_*2_*3_*3_*7_*2_*0_*3_*6_*8_*5_*4_*7_*7_*5_*8_*0_*8[lL]");
@Nullable private Object
getConstantValue2(ConditionalExpression ce) throws CompileException {
Object lhsCv = this.getConstantValue(ce.lhs);
if (!(lhsCv instanceof Boolean)) return UnitCompiler.NOT_CONSTANT;
IType ceType = this.getType2(ce);
if (!UnitCompiler.isPrimitive(ceType) && ceType != this.iClassLoader.TYPE_java_lang_String) return UnitCompiler.NOT_CONSTANT;
if (((Boolean) lhsCv).booleanValue()) {
this.fakeCompile(ce.rhs);
return this.getConstantValue(ce.mhs);
} else {
this.fakeCompile(ce.mhs);
return this.getConstantValue(ce.rhs);
}
}
@Nullable private Object
getConstantValue2(BinaryOperation bo) throws CompileException {
// "|", "^", "&", "*", "/", "%", "+", "-", "==", "!=".
if (
// SUPPRESS CHECKSTYLE StringLiteralEquality:10
bo.operator == "|"
|| bo.operator == "^"
|| bo.operator == "&"
|| bo.operator == "*"
|| bo.operator == "/"
|| bo.operator == "%"
|| bo.operator == "+"
|| bo.operator == "-"
|| bo.operator == "=="
|| bo.operator == "!="
) {
// Unroll the constant operands.
List cvs = new ArrayList<>();
for (Iterator it = bo.unrollLeftAssociation(); it.hasNext();) {
Object cv = this.getConstantValue((Rvalue) it.next());
if (cv == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT;
cvs.add(cv);
}
// Compute the constant value of the unrolled binary operation.
Iterator it = cvs.iterator();
Object lhs = it.next();
while (it.hasNext()) {
if (lhs == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT;
Object rhs = it.next();
// String concatenation?
// SUPPRESS CHECKSTYLE StringLiteralEquality
if (bo.operator == "+" && (lhs instanceof String || rhs instanceof String)) {
StringBuilder sb = new StringBuilder(lhs.toString()).append(rhs);
while (it.hasNext()) sb.append(it.next().toString());
return sb.toString();
}
if (lhs instanceof Number && rhs instanceof Number) {
try {
if (lhs instanceof Double || rhs instanceof Double) {
double lhsD = ((Number) lhs).doubleValue();
double rhsD = ((Number) rhs).doubleValue();
lhs = (
// SUPPRESS CHECKSTYLE StringLiteralEquality:7
bo.operator == "*" ? Double.valueOf(lhsD * rhsD) :
bo.operator == "/" ? Double.valueOf(lhsD / rhsD) :
bo.operator == "%" ? Double.valueOf(lhsD % rhsD) :
bo.operator == "+" ? Double.valueOf(lhsD + rhsD) :
bo.operator == "-" ? Double.valueOf(lhsD - rhsD) :
bo.operator == "==" ? Boolean.valueOf(lhsD == rhsD) :
bo.operator == "!=" ? Boolean.valueOf(lhsD != rhsD) :
UnitCompiler.NOT_CONSTANT
);
continue;
}
if (lhs instanceof Float || rhs instanceof Float) {
float lhsF = ((Number) lhs).floatValue();
float rhsF = ((Number) rhs).floatValue();
lhs = (
// SUPPRESS CHECKSTYLE StringLiteralEquality:7
bo.operator == "*" ? Float.valueOf(lhsF * rhsF) :
bo.operator == "/" ? Float.valueOf(lhsF / rhsF) :
bo.operator == "%" ? Float.valueOf(lhsF % rhsF) :
bo.operator == "+" ? Float.valueOf(lhsF + rhsF) :
bo.operator == "-" ? Float.valueOf(lhsF - rhsF) :
bo.operator == "==" ? Boolean.valueOf(lhsF == rhsF) :
bo.operator == "!=" ? Boolean.valueOf(lhsF != rhsF) :
UnitCompiler.NOT_CONSTANT
);
continue;
}
if (lhs instanceof Long || rhs instanceof Long) {
long lhsL = ((Number) lhs).longValue();
long rhsL = ((Number) rhs).longValue();
lhs = (
// SUPPRESS CHECKSTYLE StringLiteralEquality:10
bo.operator == "|" ? Long.valueOf(lhsL | rhsL) :
bo.operator == "^" ? Long.valueOf(lhsL ^ rhsL) :
bo.operator == "&" ? Long.valueOf(lhsL & rhsL) :
bo.operator == "*" ? Long.valueOf(lhsL * rhsL) :
bo.operator == "/" ? Long.valueOf(lhsL / rhsL) :
bo.operator == "%" ? Long.valueOf(lhsL % rhsL) :
bo.operator == "+" ? Long.valueOf(lhsL + rhsL) :
bo.operator == "-" ? Long.valueOf(lhsL - rhsL) :
bo.operator == "==" ? Boolean.valueOf(lhsL == rhsL) :
bo.operator == "!=" ? Boolean.valueOf(lhsL != rhsL) :
UnitCompiler.NOT_CONSTANT
);
continue;
}
if (
lhs instanceof Integer || lhs instanceof Byte || lhs instanceof Short
|| rhs instanceof Integer || lhs instanceof Byte || lhs instanceof Short
) {
int lhsI = ((Number) lhs).intValue();
int rhsI = ((Number) rhs).intValue();
lhs = (
// SUPPRESS CHECKSTYLE StringLiteralEquality:10
bo.operator == "|" ? Integer.valueOf(lhsI | rhsI) :
bo.operator == "^" ? Integer.valueOf(lhsI ^ rhsI) :
bo.operator == "&" ? Integer.valueOf(lhsI & rhsI) :
bo.operator == "*" ? Integer.valueOf(lhsI * rhsI) :
bo.operator == "/" ? Integer.valueOf(lhsI / rhsI) :
bo.operator == "%" ? Integer.valueOf(lhsI % rhsI) :
bo.operator == "+" ? Integer.valueOf(lhsI + rhsI) :
bo.operator == "-" ? Integer.valueOf(lhsI - rhsI) :
bo.operator == "==" ? Boolean.valueOf(lhsI == rhsI) :
bo.operator == "!=" ? Boolean.valueOf(lhsI != rhsI) :
UnitCompiler.NOT_CONSTANT
);
continue;
}
} catch (ArithmeticException ae) {
// Most likely a divide by zero or modulo by zero. Guess we can't make this expression into a
// constant.
return UnitCompiler.NOT_CONSTANT;
}
throw new IllegalStateException();
}
if (lhs instanceof Character && rhs instanceof Character) {
char lhsC = ((Character) lhs).charValue();
char rhsC = ((Character) rhs).charValue();
lhs = (
bo.operator == "==" ? Boolean.valueOf(lhsC == rhsC) : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.operator == "!=" ? Boolean.valueOf(lhsC != rhsC) : // SUPPRESS CHECKSTYLE StringLiteralEquality
UnitCompiler.NOT_CONSTANT
);
continue;
}
if (lhs == null || rhs == null) {
lhs = (
bo.operator == "==" ? Boolean.valueOf(lhs == rhs) : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.operator == "!=" ? Boolean.valueOf(lhs != rhs) : // SUPPRESS CHECKSTYLE StringLiteralEquality
UnitCompiler.NOT_CONSTANT
);
continue;
}
return UnitCompiler.NOT_CONSTANT;
}
return lhs;
}
// "&&" and "||" with constant LHS operand.
if (bo.operator == "&&" || bo.operator == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality
Object lhsValue = this.getConstantValue(bo.lhs);
if (lhsValue instanceof Boolean) {
boolean lhsBv = ((Boolean) lhsValue).booleanValue();
return (
bo.operator == "&&" // SUPPRESS CHECKSTYLE StringLiteralEquality
? (lhsBv ? this.getConstantValue(bo.rhs) : Boolean.FALSE)
: (lhsBv ? Boolean.TRUE : this.getConstantValue(bo.rhs))
);
}
}
return UnitCompiler.NOT_CONSTANT;
}
private Object
getConstantValue2(Cast c) throws CompileException {
Object cv = this.getConstantValue(c.value);
if (cv == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT;
if (cv instanceof Number) {
IType tt = this.getType(c.targetType);
if (tt == IClass.BYTE) return Byte.valueOf(((Number) cv).byteValue());
if (tt == IClass.SHORT) return Short.valueOf(((Number) cv).shortValue());
if (tt == IClass.INT) return Integer.valueOf(((Number) cv).intValue());
if (tt == IClass.LONG) return Long.valueOf(((Number) cv).longValue());
if (tt == IClass.FLOAT) return Float.valueOf(((Number) cv).floatValue());
if (tt == IClass.DOUBLE) return Double.valueOf(((Number) cv).doubleValue());
}
return UnitCompiler.NOT_CONSTANT;
}
@Nullable private Object
getConstantValue2(ParenthesizedExpression pe) throws CompileException {
return this.getConstantValue(pe.value);
}
private @Nullable Object
getConstantValue2(LocalVariableAccess lva) throws CompileException {
// An optimization for the (very special case)
//
// void method() // Any method declarator
// ...
// boolean x = true; // Any local variable name allowed; also value "false"
// if (x) { // The condition expression must be exactly like this
// ...
//
if (lva.getEnclosingScope() instanceof IfStatement) {
IfStatement is = (IfStatement) lva.getEnclosingScope();
if (is.condition instanceof AmbiguousName) {
Atom ra = ((AmbiguousName) is.condition).reclassified;
if (ra instanceof LocalVariableAccess) {
// It's an IF statement with a LocalVariableAccess expression.
LocalVariable lv = ((LocalVariableAccess) ra).localVariable;
// Get all statements of the immediately enclosing block, method or constructor.
List extends BlockStatement> ss = (
is.getEnclosingScope() instanceof FunctionDeclarator
? ((FunctionDeclarator) is.getEnclosingScope()).statements
: is.getEnclosingScope() instanceof Block
? ((Block) is.getEnclosingScope()).statements
: null
);
if (ss != null) {
int isi = ss.indexOf(is);
// Check all local variable declaration statements *before* the IF statement.
boolean haveSideEffects = false;
for (int i = isi - 1; i >= 0; i--) {
BlockStatement bs = (BlockStatement) ss.get(i);
if (!(bs instanceof LocalVariableDeclarationStatement)) break;
LocalVariableDeclarationStatement lvds = (LocalVariableDeclarationStatement) bs;
// Check all variable declarators (int a = 7, b = 8, c = 9, ...)
for (int j = lvds.variableDeclarators.length - 1; j >= 0; j--) {
VariableDeclarator vd = lvds.variableDeclarators[j];
ArrayInitializerOrRvalue lvi = vd.initializer;
if (vd.localVariable == lv) {
if (!lvds.isFinal() && haveSideEffects) {
// The non-final LV may be modified by intervening VD initializers.
return UnitCompiler.NOT_CONSTANT;
}
// The LV is FINAL and thus cannot be modified by intervening VD initializers.
return this.getConstantValue(lvi);
}
if (lvi != null) {
haveSideEffects |= UnitCompiler.mayHaveSideEffects(lvi);
}
}
}
}
}
}
}
return UnitCompiler.NOT_CONSTANT;
}
/**
* @return An {@link Integer} or a {@link Long}
*/
@SuppressWarnings("static-method") private Object
getConstantValue2(IntegerLiteral il) throws CompileException {
String v = il.value.toLowerCase();
// Remove underscores in integer literal (JLS8, section 3.10.1).
for (;;) {
int ui = v.indexOf('_');
if (ui == -1) break;
v = v.substring(0, ui) + v.substring(ui + 1);
}
int radix;
boolean signed;
// HexIntegerLiteral (JLS8, section 3.10.1)?
if (v.startsWith("0x")) {
radix = 16;
signed = false;
v = v.substring(2);
} else
// BinaryIntegerLiteral (JLS8, section 3.10.1)?
if (v.startsWith("0b")) {
radix = 2;
signed = false;
v = v.substring(2);
} else
// OctalIntegerLiteral (JLS8, section 3.10.1)?
if (v.startsWith("0") && !"0".equals(v) && !"0l".equals(v)) {
radix = 8;
signed = false;
v = v.substring(1);
} else
// Must be a DecimalIntegerLiteral (JLS8, section 3.10.1).
{
radix = 10;
signed = true;
}
try {
if (v.endsWith("l")) {
v = v.substring(0, v.length() - 1);
return signed ? Long.parseLong(v, radix) : Numbers.parseUnsignedLong(v, radix);
} else {
return signed ? Integer.parseInt(v, radix) : Numbers.parseUnsignedInt(v, radix);
}
} catch (NumberFormatException e) {
// SUPPRESS CHECKSTYLE AvoidHidingCause
throw UnitCompiler.compileException(il, "Invalid integer literal \"" + il.value + "\"");
}
}
/**
* @return A {@link Float} or a {@link Double}
*/
@SuppressWarnings("static-method") private Object
getConstantValue2(FloatingPointLiteral fpl) throws CompileException {
String v = fpl.value;
// Remove underscores in floating point literal.
for (;;) {
int ui = v.indexOf('_');
if (ui == -1) break;
v = v.substring(0, ui) + v.substring(ui + 1);
}
char lastChar = v.charAt(v.length() - 1);
if (lastChar == 'f' || lastChar == 'F') {
v = v.substring(0, v.length() - 1);
float fv;
try {
fv = Float.parseFloat(v);
} catch (NumberFormatException e) {
throw new InternalCompilerException(fpl.getLocation(), "SNO: parsing float literal \"" + v + "\": " + e.getMessage(), e);
}
if (Float.isInfinite(fv)) {
throw UnitCompiler.compileException(fpl, "Value of float literal \"" + v + "\" is out of range");
}
if (Float.isNaN(fv)) {
throw new InternalCompilerException(fpl.getLocation(), "SNO: parsing float literal \"" + v + "\" results in NaN");
}
// Check for FLOAT underrun.
if (fv == 0.0F) {
for (int i = 0; i < v.length(); ++i) {
char c = v.charAt(i);
if ("123456789".indexOf(c) != -1) {
throw UnitCompiler.compileException(
fpl,
"Literal \"" + v + "\" is too small to be represented as a float"
);
}
if (c != '0' && c != '.') break;
}
}
return Float.valueOf(fv);
}
if (lastChar == 'd' || lastChar == 'D') v = v.substring(0, v.length() - 1);
double dv;
try {
dv = Double.parseDouble(v);
} catch (NumberFormatException e) {
throw new InternalCompilerException(fpl.getLocation(), "SNO: parsing double literal \"" + v + "\": " + e.getMessage(), e);
}
if (Double.isInfinite(dv)) {
throw UnitCompiler.compileException(fpl, "Value of double literal \"" + v + "\" is out of range");
}
if (Double.isNaN(dv)) {
throw new InternalCompilerException(fpl.getLocation(), "SNO: parsing double literal \"" + v + "\" results is NaN");
}
// Check for DOUBLE underrun.
if (dv == 0.0F) {
for (int i = 0; i < v.length(); ++i) {
char c = v.charAt(i);
if ("123456789".indexOf(c) != -1) {
throw UnitCompiler.compileException(
fpl,
"Literal \"" + v + "\" is too small to be represented as a double"
);
}
if (c != '0' && c != '.') break;
}
}
return Double.valueOf(dv);
}
@SuppressWarnings("static-method") private boolean
getConstantValue2(BooleanLiteral bl) {
if (bl.value == "true") return true; // SUPPRESS CHECKSTYLE StringLiteralEquality
if (bl.value == "false") return false; // SUPPRESS CHECKSTYLE StringLiteralEquality
throw new InternalCompilerException(bl.getLocation(), bl.value);
}
@SuppressWarnings("static-method") private char
getConstantValue2(CharacterLiteral cl) throws CompileException {
String v = cl.value;
// Strip opening and closing single quotes.
v = v.substring(1, v.length() - 1);
// Decode escape sequences like "\n" and "\0377".
v = UnitCompiler.unescape(v, cl.getLocation());
if (v.isEmpty()) throw new CompileException("Empty character literal", cl.getLocation());
if (v.length() > 1) throw new CompileException("Invalid character literal " + cl.value, cl.getLocation());
return Character.valueOf(v.charAt(0));
}
@SuppressWarnings("static-method") private String
getConstantValue2(StringLiteral sl) throws CompileException {
String v = sl.value;
// Strip opening and closing double quotes.
v = v.substring(1, v.length() - 1);
// Decode escape sequences like "\n" and "\0377".
v = UnitCompiler.unescape(v, sl.getLocation());
return v;
}
@SuppressWarnings("static-method")
@Nullable private Object
getConstantValue2(NullLiteral nl) { return null; }
@SuppressWarnings("static-method")
@Nullable private Object
getConstantValue2(SimpleConstant sl) { return sl.value; }
// ------------ BlockStatement.generatesCode() -------------
/**
* Checks whether invocation of {@link #compile(BlockStatement)} would generate more than zero code bytes.
*/
private boolean
generatesCode(BlockStatement bs) throws CompileException {
Boolean result = (Boolean) bs.accept(new BlockStatementVisitor() {
@Override public Boolean visitInitializer(Initializer i) throws CompileException { return UnitCompiler.this.generatesCode2(i); }
@Override public Boolean visitFieldDeclaration(FieldDeclaration fd) throws CompileException { return UnitCompiler.this.generatesCode2(fd); }
@Override public Boolean visitLabeledStatement(LabeledStatement ls) { return UnitCompiler.this.generatesCode2(ls); }
@Override public Boolean visitBlock(Block b) throws CompileException { return UnitCompiler.this.generatesCode2(b); }
@Override public Boolean visitExpressionStatement(ExpressionStatement es) { return UnitCompiler.this.generatesCode2(es); }
@Override public Boolean visitIfStatement(IfStatement is) { return UnitCompiler.this.generatesCode2(is); }
@Override public Boolean visitForStatement(ForStatement fs) { return UnitCompiler.this.generatesCode2(fs); }
@Override public Boolean visitForEachStatement(ForEachStatement fes) { return UnitCompiler.this.generatesCode2(fes); }
@Override public Boolean visitWhileStatement(WhileStatement ws) { return UnitCompiler.this.generatesCode2(ws); }
@Override public Boolean visitTryStatement(TryStatement ts) { return UnitCompiler.this.generatesCode2(ts); }
@Override public Boolean visitSwitchStatement(SwitchStatement ss) { return UnitCompiler.this.generatesCode2(ss); }
@Override public Boolean visitSynchronizedStatement(SynchronizedStatement ss) { return UnitCompiler.this.generatesCode2(ss); }
@Override public Boolean visitDoStatement(DoStatement ds) { return UnitCompiler.this.generatesCode2(ds); }
@Override public Boolean visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { return UnitCompiler.this.generatesCode2(lvds); }
@Override public Boolean visitReturnStatement(ReturnStatement rs) { return UnitCompiler.this.generatesCode2(rs); }
@Override public Boolean visitThrowStatement(ThrowStatement ts) { return UnitCompiler.this.generatesCode2(ts); }
@Override public Boolean visitBreakStatement(BreakStatement bs) { return UnitCompiler.this.generatesCode2(bs); }
@Override public Boolean visitContinueStatement(ContinueStatement cs) { return UnitCompiler.this.generatesCode2(cs); }
@Override public Boolean visitAssertStatement(AssertStatement as) { return UnitCompiler.this.generatesCode2(as); }
@Override public Boolean visitEmptyStatement(EmptyStatement es) { return UnitCompiler.this.generatesCode2(es); }
@Override public Boolean visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { return UnitCompiler.this.generatesCode2(lcds); }
@Override public Boolean visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { return UnitCompiler.this.generatesCode2(aci); }
@Override public Boolean visitSuperConstructorInvocation(SuperConstructorInvocation sci) { return UnitCompiler.this.generatesCode2(sci); }
});
assert result != null;
return result;
}
@SuppressWarnings("static-method") private boolean
generatesCode2(BlockStatement bs) { return true; }
@SuppressWarnings("static-method") private boolean
generatesCode2(AssertStatement as) { return true; }
@SuppressWarnings("static-method") private boolean
generatesCode2(EmptyStatement es) { return false; }
@SuppressWarnings("static-method") private boolean
generatesCode2(LocalClassDeclarationStatement lcds) { return false; }
private boolean
generatesCode2(Initializer i) throws CompileException { return this.generatesCode(i.block); }
private boolean
generatesCode2(List l) throws CompileException {
for (BlockStatement bs : l) if (this.generatesCode(bs)) return true;
return false;
}
private boolean
generatesCode2(Block b) throws CompileException { return this.generatesCode2(b.statements); }
private boolean
generatesCode2(FieldDeclaration fd) throws CompileException {
// Code is only generated if at least one of the declared variables has a non-constant-final initializer.
for (VariableDeclarator vd : fd.variableDeclarators) {
if (this.getNonConstantFinalInitializer(fd, vd) != null) return true;
}
return false;
}
// ------------ BlockStatement.leave() -------------
/**
* Cleans up the statement context. This is currently relevant for "{@code try ... catch ... finally}" statements
* (execute {@code finally} clause) and {@code synchronized} statements (monitorexit).
*
* Statements like {@code return}, {@code break}, {@code continue} must call this method for all the statements
* they terminate.
*
*/
private void
leave(BlockStatement bs) throws CompileException {
BlockStatementVisitor bsv = new BlockStatementVisitor() {
@Override @Nullable public Void visitInitializer(Initializer i) { UnitCompiler.this.leave2(i); return null; }
@Override @Nullable public Void visitFieldDeclaration(FieldDeclaration fd) { UnitCompiler.this.leave2(fd); return null; }
@Override @Nullable public Void visitLabeledStatement(LabeledStatement ls) { UnitCompiler.this.leave2(ls); return null; }
@Override @Nullable public Void visitBlock(Block b) { UnitCompiler.this.leave2(b); return null; }
@Override @Nullable public Void visitExpressionStatement(ExpressionStatement es) { UnitCompiler.this.leave2(es); return null; }
@Override @Nullable public Void visitIfStatement(IfStatement is) { UnitCompiler.this.leave2(is); return null; }
@Override @Nullable public Void visitForStatement(ForStatement fs) { UnitCompiler.this.leave2(fs); return null; }
@Override @Nullable public Void visitForEachStatement(ForEachStatement fes) { UnitCompiler.this.leave2(fes); return null; }
@Override @Nullable public Void visitWhileStatement(WhileStatement ws) { UnitCompiler.this.leave2(ws); return null; }
@Override @Nullable public Void visitTryStatement(TryStatement ts) throws CompileException { UnitCompiler.this.leave2(ts); return null; }
@Override @Nullable public Void visitSwitchStatement(SwitchStatement ss) { UnitCompiler.this.leave2(ss); return null; }
@Override @Nullable public Void visitSynchronizedStatement(SynchronizedStatement ss) { UnitCompiler.this.leave2(ss); return null; }
@Override @Nullable public Void visitDoStatement(DoStatement ds) { UnitCompiler.this.leave2(ds); return null; }
@Override @Nullable public Void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { UnitCompiler.this.leave2(lvds); return null; }
@Override @Nullable public Void visitReturnStatement(ReturnStatement rs) { UnitCompiler.this.leave2(rs); return null; }
@Override @Nullable public Void visitThrowStatement(ThrowStatement ts) { UnitCompiler.this.leave2(ts); return null; }
@Override @Nullable public Void visitBreakStatement(BreakStatement bs) { UnitCompiler.this.leave2(bs); return null; }
@Override @Nullable public Void visitContinueStatement(ContinueStatement cs) { UnitCompiler.this.leave2(cs); return null; }
@Override @Nullable public Void visitAssertStatement(AssertStatement as) { UnitCompiler.this.leave2(as); return null; }
@Override @Nullable public Void visitEmptyStatement(EmptyStatement es) { UnitCompiler.this.leave2(es); return null; }
@Override @Nullable public Void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { UnitCompiler.this.leave2(lcds); return null; }
@Override @Nullable public Void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { UnitCompiler.this.leave2(aci); return null; }
@Override @Nullable public Void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { UnitCompiler.this.leave2(sci); return null; }
};
bs.accept(bsv);
}
private void
leave2(BlockStatement bs) {}
private void
leave2(SynchronizedStatement ss) {
this.load(ss, this.iClassLoader.TYPE_java_lang_Object, ss.monitorLvIndex);
this.monitorexit(ss);
}
private void
leave2(TryStatement ts) throws CompileException {
Block f = ts.finallY;
if (f == null) return;
this.getCodeContext().saveLocalVariables();
try {
if (this.compile(f)) return;
} finally {
this.getCodeContext().restoreLocalVariables();
}
}
// ---------------- Lvalue.compileSet() -----------------
/**
* Generates code that stores a value in the {@link Lvalue}. Expects the {@link Lvalue}'s context (see {@link
* #compileContext}) and a value of the {@link Lvalue}'s type on the operand stack.
*/
private void
compileSet(Lvalue lv) throws CompileException {
lv.accept(new LvalueVisitor() {
@Override @Nullable public Void visitAmbiguousName(AmbiguousName an) throws CompileException { UnitCompiler.this.compileSet2(an); return null; }
@Override @Nullable public Void visitArrayAccessExpression(ArrayAccessExpression aae) throws CompileException { UnitCompiler.this.compileSet2(aae); return null; }
@Override @Nullable public Void visitFieldAccess(FieldAccess fa) throws CompileException { UnitCompiler.this.compileSet2(fa); return null; }
@Override @Nullable public Void visitFieldAccessExpression(FieldAccessExpression fae) throws CompileException { UnitCompiler.this.compileSet2(fae); return null; }
@Override @Nullable public Void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) throws CompileException { UnitCompiler.this.compileSet2(scfae); return null; }
@Override @Nullable public Void visitLocalVariableAccess(LocalVariableAccess lva) { UnitCompiler.this.compileSet2(lva); return null; }
@Override @Nullable public Void visitParenthesizedExpression(ParenthesizedExpression pe) throws CompileException { UnitCompiler.this.compileSet2(pe); return null; }
});
}
private void
compileSet2(AmbiguousName an) throws CompileException {
this.compileSet(this.toLvalueOrCompileException(this.reclassify(an)));
}
private void
compileSet2(LocalVariableAccess lva) { this.store(lva, lva.localVariable); }
private void
compileSet2(FieldAccess fa) throws CompileException {
this.checkAccessible(fa.field, fa.getEnclosingScope(), fa.getLocation());
this.putfield(fa, fa.field);
}
private void
compileSet2(ArrayAccessExpression aae) throws CompileException {
this.arraystore(aae, this.getType(aae));
}
private void
compileSet2(FieldAccessExpression fae) throws CompileException {
this.compileSet(this.toLvalueOrCompileException(this.determineValue(fae)));
}
private void
compileSet2(SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
this.compileSet(this.toLvalueOrCompileException(this.determineValue(scfae)));
}
private void
compileSet2(ParenthesizedExpression pe) throws CompileException {
this.compileSet(this.toLvalueOrCompileException(pe.value));
}
// ---------------- Atom.getType() ----------------
/**
* @return For a {@link Type}, the resolved {@link IClass}, for an {@link Rvalue}, the resolved {@link IClass} of
* the rvalue's type
* @see #resolve(TypeDeclaration)
*/
private IType
getType(Atom a) throws CompileException {
IType result = (IType) a.accept(new AtomVisitor() {
@Override public IType
visitPackage(Package p) throws CompileException { return UnitCompiler.this.getType2(p); }
@Override @Nullable public IType
visitType(Type t) throws CompileException { return UnitCompiler.this.getType(t); }
@Override @Nullable public IType
visitRvalue(Rvalue rv) throws CompileException { return UnitCompiler.this.getType(rv); }
@Override @Nullable public IType
visitConstructorInvocation(ConstructorInvocation ci) throws CompileException {
return UnitCompiler.this.getType2(ci);
}
});
assert result != null;
return result;
}
private static IClass
rawTypeOf(IType iType) {
while (iType instanceof IParameterizedType) iType = ((IParameterizedType) iType).getRawType();
assert iType instanceof IClass;
return (IClass) iType;
}
private static IClass[]
rawTypesOf(IType[] iTypes) {
IClass[] result = new IClass[iTypes.length];
for (int i = 0; i < result.length; i++) result[i] = UnitCompiler.rawTypeOf(iTypes[i]);
return result;
}
private IClass
getRawType(Type t) throws CompileException { return UnitCompiler.rawTypeOf(this.getType(t)); }
private IType
getType(Type t) throws CompileException {
IType result = (IType) t.accept(new TypeVisitor() {
@Override public IType visitArrayType(ArrayType at) throws CompileException { return UnitCompiler.this.getType2(at); }
@Override public IType visitPrimitiveType(PrimitiveType bt) { return UnitCompiler.this.getType2(bt); }
@Override public IType visitReferenceType(ReferenceType rt) throws CompileException { return UnitCompiler.this.getType2(rt); }
@Override public IType visitRvalueMemberType(RvalueMemberType rmt) throws CompileException { return UnitCompiler.this.getType2(rmt); }
@Override public IType visitSimpleType(SimpleType st) { return UnitCompiler.this.getType2(st); }
});
assert result != null;
return result;
}
private IType[]
getTypes(final Type[] types) throws CompileException {
IType[] result = new IType[types.length];
for (int i = 0; i < types.length; ++i) result[i] = this.getType(types[i]);
return result;
}
private IType
getType(Rvalue rv) throws CompileException {
IType result = (IType) rv.accept(new RvalueVisitor() {
@Override @Nullable public IType
visitLvalue(Lvalue lv) throws CompileException { return UnitCompiler.this.getType(lv); }
@Override public IType visitArrayLength(ArrayLength al) { return UnitCompiler.this.getType2(al); }
@Override public IType visitAssignment(Assignment a) throws CompileException { return UnitCompiler.this.getType2(a); }
@Override public IType visitUnaryOperation(UnaryOperation uo) throws CompileException { return UnitCompiler.this.getType2(uo); }
@Override public IType visitBinaryOperation(BinaryOperation bo) throws CompileException { return UnitCompiler.this.getType2(bo); }
@Override public IType visitCast(Cast c) throws CompileException { return UnitCompiler.this.getType2(c); }
@Override public IType visitClassLiteral(ClassLiteral cl) { return UnitCompiler.this.getType2(cl); }
@Override public IType visitConditionalExpression(ConditionalExpression ce) throws CompileException { return UnitCompiler.this.getType2(ce); }
@Override public IType visitCrement(Crement c) throws CompileException { return UnitCompiler.this.getType2(c); }
@Override public IType visitInstanceof(Instanceof io) { return UnitCompiler.this.getType2(io); }
@Override public IType visitMethodInvocation(MethodInvocation mi) throws CompileException { return UnitCompiler.this.getType2(mi); }
@Override public IType visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) throws CompileException { return UnitCompiler.this.getType2(smi); }
@Override public IType visitIntegerLiteral(IntegerLiteral il) { return UnitCompiler.this.getType2(il); }
@Override public IType visitFloatingPointLiteral(FloatingPointLiteral fpl) { return UnitCompiler.this.getType2(fpl); }
@Override public IType visitBooleanLiteral(BooleanLiteral bl) { return UnitCompiler.this.getType2(bl); }
@Override public IType visitCharacterLiteral(CharacterLiteral cl) { return UnitCompiler.this.getType2(cl); }
@Override public IType visitStringLiteral(StringLiteral sl) { return UnitCompiler.this.getType2(sl); }
@Override public IType visitNullLiteral(NullLiteral nl) { return UnitCompiler.this.getType2(nl); }
@Override public IType visitSimpleConstant(SimpleConstant sl) { return UnitCompiler.this.getType2(sl); }
@Override public IType visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { return UnitCompiler.this.getType2(naci); }
@Override public IType visitNewArray(NewArray na) throws CompileException { return UnitCompiler.this.getType2(na); }
@Override public IType visitNewInitializedArray(NewInitializedArray nia) throws CompileException { return UnitCompiler.this.getType2(nia); }
@Override public IType visitNewClassInstance(NewClassInstance nci) throws CompileException { return UnitCompiler.this.getType2(nci); }
@Override public IType visitParameterAccess(ParameterAccess pa) throws CompileException { return UnitCompiler.this.getType2(pa); }
@Override public IType visitQualifiedThisReference(QualifiedThisReference qtr) throws CompileException { return UnitCompiler.this.getType2(qtr); }
@Override public IType visitThisReference(ThisReference tr) throws CompileException { return UnitCompiler.this.getType2(tr); }
@Override public IType visitLambdaExpression(LambdaExpression le) throws CompileException { return UnitCompiler.this.getType2(le); }
@Override public IType visitMethodReference(MethodReference mr) throws CompileException { return UnitCompiler.this.getType2(mr); }
@Override public IType visitInstanceCreationReference(ClassInstanceCreationReference cicr) throws CompileException { return UnitCompiler.this.getType2(cicr); }
@Override public IType visitArrayCreationReference(ArrayCreationReference acr) throws CompileException { return UnitCompiler.this.getType2(acr); }
});
assert result != null;
return result;
}
private IType
getType(Lvalue lv) throws CompileException {
IType result = (IType) lv.accept(new LvalueVisitor() {
@Override public IType visitAmbiguousName(AmbiguousName an) throws CompileException { return UnitCompiler.this.getType2(an); }
@Override public IType visitArrayAccessExpression(ArrayAccessExpression aae) throws CompileException { return UnitCompiler.this.getType2(aae); }
@Override public IType visitFieldAccess(FieldAccess fa) throws CompileException { return UnitCompiler.this.getType2(fa); }
@Override public IType visitFieldAccessExpression(FieldAccessExpression fae) throws CompileException { return UnitCompiler.this.getType2(fae); }
@Override public IType visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) throws CompileException { return UnitCompiler.this.getType2(scfae); }
@Override public IType visitLocalVariableAccess(LocalVariableAccess lva) { return UnitCompiler.this.getType2(lva); }
@Override public IType visitParenthesizedExpression(ParenthesizedExpression pe) throws CompileException { return UnitCompiler.this.getType2(pe); }
});
assert result != null;
return result;
}
private IClass
getType2(ConstructorInvocation ci) throws CompileException {
this.compileError("Explicit constructor invocation not allowed here", ci.getLocation());
return this.iClassLoader.TYPE_java_lang_Object;
}
@SuppressWarnings("static-method") private IType
getType2(SimpleType st) { return st.iType; }
@SuppressWarnings("static-method") private IClass
getType2(PrimitiveType bt) {
switch (bt.primitive) {
case VOID: return IClass.VOID;
case BYTE: return IClass.BYTE;
case SHORT: return IClass.SHORT;
case CHAR: return IClass.CHAR;
case INT: return IClass.INT;
case LONG: return IClass.LONG;
case FLOAT: return IClass.FLOAT;
case DOUBLE: return IClass.DOUBLE;
case BOOLEAN: return IClass.BOOLEAN;
default: throw new InternalCompilerException(bt.getLocation(), "Invalid primitive " + bt.primitive);
}
}
private IType
getType2(ReferenceType rt) throws CompileException {
String[] identifiers = rt.identifiers;
IType result = this.getReferenceType(
rt.getLocation(),
rt.getEnclosingScope(),
identifiers,
identifiers.length,
rt.typeArguments
);
if (result == null) {
this.compileError("Reference type \"" + rt + "\" not found", rt.getLocation());
return this.iClassLoader.TYPE_java_lang_Object;
}
return result;
}
/**
* @param typeArguments Zero-length array if type has no type variables
* @return The resolved {@link IClass}, or {@link IParameterizedType}, or {@code null}
*/
@Nullable private IType
getReferenceType(
Location location,
Scope scope,
String[] identifiers,
int n,
@Nullable TypeArgument[] typeArguments
) throws CompileException {
if (n == 1) {
return this.getReferenceType(location, identifiers[0], typeArguments, scope);
}
// JLS7 6.5.5.1 Unnamed package member type name (one identifier).
// JLS7 6.5.5.2.1 Qualified type name (two or more identifiers).
{
String className = Java.join(identifiers, ".", 0, n);
IClass result = this.findTypeByName(location, className);
if (result != null) return result;
}
// JLS7 6.5.5.2.2 referenceType '.' memberTypeName
if (n >= 2) {
IType enclosingType = this.getReferenceType(location, scope, identifiers, n - 1, new TypeArgument[0]);
if (enclosingType != null) {
String memberTypeName = identifiers[n - 1];
IClass memberType = this.findMemberType(enclosingType, memberTypeName, typeArguments, location);
if (memberType == null) {
this.compileError(
"\"" + enclosingType + "\" declares no member type \"" + memberTypeName + "\"",
location
);
return this.iClassLoader.TYPE_java_lang_Object;
}
return memberType;
}
}
return null;
}
/**
* JLS7 6.5.5.1 Simple type name (single identifier)
*
* @param typeArguments Zero-length array if type is not parameterized
* @return The resolved {@link IClass} or {@link IParameterizedType}
*/
private IType
getReferenceType(
Location location,
String simpleTypeName,
@Nullable TypeArgument[] typeArguments,
Scope scope
) throws CompileException {
if ("var".equals(simpleTypeName)) {
this.compileError("Local variable type inference NYI", location);
return this.iClassLoader.TYPE_java_lang_Object;
}
// Method declaration type parameter?
for (Scope s = scope; !(s instanceof CompilationUnit); s = s.getEnclosingScope()) {
if (!(s instanceof MethodDeclarator)) continue;
MethodDeclarator md = (MethodDeclarator) s;
TypeParameter[] typeParameters = md.getOptionalTypeParameters();
if (typeParameters != null) {
for (TypeParameter tp : typeParameters) {
if (tp.name.equals(simpleTypeName)) {
IType[] boundTypes;
ReferenceType[] ob = tp.bound;
if (ob == null) {
boundTypes = new IType[] { this.iClassLoader.TYPE_java_lang_Object };
} else {
boundTypes = new IType[ob.length];
for (int i = 0; i < boundTypes.length; i++) {
boundTypes[i] = this.getType(ob[i]);
}
}
// Here is the big simplification: Instead of returning the "correct" type, honoring type
// arguments, we simply return the first bound. E.g. "Map.get(K)" returns a "V", but
// JANINO says it's an "Object" (the implicit bound of "V").
return boundTypes[0];
}
}
}
}
// Type declaration type parameter?
for (Scope s = scope; !(s instanceof CompilationUnit); s = s.getEnclosingScope()) {
if (!(s instanceof NamedTypeDeclaration)) continue;
NamedTypeDeclaration ntd = (NamedTypeDeclaration) s;
TypeParameter[] typeParameters = ntd.getOptionalTypeParameters();
if (typeParameters != null) {
for (TypeParameter tp : typeParameters) {
if (tp.name.equals(simpleTypeName)) {
IType[] boundTypes;
ReferenceType[] ob = tp.bound;
if (ob == null) {
boundTypes = new IClass[] { this.iClassLoader.TYPE_java_lang_Object };
} else {
boundTypes = new IClass[ob.length];
for (int i = 0; i < boundTypes.length; i++) {
boundTypes[i] = this.getType(ob[i]);
}
}
return boundTypes[0];
}
}
}
}
// // Determine type arguments.
// IType[] tas;
// {
// if (typeArguments == null) {
// tas = new IType[0];
// } else {
// tas = new IType[typeArguments.length];
// for (int i = 0; i < tas.length; i++) {
// final TypeArgument ta = typeArguments[i];
// tas[i] = this.typeArgumentToIType(ta);
// }
// }
// }
try {
return this.getRawReferenceType(location, simpleTypeName, scope)/*.parameterize(tas)*/;
} catch (CompileException ce) {
throw new CompileException(ce.getMessage(), location, ce);
}
}
@SuppressWarnings("unused") private IType
typeArgumentToIType(TypeArgument ta) throws CompileException {
if (ta instanceof ReferenceType) {
return this.getType((ReferenceType) ta);
} else
if (ta instanceof Wildcard) {
Wildcard w = (Wildcard) ta;
final IType ub = w.bounds == Wildcard.BOUNDS_EXTENDS ? this.getType(w.referenceType) : UnitCompiler.this.iClassLoader.TYPE_java_lang_Object;
final IType lb = w.bounds == Wildcard.BOUNDS_SUPER ? this.getType(w.referenceType) : null;
return new IWildcardType() {
@Override public IType getUpperBound() { return ub; }
@Override @Nullable public IType getLowerBound() { return lb; }
@Override public String
toString() {
String s = "?";
if (ub != UnitCompiler.this.iClassLoader.TYPE_java_lang_Object) s += " extends " + ub;
if (lb != null) s += " super " + lb;
return s;
}
};
} else
if (ta instanceof ArrayType) {
// TODO
return UnitCompiler.this.iClassLoader.TYPE_java_lang_Object;
} else
{
throw new AssertionError(ta.getClass() + ": " + ta);
}
}
private IClass
getRawReferenceType(Location location, String simpleTypeName, Scope scope) throws CompileException {
// 6.5.5.1.1 Local class.
{
LocalClassDeclaration lcd = UnitCompiler.findLocalClassDeclaration(
scope,
simpleTypeName
);
if (lcd != null) return this.resolve(lcd);
}
// 6.5.5.1.2 Member type.
for (Scope s = scope; !(s instanceof CompilationUnit); s = s.getEnclosingScope()) {
if (s instanceof TypeDeclaration) {
IClass mt = this.findMemberType(
this.resolve((AbstractTypeDeclaration) s),
simpleTypeName,
null, // typeArguments
location
);
if (mt != null) return mt;
}
}
// 6.5.5.1.4a Single-type import.
{
IClass importedClass = this.importSingleType(simpleTypeName, location);
if (importedClass != null) return importedClass;
}
CompilationUnit scopeCompilationUnit;
for (Scope s = scope;; s = s.getEnclosingScope()) {
if (s instanceof CompilationUnit) {
scopeCompilationUnit = (CompilationUnit) s;
break;
}
}
// 6.5.5.1.4b Type declared in same compilation unit.
{
PackageMemberTypeDeclaration pmtd = (
scopeCompilationUnit.getPackageMemberTypeDeclaration(simpleTypeName)
);
if (pmtd != null) return this.resolve(pmtd);
}
// 6.5.5.1.5 Type declared in other compilation unit of same package.
{
PackageDeclaration opd = scopeCompilationUnit.packageDeclaration;
String pkg = opd == null ? null : opd.packageName;
String className = pkg == null ? simpleTypeName : pkg + "." + simpleTypeName;
IClass result = this.findTypeByName(location, className);
if (result != null) return result;
}
// 6.5.5.1.6 Type-import-on-demand declaration.
{
IClass importedClass = this.importTypeOnDemand(simpleTypeName, location);
if (importedClass != null) return importedClass;
}
// JLS7 6.5.2.BL1.B2: Type imported through single import.
{
IClass importedIClass = this.importSingleType(simpleTypeName, location);
if (importedIClass != null) {
if (!this.isAccessible(importedIClass, scope)) {
this.compileError("Member type \"" + simpleTypeName + "\" is not accessible", location);
}
return importedIClass;
}
}
// JLS7 6.5.2.BL1.B2: Member type imported through single static import.
{
IClass importedMemberType = null;
for (IClass mt : Iterables.filterByClass(this.importSingleStatic(simpleTypeName), IClass.class)) {
if (importedMemberType != null && mt != importedMemberType) {
this.compileError(
"Ambiguous static member type import: \""
+ importedMemberType.toString()
+ "\" vs. \""
+ mt
+ "\""
);
}
importedMemberType = mt;
}
if (importedMemberType != null) return importedMemberType;
}
// JLS7 6.5.2.BL1.B2: Member type imported through static-import-on-demand.
{
Iterator
it = Iterables.filterByClass(this.importStaticOnDemand(simpleTypeName).iterator(), IClass.class);
if (it.hasNext()) return (IClass) it.next();
}
// Unnamed package member type.
{
IClass result = this.findTypeByName(location, simpleTypeName);
if (result != null) return result;
}
// Type argument of the enclosing anonymous class declaration?
for (
Scope s = scope;
!(s instanceof CompilationUnit);
s = s.getEnclosingScope()
) {
if (!(s instanceof AnonymousClassDeclaration)) continue;
AnonymousClassDeclaration acd = (AnonymousClassDeclaration) s;
Type bt = acd.baseType;
if (bt instanceof ReferenceType) {
TypeArgument[] otas = ((ReferenceType) bt).typeArguments;
if (otas != null) {
for (TypeArgument ta : otas) {
if (ta instanceof ReferenceType) {
String[] is = ((ReferenceType) ta).identifiers;
if (is.length == 1 && is[0].equals(simpleTypeName)) {
return this.iClassLoader.TYPE_java_lang_Object;
}
}
}
}
}
}
// 6.5.5.1.8 Give up.
this.compileError("Cannot determine simple type name \"" + simpleTypeName + "\"", location);
return this.iClassLoader.TYPE_java_lang_Object;
}
/**
* Imports a member class or interface, static field or static method via the compilation unit's static import
* on-demand declarations.
*
* @return A list of {@link IField}s, {@link IMethod}s and/or {@link IClass}es with that simpleName ;
* may be empty
*/
private List
importStaticOnDemand(String simpleName) throws CompileException {
List result = new ArrayList<>();
for (StaticImportOnDemandDeclaration siodd : Iterables.filterByClass(
this.abstractCompilationUnit.importDeclarations,
StaticImportOnDemandDeclaration.class
)) {
IClass iClass = this.findTypeByFullyQualifiedName(siodd.getLocation(), siodd.identifiers);
if (iClass == null) {
this.compileError("Could not load \"" + Java.join(siodd.identifiers, ".") + "\"", siodd.getLocation());
continue;
}
this.importStatic(iClass, simpleName, result, siodd.getLocation());
}
return result;
}
private IClass
getType2(RvalueMemberType rvmt) throws CompileException {
IType rvt = this.getType(rvmt.rvalue);
IClass memberType = this.findMemberType(rvt, rvmt.identifier, null, rvmt.getLocation());
if (memberType == null) {
this.compileError("\"" + rvt + "\" has no member type \"" + rvmt.identifier + "\"", rvmt.getLocation());
return this.iClassLoader.TYPE_java_lang_Object;
}
return memberType;
}
private IClass
getType2(ArrayType at) throws CompileException {
return this.iClassLoader.getArrayIClass(this.getRawType(at.componentType));
}
private IType
getType2(AmbiguousName an) throws CompileException {
return this.getType(this.reclassify(an));
}
private IClass
getType2(Package p) throws CompileException {
this.compileError("Unknown variable or type \"" + p.name + "\"", p.getLocation());
return this.iClassLoader.TYPE_java_lang_Object;
}
@SuppressWarnings("static-method") private IType
getType2(LocalVariableAccess lva) {
return lva.localVariable.type;
}
@SuppressWarnings("static-method") private IType
getType2(FieldAccess fa) throws CompileException {
return fa.field.getType();
}
@SuppressWarnings("static-method") private IClass
getType2(ArrayLength al) {
return IClass.INT;
}
private IClass
getType2(ThisReference tr) throws CompileException {
return this.getIClass(tr);
}
@SuppressWarnings("static-method") private IClass
getType2(LambdaExpression le) throws CompileException {
throw UnitCompiler.compileException(le, "Compilation of lambda expression NYI");
}
@SuppressWarnings("static-method") private IClass
getType2(MethodReference mr) throws CompileException {
throw UnitCompiler.compileException(mr, "Compilation of method reference NYI");
}
@SuppressWarnings("static-method") private IClass
getType2(ClassInstanceCreationReference cicr) throws CompileException {
throw UnitCompiler.compileException(cicr, "Compilation of class instance creation reference NYI");
}
@SuppressWarnings("static-method") private IClass
getType2(ArrayCreationReference acr) throws CompileException {
throw UnitCompiler.compileException(acr, "Compilation of array creation reference NYI");
}
private IType
getType2(QualifiedThisReference qtr) throws CompileException { return this.getTargetIType(qtr); }
private IClass
getType2(ClassLiteral cl) { return this.iClassLoader.TYPE_java_lang_Class; }
private IType
getType2(Assignment a) throws CompileException { return this.getType(a.lhs); }
private IType
getType2(ConditionalExpression ce) throws CompileException {
IType mhsType = this.getType(ce.mhs);
IType rhsType = this.getType(ce.rhs);
if (mhsType == rhsType) {
// JLS7 15.25, list 1, bullet 1: "b ? T : T => T"
return mhsType;
} else
if (this.isUnboxingConvertible(mhsType) == rhsType) {
// JLS7 15.25, list 1, bullet 2: "b ? Integer : int => int"
return rhsType;
} else
if (this.isUnboxingConvertible(rhsType) == mhsType) {
// JLS7 15.25, list 1, bullet 2: "b ? int : Integer => int"
return mhsType;
} else
if (this.getConstantValue(ce.mhs) == null && !UnitCompiler.isPrimitive(rhsType)) {
// JLS7 15.25, list 1, bullet 3: "b ? null : String => String"
return rhsType;
} else
if (this.getConstantValue(ce.mhs) == null && this.isBoxingConvertible(rhsType) != null) {
// Undocumented JAVAC feature: "b ? null : 7 => Integer"
IClass result = this.isBoxingConvertible(rhsType);
assert result != null;
return result;
} else
if (!UnitCompiler.isPrimitive(mhsType) && this.getConstantValue(ce.rhs) == null) {
// JLS7 15.25, list 1, bullet 3: "b ? String : null => String"
return mhsType;
} else
if (this.isBoxingConvertible(mhsType) != null && this.getConstantValue(ce.rhs) == null) {
// Undocumented JAVAC feature: "b ? 7 : null => Integer"
IClass result = this.isBoxingConvertible(mhsType);
assert result != null;
return result;
} else
if (this.isConvertibleToPrimitiveNumeric(mhsType) && this.isConvertibleToPrimitiveNumeric(rhsType)) {
// JLS7 15.25, list 1, bullet 4, bullet 1: "b ? Byte : Short => short"
if (
(mhsType == IClass.BYTE || mhsType == this.iClassLoader.TYPE_java_lang_Byte)
&& (rhsType == IClass.SHORT || rhsType == this.iClassLoader.TYPE_java_lang_Short)
) return IClass.SHORT;
if (
(rhsType == IClass.BYTE || rhsType == this.iClassLoader.TYPE_java_lang_Byte)
&& (mhsType == IClass.SHORT || mhsType == this.iClassLoader.TYPE_java_lang_Short)
) return IClass.SHORT;
// JLS7 15.25, list 1, bullet 4, bullet 2: "b ? (byte) 1 : byte => byte"
Object rhscv = this.getConstantValue(ce.rhs);
if (
(mhsType == IClass.BYTE || mhsType == IClass.SHORT || mhsType == IClass.CHAR)
&& rhscv != null
&& this.constantAssignmentConversion(ce.rhs, rhscv, mhsType) != null
) return mhsType;
Object mhscv = this.getConstantValue(ce.mhs);
if (
(rhsType == IClass.BYTE || rhsType == IClass.SHORT || rhsType == IClass.CHAR)
&& mhscv != null
&& this.constantAssignmentConversion(ce.mhs, mhscv, rhsType) != null
) return rhsType;
// JLS7 15.25, list 1, bullet 4, bullet 3: "b ? 127 : byte => byte"
if (
mhsType == IClass.INT
&& rhsType == IClass.BYTE
&& UnitCompiler.isByteConstant(mhscv) != null
) {
// Fix up the constant to be a byte
ce.mhs.constantValue = UnitCompiler.isByteConstant(mhscv);
return IClass.BYTE;
}
if (
rhsType == IClass.INT
&& mhsType == IClass.BYTE
&& UnitCompiler.isByteConstant(rhscv) != null
) {
// Fix up the constant to be a byte
ce.rhs.constantValue = UnitCompiler.isByteConstant(rhscv);
return IClass.BYTE;
}
// JLS7 15.25, list 1, bullet 4, bullet 4: "b ? Integer : Double => double"
return this.binaryNumericPromotionType(
ce,
this.getUnboxedType(mhsType),
this.getUnboxedType(rhsType)
);
}
if (!UnitCompiler.isPrimitive(mhsType) || !UnitCompiler.isPrimitive(rhsType)) {
// JLS7 15.25, list 1, bullet 5: "b ? Base : Derived => Base"
// mhsType = (IClass) Objects.or(this.isBoxingConvertible(mhsType), mhsType);
// rhsType = (IClass) Objects.or(this.isBoxingConvertible(rhsType), rhsType);
return this.commonSupertype(mhsType, rhsType);
} else
{
this.compileError(
"Incompatible expression types \"" + mhsType + "\" and \"" + rhsType + "\"",
ce.getLocation()
);
return this.iClassLoader.TYPE_java_lang_Object;
}
}
private IType
getType2(Crement c) throws CompileException { return this.getType(c.operand); }
private IType
getType2(ArrayAccessExpression aae) throws CompileException {
IType componentType = UnitCompiler.getComponentType(this.getType(aae.lhs));
assert componentType != null : "null component type for " + aae;
return componentType;
}
private IType
getType2(FieldAccessExpression fae) throws CompileException {
this.determineValue(fae);
return this.getType(this.determineValue(fae));
}
private IType
getType2(SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
return this.getType(this.determineValue(scfae));
}
private IClass
getType2(UnaryOperation uo) throws CompileException {
if (uo.operator == "!") return IClass.BOOLEAN; // SUPPRESS CHECKSTYLE StringLiteralEquality
// SUPPRESS CHECKSTYLE StringLiteralEquality
if (uo.operator == "+" || uo.operator == "-" || uo.operator == "~") {
return this.unaryNumericPromotionType(uo, this.getUnboxedType(this.getType(uo.operand)));
}
this.compileError("Unexpected operator \"" + uo.operator + "\"", uo.getLocation());
return IClass.BOOLEAN;
}
@SuppressWarnings("static-method")
private IClass
getType2(Instanceof io) { return IClass.BOOLEAN; }
private IType
getType2(BinaryOperation bo) throws CompileException {
if (
// SUPPRESS CHECKSTYLE StringLiteralEquality:8
bo.operator == "||"
|| bo.operator == "&&"
|| bo.operator == "=="
|| bo.operator == "!="
|| bo.operator == "<"
|| bo.operator == ">"
|| bo.operator == "<="
|| bo.operator == ">="
) return IClass.BOOLEAN;
if (bo.operator == "|" || bo.operator == "^" || bo.operator == "&") { // SUPPRESS CHECKSTYLE StringLiteralEquality
IType lhsType = this.getType(bo.lhs);
return (
lhsType == IClass.BOOLEAN || lhsType == this.iClassLoader.TYPE_java_lang_Boolean
? IClass.BOOLEAN
: this.binaryNumericPromotionType(bo, lhsType, this.getType(bo.rhs))
);
}
if (bo.operator == "*" || bo.operator == "/" || bo.operator == "%" || bo.operator == "+" || bo.operator == "-") { // SUPPRESS CHECKSTYLE StringLiteralEquality
IClassLoader icl = this.iClassLoader;
// Unroll the operands of this binary operation.
Iterator ops = bo.unrollLeftAssociation();
IType lhsType = this.getType((Rvalue) ops.next());
// Check the far left operand type.
if (bo.operator == "+" && lhsType == icl.TYPE_java_lang_String) { // SUPPRESS CHECKSTYLE StringLiteralEquality
return icl.TYPE_java_lang_String;
}
// Determine the expression type.
lhsType = this.getUnboxedType(lhsType);
do {
IType rhsType = this.getUnboxedType(this.getType((Rvalue) ops.next()));
if (bo.operator == "+" && rhsType == icl.TYPE_java_lang_String) { // SUPPRESS CHECKSTYLE StringLiteralEquality
return icl.TYPE_java_lang_String;
}
lhsType = this.binaryNumericPromotionType(bo, lhsType, rhsType);
} while (ops.hasNext());
return lhsType;
}
if (bo.operator == "<<" || bo.operator == ">>" || bo.operator == ">>>") { // SUPPRESS CHECKSTYLE StringLiteralEquality
IType lhsType = this.getType(bo.lhs);
return this.unaryNumericPromotionType(bo, lhsType);
}
this.compileError("Unexpected operator \"" + bo.operator + "\"", bo.getLocation());
return this.iClassLoader.TYPE_java_lang_Object;
}
/**
* @return The type , or, iff type is a primitive wrapper type, the unwrapped type
*/
private IType
getUnboxedType(IType type) {
IClass c = this.isUnboxingConvertible(type);
return c != null ? c : type;
}
private IType
getType2(Cast c) throws CompileException {
return this.getType(c.targetType);
}
private IType
getType2(ParenthesizedExpression pe) throws CompileException {
return this.getType(pe.value);
}
private IClass
getType2(MethodInvocation mi) throws CompileException {
IMethod iMethod = mi.iMethod != null ? mi.iMethod : (mi.iMethod = this.findIMethod(mi));
return iMethod.getReturnType();
}
private IClass
getType2(SuperclassMethodInvocation scmi) throws CompileException {
return this.findIMethod(scmi).getReturnType();
}
private IType
getType2(NewClassInstance nci) throws CompileException {
if (nci.iType != null) return nci.iType;
assert nci.type != null;
return (nci.iType = this.getType(nci.type));
}
private IClass
getType2(NewAnonymousClassInstance naci) {
return this.resolve(naci.anonymousClassDeclaration);
}
private IType
getType2(ParameterAccess pa) throws CompileException {
return this.getLocalVariable(pa.formalParameter).type;
}
private IClass
getType2(NewArray na) throws CompileException {
return UnitCompiler.this.iClassLoader.getArrayIClass(
UnitCompiler.rawTypeOf(this.getType(na.type)),
na.dimExprs.length + na.dims
);
}
private IType
getType2(NewInitializedArray nia) throws CompileException {
IType at = nia.arrayType != null ? this.getType(nia.arrayType) : nia.arrayIClass;
assert at != null;
return at;
}
@SuppressWarnings("static-method") private IClass
getType2(IntegerLiteral il) {
String v = il.value;
char lastChar = v.charAt(v.length() - 1);
return lastChar == 'l' || lastChar == 'L' ? IClass.LONG : IClass.INT;
}
@SuppressWarnings("static-method") private IClass
getType2(FloatingPointLiteral fpl) {
String v = fpl.value;
char lastChar = v.charAt(v.length() - 1);
return lastChar == 'f' || lastChar == 'F' ? IClass.FLOAT : IClass.DOUBLE;
}
@SuppressWarnings("static-method") private IClass
getType2(BooleanLiteral bl) {
return IClass.BOOLEAN;
}
@SuppressWarnings("static-method") private IClass
getType2(CharacterLiteral cl) {
return IClass.CHAR;
}
private IClass
getType2(StringLiteral sl) {
return this.iClassLoader.TYPE_java_lang_String;
}
@SuppressWarnings("static-method") private IClass
getType2(NullLiteral nl) {
return IClass.NULL;
}
private IClass
getType2(SimpleConstant sl) {
Object v = sl.value;
if (v instanceof Byte) return IClass.BYTE;
if (v instanceof Short) return IClass.SHORT;
if (v instanceof Integer) return IClass.INT;
if (v instanceof Long) return IClass.LONG;
if (v instanceof Float) return IClass.FLOAT;
if (v instanceof Double) return IClass.DOUBLE;
if (v instanceof Boolean) return IClass.BOOLEAN;
if (v instanceof Character) return IClass.CHAR;
if (v instanceof String) return this.iClassLoader.TYPE_java_lang_String;
if (v == null) return IClass.NULL;
throw new InternalCompilerException(sl.getLocation(), "Invalid SimpleLiteral value type \"" + v.getClass() + "\"");
}
// ---------------- Atom.isType() ---------------
private boolean
isType(Atom a) throws CompileException {
Boolean result = (Boolean) a.accept(new AtomVisitor() {
@Override public Boolean visitPackage(Package p) { return UnitCompiler.this.isType2(p); }
@Override @Nullable public Boolean
visitType(Type t) {
return (Boolean) t.accept(new TypeVisitor() {
@Override public Boolean visitArrayType(ArrayType at) { return UnitCompiler.this.isType2(at); }
@Override public Boolean visitPrimitiveType(PrimitiveType bt) { return UnitCompiler.this.isType2(bt); }
@Override public Boolean visitReferenceType(ReferenceType rt) { return UnitCompiler.this.isType2(rt); }
@Override public Boolean visitRvalueMemberType(RvalueMemberType rmt) { return UnitCompiler.this.isType2(rmt); }
@Override public Boolean visitSimpleType(SimpleType st) { return UnitCompiler.this.isType2(st); }
});
}
@Override @Nullable public Boolean
visitRvalue(Rvalue rv) throws CompileException {
return (Boolean) rv.accept(new RvalueVisitor() {
@Override @Nullable public Boolean
visitLvalue(Lvalue lv) throws CompileException {
return (Boolean) lv.accept(new LvalueVisitor() {
@Override public Boolean visitAmbiguousName(AmbiguousName an) throws CompileException { return UnitCompiler.this.isType2(an); }
@Override public Boolean visitArrayAccessExpression(ArrayAccessExpression aae) { return UnitCompiler.this.isType2(aae); }
@Override public Boolean visitFieldAccess(FieldAccess fa) { return UnitCompiler.this.isType2(fa); }
@Override public Boolean visitFieldAccessExpression(FieldAccessExpression fae) { return UnitCompiler.this.isType2(fae); }
@Override public Boolean visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { return UnitCompiler.this.isType2(scfae); }
@Override public Boolean visitLocalVariableAccess(LocalVariableAccess lva) { return UnitCompiler.this.isType2(lva); }
@Override public Boolean visitParenthesizedExpression(ParenthesizedExpression pe) { return UnitCompiler.this.isType2(pe); }
});
}
@Override public Boolean visitArrayLength(ArrayLength al) { return UnitCompiler.this.isType2(al); }
@Override public Boolean visitAssignment(Assignment a) { return UnitCompiler.this.isType2(a); }
@Override public Boolean visitUnaryOperation(UnaryOperation uo) { return UnitCompiler.this.isType2(uo); }
@Override public Boolean visitBinaryOperation(BinaryOperation bo) { return UnitCompiler.this.isType2(bo); }
@Override public Boolean visitCast(Cast c) { return UnitCompiler.this.isType2(c); }
@Override public Boolean visitClassLiteral(ClassLiteral cl) { return UnitCompiler.this.isType2(cl); }
@Override public Boolean visitConditionalExpression(ConditionalExpression ce) { return UnitCompiler.this.isType2(ce); }
@Override public Boolean visitCrement(Crement c) { return UnitCompiler.this.isType2(c); }
@Override public Boolean visitInstanceof(Instanceof io) { return UnitCompiler.this.isType2(io); }
@Override public Boolean visitMethodInvocation(MethodInvocation mi) { return UnitCompiler.this.isType2(mi); }
@Override public Boolean visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { return UnitCompiler.this.isType2(smi); }
@Override public Boolean visitIntegerLiteral(IntegerLiteral il) { return UnitCompiler.this.isType2(il); }
@Override public Boolean visitFloatingPointLiteral(FloatingPointLiteral fpl) { return UnitCompiler.this.isType2(fpl); }
@Override public Boolean visitBooleanLiteral(BooleanLiteral bl) { return UnitCompiler.this.isType2(bl); }
@Override public Boolean visitCharacterLiteral(CharacterLiteral cl) { return UnitCompiler.this.isType2(cl); }
@Override public Boolean visitStringLiteral(StringLiteral sl) { return UnitCompiler.this.isType2(sl); }
@Override public Boolean visitNullLiteral(NullLiteral nl) { return UnitCompiler.this.isType2(nl); }
@Override public Boolean visitSimpleConstant(SimpleConstant sl) { return UnitCompiler.this.isType2(sl); }
@Override public Boolean visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { return UnitCompiler.this.isType2(naci); }
@Override public Boolean visitNewArray(NewArray na) { return UnitCompiler.this.isType2(na); }
@Override public Boolean visitNewInitializedArray(NewInitializedArray nia) { return UnitCompiler.this.isType2(nia); }
@Override public Boolean visitNewClassInstance(NewClassInstance nci) { return UnitCompiler.this.isType2(nci); }
@Override public Boolean visitParameterAccess(ParameterAccess pa) { return UnitCompiler.this.isType2(pa); }
@Override public Boolean visitQualifiedThisReference(QualifiedThisReference qtr) { return UnitCompiler.this.isType2(qtr); }
@Override public Boolean visitThisReference(ThisReference tr) { return UnitCompiler.this.isType2(tr); }
@Override public Boolean visitLambdaExpression(LambdaExpression le) { return UnitCompiler.this.isType2(le); }
@Override public Boolean visitMethodReference(MethodReference mr) { return UnitCompiler.this.isType2(mr); }
@Override public Boolean visitInstanceCreationReference(ClassInstanceCreationReference cicr) { return UnitCompiler.this.isType2(cicr); }
@Override public Boolean visitArrayCreationReference(ArrayCreationReference acr) { return UnitCompiler.this.isType2(acr); }
});
}
@Override @Nullable public Boolean
visitConstructorInvocation(ConstructorInvocation ci) { return false; }
});
assert result != null;
return result;
}
@SuppressWarnings("static-method") private boolean
isType2(Atom a) { return a instanceof Type; }
private boolean
isType2(AmbiguousName an) throws CompileException { return this.isType(this.reclassify(an)); }
/**
* Determines whether the given {@link IClass.IMember} is accessible in the given context, according to
* JLS7 6.6.1.BL1.B4. Issues a {@link #compileError(String)} if not.
*/
private boolean
isAccessible(IClass.IMember member, Scope contextScope) throws CompileException {
// You have to check that both the class and member are accessible in this scope.
IClass declaringIClass = member.getDeclaringIClass();
return (
this.isAccessible(declaringIClass, contextScope)
&& this.isAccessible(declaringIClass, member.getAccess(), contextScope)
);
}
/**
* Checks whether the given {@link IClass.IMember} is accessible in the given context, according to JLS7
* 6.6.1.BL1.B4. Issues a {@link #compileError(String)} if not.
*/
private void
checkAccessible(IClass.IMember member, Scope contextScope, Location location) throws CompileException {
// You have to check that both the class and member are accessible in this scope.
IClass declaringIClass = member.getDeclaringIClass();
this.checkAccessible(declaringIClass, contextScope, location);
this.checkMemberAccessible(declaringIClass, member, contextScope, location);
}
/**
* Determines whether a member (class, interface, field or method) declared in a given class is accessible from a
* given block statement context, according to JLS7 6.6.1.4.
*/
private boolean
isAccessible(IClass iClassDeclaringMember, Access memberAccess, Scope contextScope) throws CompileException {
return null == this.internalCheckAccessible(iClassDeclaringMember, memberAccess, contextScope);
}
/**
* Verifies that a member (class, interface, field or method) declared in a given class is accessible from a given
* block statement context, according to JLS7 6.6.1.4. Issue a {@link #compileError(String)} if not.
*/
private void
checkMemberAccessible(
IClass iClassDeclaringMember,
IClass.IMember member,
Scope contextScope,
Location location
) throws CompileException {
String message = this.internalCheckAccessible(iClassDeclaringMember, member.getAccess(), contextScope);
if (message != null) this.compileError(member.toString() + ": " + message, location);
}
/**
* @return a descriptive text iff a member declared in that {@link IClass} with that {@link Access} is inaccessible
*/
@Nullable private String
internalCheckAccessible(
IClass iClassDeclaringMember,
Access memberAccess,
Scope contextScope
) throws CompileException {
// At this point, memberAccess is PUBLIC, DEFAULT, PROTECTED or PRIVATE.
// PUBLIC members are always accessible.
if (memberAccess == Access.PUBLIC) return null;
// At this point, the member is DEFAULT, PROTECTED or PRIVATE accessible.
// Determine the class declaring the context.
IClass iClassDeclaringContext = null;
for (Scope s = contextScope; !(s instanceof CompilationUnit); s = s.getEnclosingScope()) {
if (s instanceof TypeDeclaration) {
iClassDeclaringContext = this.resolve((TypeDeclaration) s);
break;
}
}
// Access is always allowed for block statements declared in the same class as the member.
if (iClassDeclaringContext == iClassDeclaringMember) return null;
// Check whether the member and the context block statement are enclosed by the same top-level type.
if (
iClassDeclaringContext != null
&& !this.options.contains(JaninoOption.PRIVATE_MEMBERS_OF_ENCLOSING_AND_ENCLOSED_TYPES_INACCESSIBLE)
) {
IClass topLevelIClassEnclosingMember = iClassDeclaringMember;
for (IClass c = iClassDeclaringMember.getDeclaringIClass(); c != null; c = c.getDeclaringIClass()) {
topLevelIClassEnclosingMember = c;
}
IClass topLevelIClassEnclosingContextBlockStatement = iClassDeclaringContext;
for (
IClass c = iClassDeclaringContext.getDeclaringIClass();
c != null;
c = c.getDeclaringIClass()
) topLevelIClassEnclosingContextBlockStatement = c;
if (topLevelIClassEnclosingMember == topLevelIClassEnclosingContextBlockStatement) return null;
}
if (memberAccess == Access.PRIVATE) {
return "Private member cannot be accessed from type \"" + iClassDeclaringContext + "\".";
}
// At this point, the member is DEFAULT or PROTECTED accessible.
// Check whether the member and the context block statement are declared in the same package.
if (iClassDeclaringContext != null && Descriptor.areInSamePackage(
iClassDeclaringMember.getDescriptor(),
iClassDeclaringContext.getDescriptor()
)) return null;
if (memberAccess == Access.DEFAULT) {
return (
"Member with \"package\" access cannot be accessed from type \""
+ iClassDeclaringContext
+ "\"."
);
}
// At this point, the member is PROTECTED accessible.
// Check whether the class declaring the context block statement is a subclass of the class declaring the
// member or a nested class whose parent is a subclass
{
IClass parentClass = iClassDeclaringContext;
do {
assert parentClass != null;
if (iClassDeclaringMember.isAssignableFrom(parentClass)) {
return null;
}
parentClass = parentClass.getOuterIClass();
} while (parentClass != null);
}
return (
"Protected member cannot be accessed from type \""
+ iClassDeclaringContext
+ "\", which is neither declared in the same package as nor is a subclass of \""
+ iClassDeclaringMember
+ "\"."
);
}
/**
* Determines whether the given {@link IClass} is accessible in the given context, according to JLS7 6.6.1.2 and
* 6.6.1.4.
*/
private boolean
isAccessible(IClass type, Scope contextScope) throws CompileException {
return null == this.internalCheckAccessible(type, contextScope);
}
/**
* Checks whether the given {@link IClass} is accessible in the given context, according to JLS7 6.6.1.2 and
* 6.6.1.4. Issues a {@link #compileError(String)} if not.
*/
private void
checkAccessible(IClass type, Scope contextScope, Location location) throws CompileException {
String message = this.internalCheckAccessible(type, contextScope);
if (message != null) this.compileError(message, location);
}
/**
* @return An error message, or {@code null}
*/
@Nullable private String
internalCheckAccessible(IClass type, Scope contextScope) throws CompileException {
// Determine the type declaring the type.
IClass iClassDeclaringType = type.getDeclaringIClass();
// Check accessibility of package member type.
if (iClassDeclaringType == null) {
if (type.getAccess() == Access.PUBLIC) {
return null;
} else
if (type.getAccess() == Access.DEFAULT) {
// Determine the type declaring the context block statement.
IClass iClassDeclaringContextBlockStatement;
for (Scope s = contextScope;; s = s.getEnclosingScope()) {
if (s instanceof TypeDeclaration) {
iClassDeclaringContextBlockStatement = this.resolve((TypeDeclaration) s);
break;
}
if (s instanceof EnclosingScopeOfTypeDeclaration) {
iClassDeclaringContextBlockStatement = this.resolve(
((EnclosingScopeOfTypeDeclaration) s).typeDeclaration
);
break;
}
}
// Check whether the type is accessed from within the same package.
String packageDeclaringType = Descriptor.getPackageName(type.getDescriptor());
String contextPackage = Descriptor.getPackageName(iClassDeclaringContextBlockStatement.getDescriptor());
if (
packageDeclaringType == null
? contextPackage != null
: !packageDeclaringType.equals(contextPackage)
) return "\"" + type + "\" is inaccessible from this package";
return null;
} else
{
throw new InternalCompilerException((
"\"" + type + "\" has unexpected access \"" + type.getAccess() + "\""
));
}
}
// "type" is a member type at this point.
return this.internalCheckAccessible(iClassDeclaringType, type.getAccess(), contextScope);
}
private Type
toTypeOrCompileException(Atom a) throws CompileException {
Type result = a.toType();
if (result == null) {
this.compileError("Expression \"" + a.toString() + "\" is not a type", a.getLocation());
return new SimpleType(a.getLocation(), this.iClassLoader.TYPE_java_lang_Object);
}
return result;
}
private Rvalue
toRvalueOrCompileException(final Atom a) throws CompileException {
Rvalue result = a.toRvalue();
if (result == null) {
this.compileError("Expression \"" + a.toString() + "\" is not an rvalue", a.getLocation());
return new StringLiteral(a.getLocation(), "\"X\"");
}
return result;
}
private Lvalue
toLvalueOrCompileException(final Atom a) throws CompileException {
Lvalue result = a.toLvalue();
if (result == null) {
this.compileError("Expression \"" + a.toString() + "\" is not an lvalue", a.getLocation());
return new Lvalue(a.getLocation()) {
@Override @Nullable public R
accept(LvalueVisitor visitor) { return null; }
@Override public String
toString() { return a.toString(); }
};
}
return result;
}
/**
* Copies the values of the synthetic parameters of this constructor ("this$..." and "val$...") to the synthetic
* fields of the object ("this$..." and "val$...").
*/
void
assignSyntheticParametersToSyntheticFields(ConstructorDeclarator cd) throws CompileException {
for (IClass.IField sf : cd.getDeclaringClass().syntheticFields.values()) {
LocalVariable syntheticParameter = (LocalVariable) cd.syntheticParameters.get(sf.getName());
if (syntheticParameter == null) {
throw new InternalCompilerException(cd.getLocation(), (
"SNO: Synthetic parameter for synthetic field \""
+ sf.getName()
+ "\" not found"
));
}
ExpressionStatement es = new ExpressionStatement(new Assignment(
cd.getLocation(), // location
new FieldAccess( // lhs
cd.getLocation(), // location
new ThisReference(cd.getLocation()), // lhs
sf // field
),
"=", // operator
new LocalVariableAccess( // rhs
cd.getLocation(), // location
syntheticParameter // localVariable
)
));
es.setEnclosingScope(cd);
this.compile(es);
}
}
/**
* Compiles the instance variable initializers and the instance initializers in their lexical order.
*/
void
initializeInstanceVariablesAndInvokeInstanceInitializers(ConstructorDeclarator cd) throws CompileException {
// Compilation of block statements can create synthetic variables, so we must not use an iterator.
List vdais = cd.getDeclaringClass().fieldDeclarationsAndInitializers;
for (int i = 0; i < vdais.size(); i++) {
BlockStatement bs = (BlockStatement) vdais.get(i);
if (bs instanceof Initializer && ((Initializer) bs).isStatic()) continue;
if (bs instanceof FieldDeclaration && ((FieldDeclaration) bs).isStatic()) continue;
if (!this.compile(bs)) {
this.compileError(
"Instance variable declarator or instance initializer does not complete normally",
bs.getLocation()
);
}
}
}
/**
* Statements that jump out of blocks ({@code return}, {@code break}, {@code continue}) must call this method to
* make sure that the {@code finally} clauses of all {@code try ... catch} and {@code synchronized} statements are
* executed.
*/
private void
leaveStatements(Scope from, Scope to) throws CompileException {
Scope prev = null;
for (Scope s = from; s != to; s = s.getEnclosingScope()) {
if (
s instanceof BlockStatement
&& !(s instanceof TryStatement && ((TryStatement) s).finallY == prev)
) {
this.leave((BlockStatement) s);
}
prev = s;
}
}
/**
* The LHS operand of type lhsType is expected on the stack.
*
* The following operators are supported: {@code | ^ & * / % + - << >> >>>}
*
*/
private IType
compileArithmeticBinaryOperation(
Locatable locatable,
IType lhsType,
String operator,
Rvalue rhs
) throws CompileException {
return this.compileArithmeticOperation(
locatable,
lhsType,
Arrays.asList(rhs).iterator(),
operator
);
}
/**
* Executes an arithmetic operation on a sequence of operands . If type is non-{@code null},
* then the first operand with that type is already on the stack.
*
* The following operators are supported: {@code | ^ & * / % + - << >> >>>}
*
*/
private IType
compileArithmeticOperation(
final Locatable locatable,
@Nullable IType firstOperandType,
Iterator operands,
String operator
) throws CompileException {
// A very special case.
if (operator == "+" && firstOperandType == this.iClassLoader.TYPE_java_lang_String) { // SUPPRESS CHECKSTYLE StringLiteralEquality
assert firstOperandType != null;
return this.compileStringConcatenation(locatable, firstOperandType, (Rvalue) operands.next(), operands);
}
IType type = firstOperandType == null ? this.compileGetValue((Rvalue) operands.next()) : firstOperandType;
// Operator which is allowed for BYTE, SHORT, INT, LONG and BOOLEAN operands?
if (operator == "|" || operator == "^" || operator == "&") { // SUPPRESS CHECKSTYLE StringLiteralEquality:5
while (operands.hasNext()) {
Rvalue operand = (Rvalue) operands.next();
IType rhsType = this.getType(operand);
if (this.isConvertibleToPrimitiveNumeric(type) && this.isConvertibleToPrimitiveNumeric(rhsType)) {
IClass promotedType = this.binaryNumericPromotionType(
operand,
this.getUnboxedType(type),
this.getUnboxedType(rhsType)
);
if (promotedType != IClass.INT && promotedType != IClass.LONG) {
throw new CompileException("Invalid operand type " + promotedType, operand.getLocation());
}
this.numericPromotion(operand, this.convertToPrimitiveNumericType(operand, type), promotedType);
this.compileGetValue(operand);
this.numericPromotion(operand, this.convertToPrimitiveNumericType(operand, rhsType), promotedType);
this.andOrXor(operand, operator);
type = promotedType;
} else
if (
(type == IClass.BOOLEAN || this.getUnboxedType(type) == IClass.BOOLEAN)
&& (rhsType == IClass.BOOLEAN || this.getUnboxedType(rhsType) == IClass.BOOLEAN)
) {
IClassLoader icl = this.iClassLoader;
if (type == icl.TYPE_java_lang_Boolean) {
this.unboxingConversion(locatable, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN);
}
this.compileGetValue(operand);
if (rhsType == icl.TYPE_java_lang_Boolean) {
this.unboxingConversion(locatable, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN);
}
this.andOrXor(operand, operator);
type = IClass.BOOLEAN;
} else
{
this.compileError((
"Operator \""
+ operator
+ "\" not defined on types \""
+ type
+ "\" and \""
+ rhsType
+ "\""
), locatable.getLocation());
type = IClass.INT;
}
}
return type;
}
// Operator which is allowed for INT, LONG, FLOAT, DOUBLE and (for operator '+') STRING operands?
if (operator == "*" || operator == "/" || operator == "%" || operator == "+" || operator == "-") { // SUPPRESS CHECKSTYLE StringLiteralEquality:6
while (operands.hasNext()) {
Rvalue operand = (Rvalue) operands.next();
// String concatenation?
if (operator == "+" && ( // SUPPRESS CHECKSTYLE StringLiteralEquality
type == this.iClassLoader.TYPE_java_lang_String
|| this.getType(operand) == this.iClassLoader.TYPE_java_lang_String
)) return this.compileStringConcatenation(locatable, type, operand, operands);
// It's a numeric arithmetic operation.
IType rhsType = this.getType(operand);
IClass promotedType = this.binaryNumericPromotionType(
operand,
this.getUnboxedType(type),
this.getUnboxedType(rhsType)
);
this.numericPromotion(operand, this.convertToPrimitiveNumericType(operand, type), promotedType);
this.compileGetValue(operand);
this.numericPromotion(operand, this.convertToPrimitiveNumericType(operand, rhsType), promotedType);
this.mulDivRemAddSub(operand, operator);
type = promotedType;
}
return type;
}
// Operator which is allowed for BYTE, SHORT, INT and LONG lhs operand and BYTE, SHORT, INT or LONG rhs operand?
if (operator == "<<" || operator == ">>" || operator == ">>>") { // SUPPRESS CHECKSTYLE StringLiteralEquality:4
while (operands.hasNext()) {
Rvalue operand = (Rvalue) operands.next();
type = this.unaryNumericPromotion(operand, type);
IType rhsType = this.compileGetValue(operand);
IClass promotedRhsType = this.unaryNumericPromotion(operand, rhsType);
if (promotedRhsType == IClass.INT) {
;
} else
if (promotedRhsType == IClass.LONG) {
this.l2i(operand);
} else
{
this.compileError(
"Shift distance of type \"" + rhsType + "\" is not allowed",
locatable.getLocation()
);
}
this.shift(operand, operator);
}
return type;
}
throw new InternalCompilerException(locatable.getLocation(), "Unexpected operator \"" + operator + "\"");
}
/**
* @param type The type of the first operand, which is already on the stack
* @param secondOperand The second operand
* @param operands All following operands
*/
private IClass
compileStringConcatenation(
final Locatable locatable,
IType type,
final Rvalue secondOperand,
Iterator operands
) throws CompileException {
// Convert the first operand (which is already on the operand stack) to "String".
this.stringConversion(locatable, type);
// Compute list of operands and merge consecutive constant operands.
List tmp = new ArrayList<>();
for (Rvalue nextOperand = secondOperand; nextOperand != null;) {
Object cv = this.getConstantValue(nextOperand);
if (cv == UnitCompiler.NOT_CONSTANT) {
// Non-constant operand.
tmp.add(nextOperand);
nextOperand = operands.hasNext() ? (Rvalue) operands.next() : null;
} else
{
// Constant operand. Check to see whether the next operand is also constant.
if (operands.hasNext()) {
nextOperand = (Rvalue) operands.next();
Object cv2 = this.getConstantValue(nextOperand);
if (cv2 != UnitCompiler.NOT_CONSTANT) {
StringBuilder sb = new StringBuilder(String.valueOf(cv)).append(cv2);
for (;;) {
if (!operands.hasNext()) {
nextOperand = null;
break;
}
nextOperand = (Rvalue) operands.next();
Object cv3 = this.getConstantValue(nextOperand);
if (cv3 == UnitCompiler.NOT_CONSTANT) break;
sb.append(cv3);
}
cv = sb.toString();
}
} else
{
nextOperand = null;
}
// Break long string constants up into UTF8-able chunks.
for (final String s : UnitCompiler.makeUtf8Able(String.valueOf(cv))) {
tmp.add(new SimpleConstant(locatable.getLocation(), s));
}
}
}
// At this point "tmp" contains an optimized sequence of Strings (representing constant portions) and Rvalues
// (non-constant portions).
if (tmp.size() <= UnitCompiler.STRING_CONCAT_LIMIT - 1) {
// String concatenation through "a.concat(b).concat(c)".
for (Rvalue operand : tmp) {
// "s.concat(String.valueOf(operand))"
UnitCompiler.this.stringConversion(operand, UnitCompiler.this.compileGetValue(operand));
this.invokeMethod(locatable, this.iClassLoader.METH_java_lang_String__concat__java_lang_String);
}
return this.iClassLoader.TYPE_java_lang_String;
}
// String concatenation through "new StringBuilder(a).append(b).append(c).append(d).toString()".
this.neW(locatable, this.iClassLoader.TYPE_java_lang_StringBuilder);
this.dupx(locatable);
this.swap(locatable);
this.invokeConstructor(locatable, this.iClassLoader.CTOR_java_lang_StringBuilder__java_lang_String);
this.getCodeContext().popUninitializedVariableOperand();
this.getCodeContext().pushObjectOperand(Descriptor.JAVA_LANG_STRINGBUILDER);
for (Iterator it = tmp.iterator(); it.hasNext();) {
Rvalue operand = (Rvalue) it.next();
// "sb.append(operand)"
IType t = UnitCompiler.this.compileGetValue(operand);
this.invokeMethod(locatable, (
t == IClass.BYTE ? this.iClassLoader.METH_java_lang_StringBuilder__append__int :
t == IClass.SHORT ? this.iClassLoader.METH_java_lang_StringBuilder__append__int :
t == IClass.INT ? this.iClassLoader.METH_java_lang_StringBuilder__append__int :
t == IClass.LONG ? this.iClassLoader.METH_java_lang_StringBuilder__append__long :
t == IClass.FLOAT ? this.iClassLoader.METH_java_lang_StringBuilder__append__float :
t == IClass.DOUBLE ? this.iClassLoader.METH_java_lang_StringBuilder__append__double :
t == IClass.CHAR ? this.iClassLoader.METH_java_lang_StringBuilder__append__char :
t == IClass.BOOLEAN ? this.iClassLoader.METH_java_lang_StringBuilder__append__boolean :
this.iClassLoader.METH_java_lang_StringBuilder__append__java_lang_Object
));
}
// "StringBuilder.toString()":
this.invokeMethod(locatable, this.iClassLoader.METH_java_lang_StringBuilder__toString);
return this.iClassLoader.TYPE_java_lang_String;
}
/**
* Helper interface for string conversion.
*/
interface Compilable { void compile() throws CompileException; }
/**
* Converts object of type "sourceType" to type "String" (JLS7 15.18.1.1).
*/
private void
stringConversion(Locatable locatable, IType sourceType) throws CompileException {
IClass sourceClass = UnitCompiler.rawTypeOf(sourceType);
this.invokeMethod(locatable, (
sourceClass == IClass.BYTE ? this.iClassLoader.METH_java_lang_String__valueOf__int :
sourceClass == IClass.SHORT ? this.iClassLoader.METH_java_lang_String__valueOf__int :
sourceClass == IClass.INT ? this.iClassLoader.METH_java_lang_String__valueOf__int :
sourceClass == IClass.LONG ? this.iClassLoader.METH_java_lang_String__valueOf__long :
sourceClass == IClass.FLOAT ? this.iClassLoader.METH_java_lang_String__valueOf__float :
sourceClass == IClass.DOUBLE ? this.iClassLoader.METH_java_lang_String__valueOf__double :
sourceClass == IClass.CHAR ? this.iClassLoader.METH_java_lang_String__valueOf__char :
sourceClass == IClass.BOOLEAN ? this.iClassLoader.METH_java_lang_String__valueOf__boolean :
this.iClassLoader.METH_java_lang_String__valueOf__java_lang_Object
));
}
/**
* Expects the object to initialize on the stack.
*
* Notice: This method is used both for explicit constructor invocation (first statement of a constructor body)
* and implicit constructor invocation (right after NEW).
*
*
* @param enclosingInstance Used if the target class is an inner class
*/
private void
invokeConstructor(
Locatable locatable,
Scope scope,
@Nullable Rvalue enclosingInstance,
IType targetType,
Rvalue[] arguments
) throws CompileException {
IClass rawTargetType = UnitCompiler.rawTypeOf(targetType);
// Find constructors.
IClass.IConstructor[] iConstructors = rawTargetType.getDeclaredIConstructors();
if (iConstructors.length == 0) {
throw new InternalCompilerException(
locatable.getLocation(),
"SNO: Target class \"" + rawTargetType.getDescriptor() + "\" has no constructors"
);
}
IClass.IConstructor iConstructor = (IClass.IConstructor) this.findMostSpecificIInvocable(
locatable, // l
iConstructors, // iInvocables
arguments, // arguments
scope // contextScope
);
// Check exceptions that the constructor may throw.
IClass[] thrownExceptions = iConstructor.getThrownExceptions();
for (IClass te : thrownExceptions) {
this.checkThrownException(locatable, te, scope);
}
// Enum constant: Pass constant name and ordinal as synthetic parameters.
ENUM_CONSTANT:
if (
scope instanceof FieldDeclaration
&& scope.getEnclosingScope() instanceof EnumDeclaration
) {
FieldDeclaration fd = (FieldDeclaration) scope;
EnumDeclaration ed = (EnumDeclaration) fd.getEnclosingScope();
if (fd.variableDeclarators.length != 1) break ENUM_CONSTANT;
String fieldName = fd.variableDeclarators[0].name;
int ordinal = 0;
for (EnumConstant ec : ed.getConstants()) {
if (fieldName.equals(ec.name)) {
// Now we know that this field IS an enum constant.
this.consT(locatable, fieldName);
this.consT(locatable, ordinal);
break ENUM_CONSTANT;
}
ordinal++;
}
}
// Pass enclosing instance as a synthetic parameter.
if (enclosingInstance != null) {
IClass outerIClass = rawTargetType.getOuterIClass();
if (outerIClass != null) {
IType eiic = this.compileGetValue(enclosingInstance);
if (!UnitCompiler.isAssignableFrom(outerIClass, eiic)) {
this.compileError(
"Type of enclosing instance (\"" + eiic + "\") is not assignable to \"" + outerIClass + "\"",
locatable.getLocation()
);
}
}
}
// Pass local variables to constructor as synthetic parameters.
{
IClass.IField[] syntheticFields = rawTargetType.getSyntheticIFields();
// Determine enclosing function declarator and type declaration.
TypeBodyDeclaration scopeTbd;
TypeDeclaration scopeTypeDeclaration;
{
Scope s = scope;
for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope());
scopeTbd = (TypeBodyDeclaration) s;
scopeTypeDeclaration = scopeTbd.getDeclaringType();
}
if (!(scopeTypeDeclaration instanceof AbstractClassDeclaration)) {
if (syntheticFields.length > 0) {
throw new InternalCompilerException(locatable.getLocation(), "SNO: Target class has synthetic fields");
}
}
// Notice: Constructor invocations can also occur in interface declarations in constant initializers.
if (scopeTypeDeclaration instanceof AbstractClassDeclaration) {
AbstractClassDeclaration scopeClassDeclaration = (AbstractClassDeclaration) scopeTypeDeclaration;
for (IClass.IField sf : syntheticFields) {
if (!sf.getName().startsWith("val$")) continue;
IClass.IField eisf = (IClass.IField) scopeClassDeclaration.syntheticFields.get(sf.getName());
if (eisf != null) {
if (scopeTbd instanceof MethodDeclarator) {
this.load(locatable, this.resolve(scopeClassDeclaration), 0);
this.getfield(locatable, eisf);
} else
if (scopeTbd instanceof ConstructorDeclarator) {
ConstructorDeclarator constructorDeclarator = (ConstructorDeclarator) scopeTbd;
LocalVariable syntheticParameter = (
(LocalVariable) constructorDeclarator.syntheticParameters.get(sf.getName())
);
if (syntheticParameter == null) {
this.compileError((
"Compiler limitation: Constructor cannot access local variable \""
+ sf.getName().substring(4)
+ "\" declared in an enclosing block because none of the methods accesses it. "
+ "As a workaround, declare a dummy method that accesses the local variable."
), locatable.getLocation());
this.consT(locatable, (Object) null);
} else {
this.load(locatable, syntheticParameter);
}
} else
if (scopeTbd instanceof FieldDeclaration) {
this.compileError((
"Compiler limitation: Field initializers cannot access local variable \""
+ sf.getName().substring(4)
+ "\" declared in an enclosing block because none of the methods accesses it. "
+ "As a workaround, declare a dummy method that accesses the local variable."
), locatable.getLocation());
this.consT(locatable, (Object) null);
} else
{
throw new AssertionError(scopeTbd);
}
} else {
String localVariableName = sf.getName().substring(4);
LocalVariable lv;
DETERMINE_LV: {
Scope s;
// Does one of the enclosing blocks declare a local variable with that name?
for (s = scope; s instanceof BlockStatement; s = s.getEnclosingScope()) {
BlockStatement bs = (BlockStatement) s;
Scope es = bs.getEnclosingScope();
List extends BlockStatement> statements;
if (es instanceof Block) {
statements = ((Block) es).statements;
} else
if (es instanceof FunctionDeclarator) {
statements = ((FunctionDeclarator) es).statements;
} else
if (es instanceof ForEachStatement) {
FunctionDeclarator.FormalParameter fp = ((ForEachStatement) es).currentElement;
if (fp.name.equals(localVariableName)) {
lv = this.getLocalVariable(fp);
break DETERMINE_LV;
}
continue;
} else
{
continue;
}
if (statements != null) {
for (BlockStatement bs2 : statements) {
if (bs2 == bs) break;
if (bs2 instanceof LocalVariableDeclarationStatement) {
LocalVariableDeclarationStatement lvds = (
(LocalVariableDeclarationStatement) bs2
);
for (VariableDeclarator vd : lvds.variableDeclarators) {
if (vd.name.equals(localVariableName)) {
lv = this.getLocalVariable(lvds, vd);
break DETERMINE_LV;
}
}
}
}
}
}
// Does the declaring function declare a parameter with that name?
while (!(s instanceof FunctionDeclarator)) s = s.getEnclosingScope();
FunctionDeclarator fd = (FunctionDeclarator) s;
for (FormalParameter fp : fd.formalParameters.parameters) {
if (fp.name.equals(localVariableName)) {
lv = this.getLocalVariable(fp);
break DETERMINE_LV;
}
}
throw new InternalCompilerException(fd.getLocation(), (
"SNO: Synthetic field \""
+ sf.getName()
+ "\" neither maps a synthetic field of an enclosing instance nor a local variable"
));
}
this.load(locatable, lv);
}
}
}
}
// Evaluate constructor arguments.
Rvalue[] adjustedArgs = null;
IClass[] parameterTypes = iConstructor.getParameterTypes();
int actualSize = arguments.length;
if (iConstructor.isVarargs() && iConstructor.argsNeedAdjust()) {
adjustedArgs = new Rvalue[parameterTypes.length];
Rvalue[] lastArgs = new Rvalue[actualSize - parameterTypes.length + 1];
for (int i = 0, j = parameterTypes.length - 1; i < lastArgs.length; ++i, ++j) {
lastArgs[i] = arguments[j];
}
for (int i = parameterTypes.length - 2; i >= 0; --i) {
adjustedArgs[i] = arguments[i];
}
Location loc = (lastArgs.length == 0 ? locatable : lastArgs[lastArgs.length - 1]).getLocation();
adjustedArgs[adjustedArgs.length - 1] = new NewInitializedArray(
loc, // location
parameterTypes[parameterTypes.length - 1], // arrayIClass
new ArrayInitializer(loc, lastArgs) // arrayInitializer
);
arguments = adjustedArgs;
}
for (int i = 0; i < arguments.length; ++i) {
this.assignmentConversion(
locatable, // locatable
this.compileGetValue(arguments[i]), // sourceType
parameterTypes[i], // targetType
this.getConstantValue(arguments[i]) // constantValue
);
}
// Invoke!
// Notice that the method descriptor is "iConstructor.getDescriptor()", prepended with the synthetic parameters.
this.invokeConstructor(locatable, iConstructor);
}
/**
* @return The {@link IField}s that are declared by the fieldDeclaration
*/
private IClass.IField[]
compileFields(final FieldDeclaration fieldDeclaration) {
IClass.IField[] result = new IClass.IField[fieldDeclaration.variableDeclarators.length];
for (int i = 0; i < result.length; ++i) {
VariableDeclarator vd = fieldDeclaration.variableDeclarators[i];
result[i] = this.compileField(
fieldDeclaration.getDeclaringType(),
fieldDeclaration.getAnnotations(),
fieldDeclaration.getAccess(),
fieldDeclaration.isStatic(),
fieldDeclaration.isFinal(),
fieldDeclaration.type,
vd.brackets,
vd.name,
vd.initializer
);
}
return result;
}
/**
* Compiles one variable declarator into an {@link IField}.
*
* Example: "b" in in the variable declaration
*
*
* class Foo {
* @Deprecated private int[] a, b[], c;
* }
*
*
* @param declaringType "{@code class Foo}"
* @param type "{@code int[]}"
*/
private IField
compileField(
final TypeDeclaration declaringType,
final Annotation[] annotations,
final Access access,
final boolean statiC,
final boolean finaL,
final Type type,
final int brackets,
final String name,
@Nullable final ArrayInitializerOrRvalue initializer
) {
return this.resolve(declaringType).new IField() {
@Nullable private IAnnotation[] ias;
// Implement IMember.
@Override public Access
getAccess() { return declaringType instanceof InterfaceDeclaration ? Access.PUBLIC : access; }
@Override public IAnnotation[]
getAnnotations() {
if (this.ias != null) return this.ias;
try {
return (this.ias = UnitCompiler.this.toIAnnotations(annotations));
} catch (CompileException ce) {
throw new InternalCompilerException(declaringType.getLocation(), null, ce);
}
}
// Implement "IField".
@Override public boolean
isStatic() { return declaringType instanceof InterfaceDeclaration || statiC; }
@Override public IClass
getType() throws CompileException {
return UnitCompiler.this.iClassLoader.getArrayIClass(
UnitCompiler.this.getRawType(type),
brackets
);
}
@Override public String
getName() { return name; }
@Override @Nullable public Object
getConstantValue() throws CompileException {
if (finaL && initializer != null) {
Object constantInitializerValue = UnitCompiler.this.getConstantValue(initializer);
if (constantInitializerValue != UnitCompiler.NOT_CONSTANT) {
return UnitCompiler.this.constantAssignmentConversion(
initializer, // locatable
constantInitializerValue, // value
this.getType() // targetType
);
}
}
return UnitCompiler.NOT_CONSTANT;
}
};
}
/**
* Determines the non-constant-final initializer of the given {@link VariableDeclarator}.
*
* @return {@code null} if the variable is declared without an initializer or if the initializer is
* constant-final
*/
@Nullable ArrayInitializerOrRvalue
getNonConstantFinalInitializer(FieldDeclaration fd, VariableDeclarator vd) throws CompileException {
// Check if optional initializer exists.
if (vd.initializer == null) return null;
// Check if initializer is constant-final.
if (
fd.isStatic()
&& fd.isFinal()
&& vd.initializer instanceof Rvalue
&& this.getConstantValue((Rvalue) vd.initializer) != UnitCompiler.NOT_CONSTANT
) return null;
return vd.initializer;
}
private Atom
reclassify(AmbiguousName an) throws CompileException {
if (an.reclassified != null) return an.reclassified;
return (an.reclassified = this.reclassifyName(an.getLocation(), an.getEnclosingScope(), an.identifiers, an.n));
}
private IAnnotation[]
toIAnnotations(Annotation[] annotations) throws CompileException {
IAnnotation[] result = new IAnnotation[annotations.length];
for (int i = 0; i < result.length; i++) result[i] = this.toIAnnotation(annotations[i]);
return result;
}
private IAnnotation
toIAnnotation(Annotation annotation) throws CompileException {
IAnnotation result = (IAnnotation) annotation.accept(
new AnnotationVisitor() {
@Override public IAnnotation
visitMarkerAnnotation(final MarkerAnnotation ma) throws CompileException {
return this.toIAnnotation(ma.type, new ElementValuePair[0]);
}
@Override public IAnnotation
visitSingleElementAnnotation(SingleElementAnnotation sea) throws CompileException {
return this.toIAnnotation(
sea.type,
new ElementValuePair[] { new ElementValuePair("value", sea.elementValue) }
);
}
@Override public IAnnotation
visitNormalAnnotation(NormalAnnotation na) throws CompileException {
return this.toIAnnotation(na.type, na.elementValuePairs);
}
private IAnnotation
toIAnnotation(final Type type, ElementValuePair[] elementValuePairs) throws CompileException {
final Map m = new HashMap<>();
for (ElementValuePair evp : elementValuePairs) {
m.put(evp.identifier, this.toObject(evp.elementValue));
}
return new IAnnotation() {
@Override public Object
getElementValue(String name) { return m.get(name); }
@Override public IType
getAnnotationType() throws CompileException { return UnitCompiler.this.getType(type); }
};
}
/**
* @return A wrapped primitive value, a {@link String}, an {@link IClass} (representing a class
* literal), an {@link IClass.IField} (representing an enum constant), or an {@link Object}[]
* array containing any of the previously described
*/
private Object
toObject(ElementValue ev) throws CompileException {
try {
Object result = ev.accept(new ElementValueVisitor() {
@Override public Object
visitRvalue(Rvalue rv) throws CompileException {
if (rv instanceof AmbiguousName) {
AmbiguousName an = (AmbiguousName) rv;
rv = UnitCompiler.this.reclassify(an).toRvalueOrCompileException();
}
// Class literal?
if (rv instanceof ClassLiteral) {
ClassLiteral cl = (ClassLiteral) rv;
return UnitCompiler.this.getType(cl.type);
}
// Enum constant?
if (rv instanceof FieldAccess) {
FieldAccess fa = (FieldAccess) rv;
return fa.field;
}
Object result = UnitCompiler.this.getConstantValue(rv);
if (result == null) {
UnitCompiler.this.compileError(
"Null value not allowed as an element value",
rv.getLocation()
);
return 1;
}
if (result == UnitCompiler.NOT_CONSTANT) {
UnitCompiler.this.compileError(
"Element value is not a constant expression",
rv.getLocation()
);
return 1;
}
return result;
}
@Override public Object
visitAnnotation(Annotation a) throws CompileException {
return UnitCompiler.this.toIAnnotation(a);
}
@Override public Object
visitElementValueArrayInitializer(ElementValueArrayInitializer evai)
throws CompileException {
Object[] result = new Object[evai.elementValues.length];
for (int i = 0; i < result.length; i++) {
result[i] = toObject(evai.elementValues[i]);
}
return result;
}
});
assert result != null;
return result;
} catch (/*CompileException*/ Exception ce) {
if (ce instanceof CompileException) throw (CompileException) ce;
throw new IllegalStateException(ce);
}
}
}
);
assert result != null;
return result;
}
/**
* Reclassifies the ambiguous name consisting of the first n of the identifiers (JLS7
* 6.5.2.2).
*/
private Atom
reclassifyName(Location location, Scope scope, final String[] identifiers, int n) throws CompileException {
if (n == 1) return this.reclassifyName(location, scope, identifiers[0]);
// 6.5.2.2
Atom lhs = this.reclassifyName(location, scope, identifiers, n - 1);
String rhs = identifiers[n - 1];
// 6.5.2.2.1
UnitCompiler.LOGGER.log(Level.FINE, "lhs={0}", lhs);
if (lhs instanceof Package) {
String className = ((Package) lhs).name + '.' + rhs;
IClass result = this.findTypeByName(location, className);
if (result != null) return new SimpleType(location, result);
return new Package(location, className);
}
// 6.5.2.2.3.2 EXPRESSION.length
if ("length".equals(rhs) && UnitCompiler.isArray(this.getType(lhs))) {
ArrayLength al = new ArrayLength(location, this.toRvalueOrCompileException(lhs));
if (!(scope instanceof BlockStatement)) {
this.compileError("\".length\" only allowed in expression context");
return al;
}
al.setEnclosingScope(scope);
return al;
}
IType lhsType = this.getType(lhs);
// Notice: Don't need to check for 6.5.2.2.2.1 TYPE.METHOD and 6.5.2.2.3.1 EXPRESSION.METHOD here because that
// has been done before.
{
IClass.IField field = this.findIField(UnitCompiler.rawTypeOf(lhsType), rhs, location);
if (field != null) {
// 6.5.2.2.2.2 TYPE.FIELD
// 6.5.2.2.3.2 EXPRESSION.FIELD
FieldAccess fa = new FieldAccess(
location,
lhs,
field
);
fa.setEnclosingScope(scope);
return fa;
}
}
IClass[] classes = UnitCompiler.rawTypeOf(lhsType).getDeclaredIClasses();
for (final IClass memberType : classes) {
String name = Descriptor.toClassName(memberType.getDescriptor());
name = name.substring(name.lastIndexOf('$') + 1);
if (name.equals(rhs)) {
// 6.5.2.2.2.3 TYPE.TYPE
// 6.5.2.2.3.3 EXPRESSION.TYPE
return new SimpleType(location, memberType);
}
}
this.compileError(
"\"" + rhs + "\" is neither a method, a field, nor a member class of \"" + lhsType + "\"",
location
);
return new Atom(location) {
@Override @Nullable public R
accept(AtomVisitor visitor) { return null; }
@Override public String
toString() { return Java.join(identifiers, "."); }
};
}
/**
* Finds the named {@link IClass} in this compilation unit, or through the {@link #iClassLoader}.
*
* @param className Fully qualified class name, e.g. "pkg1.pkg2.Outer$Inner"
* @return {@code null} iff an {@link IClass} with that name could not be loaded
* @throws CompileException An exception was raised while loading the {@link IClass}
*/
@Nullable private IClass
findTypeByName(Location location, String className) throws CompileException {
// Is the type defined in the same compilation unit?
IClass res = this.findClass(className);
if (res != null) return res;
// Is the type defined on the classpath?
try {
return this.iClassLoader.loadIClass(Descriptor.fromClassName(className));
} catch (ClassNotFoundException ex) {
Throwable cause = ex.getCause();
if (cause instanceof CompileException) throw (CompileException) cause;
throw new CompileException(className, location, ex);
}
}
/**
* JLS7 6.5.2.1
*/
private Atom
reclassifyName(Location location, Scope scope, final String identifier) throws CompileException {
// Determine scope block statement, type body declaration, type and compilation unit.
TypeBodyDeclaration scopeTbd = null;
AbstractTypeDeclaration scopeTypeDeclaration = null;
CompilationUnit scopeCompilationUnit;
{
Scope s = scope;
while (
(s instanceof BlockStatement || s instanceof CatchClause)
&& !(s instanceof TypeBodyDeclaration)
) s = s.getEnclosingScope();
if (s instanceof TypeBodyDeclaration) {
scopeTbd = (TypeBodyDeclaration) s;
s = s.getEnclosingScope();
}
if (s instanceof TypeDeclaration) {
scopeTypeDeclaration = (AbstractTypeDeclaration) s;
s = s.getEnclosingScope();
}
while (!(s instanceof CompilationUnit)) s = s.getEnclosingScope();
scopeCompilationUnit = (CompilationUnit) s;
}
// 6.5.2.1.BL1
// 6.5.2.BL1.B1.B1.1 (JLS7: 6.5.2.BL1.B1.B1.1) / 6.5.6.1.1 Local variable.
// 6.5.2.BL1.B1.B1.2 (JLS7: 6.5.2.BL1.B1.B1.2) / 6.5.6.1.1 Parameter.
{
Scope s = scope;
if (s instanceof BlockStatement) {
BlockStatement bs = (BlockStatement) s;
LocalVariable lv = bs.findLocalVariable(identifier);
if (lv != null) {
LocalVariableAccess lva = new LocalVariableAccess(location, lv);
lva.setEnclosingScope(bs);
return lva;
}
s = s.getEnclosingScope();
}
while (s instanceof BlockStatement || s instanceof CatchClause) s = s.getEnclosingScope();
if (s instanceof FunctionDeclarator) {
s = s.getEnclosingScope();
}
if (s instanceof InnerClassDeclaration) {
InnerClassDeclaration icd = (InnerClassDeclaration) s; // SUPPRESS CHECKSTYLE UsageDistance
s = s.getEnclosingScope();
if (s instanceof AnonymousClassDeclaration) {
s = s.getEnclosingScope();
} else
if (s instanceof FieldDeclaration) {
s = s.getEnclosingScope().getEnclosingScope();
}
while (s instanceof BlockStatement) {
LocalVariable lv = ((BlockStatement) s).findLocalVariable(identifier);
if (lv != null) {
if (!lv.finaL) {
this.compileError((
"Cannot access non-final local variable \""
+ identifier
+ "\" from inner class"
), location);
}
final IType lvType = lv.type;
IClass.IField iField = new SimpleIField(
this.resolve(icd),
"val$" + identifier,
UnitCompiler.rawTypeOf(lvType)
);
icd.defineSyntheticField(iField);
FieldAccess fa = new FieldAccess(
location, // location
new QualifiedThisReference( // lhs
location, // location
new SimpleType(location, this.resolve(icd)) // qualification
),
iField // field
);
fa.setEnclosingScope(scope);
return fa;
}
s = s.getEnclosingScope();
while (s instanceof BlockStatement) s = s.getEnclosingScope();
if (!(s instanceof FunctionDeclarator)) break;
s = s.getEnclosingScope();
if (!(s instanceof InnerClassDeclaration)) break;
icd = (InnerClassDeclaration) s;
s = s.getEnclosingScope();
}
}
}
// 6.5.2.BL1.B1.B1.3 (JLS7: 6.5.2.BL1.B1.B1.3) / 6.5.6.1.2.1 Field.
BlockStatement enclosingBlockStatement = null;
for (Scope s = scope; !(s instanceof CompilationUnit); s = s.getEnclosingScope()) {
if (s instanceof BlockStatement && enclosingBlockStatement == null) {
enclosingBlockStatement = (BlockStatement) s;
}
if (s instanceof TypeDeclaration) {
final AbstractTypeDeclaration enclosingTypeDecl = (AbstractTypeDeclaration) s;
final IClass etd = this.resolve(enclosingTypeDecl);
final IClass.IField f = this.findIField(etd, identifier, location);
if (f != null) {
if (f.isStatic()) {
this.warning("IASF", (
"Implicit access to static field \""
+ identifier
+ "\" of declaring class (better write \""
+ f.getDeclaringIClass()
+ '.'
+ f.getName()
+ "\")"
), location);
} else
if (f.getDeclaringIClass() == etd) {
this.warning("IANSF", (
"Implicit access to non-static field \""
+ identifier
+ "\" of declaring class (better write \"this."
+ f.getName()
+ "\")"
), location);
} else {
this.warning("IANSFEI", (
"Implicit access to non-static field \""
+ identifier
+ "\" of enclosing instance (better write \""
+ f.getDeclaringIClass()
+ ".this."
+ f.getName()
+ "\")"
), location);
}
assert scopeTypeDeclaration != null;
SimpleType ct = new SimpleType(scopeTypeDeclaration.getLocation(), etd);
Atom lhs;
if (scopeTbd == null) {
// Field access in top-level type declaration context (member annotation).
lhs = ct;
} else
if (scopeTbd instanceof MethodDeclarator && ((MethodDeclarator) scopeTbd).isStatic()) {
// Field access in static method context.
lhs = ct;
} else
{
// Field access in non-static method context.
if (f.isStatic()) {
// Access to static field.
lhs = ct;
} else {
// Access to non-static field.
lhs = new QualifiedThisReference(location, ct);
}
}
Rvalue res = new FieldAccess(location, lhs, f);
res.setEnclosingScope(scope);
return res;
}
}
}
// JLS7 6.5.2.BL1.B2.1 Static field imported through single static import.
for (IField f : Iterables.filterByClass(this.importSingleStatic(identifier), IField.class)) {
if (this.isAccessible(f, scope)) {
FieldAccess fieldAccess = new FieldAccess(
location,
new SimpleType(location, f.getDeclaringIClass()),
f
);
fieldAccess.setEnclosingScope(scope);
return fieldAccess;
}
}
// JLS7 6.5.2.BL1.B2.2 Static field imported through static-import-on-demand.
for (IField f : Iterables.filterByClass(this.importStaticOnDemand(identifier), IField.class)) {
if (this.isAccessible(f, scope)) {
FieldAccess fieldAccess = new FieldAccess(
location,
new SimpleType(location, f.getDeclaringIClass()),
f
);
fieldAccess.setEnclosingScope(scope);
return fieldAccess;
}
}
// Hack: "java" MUST be a package, not a class.
if ("java".equals(identifier)) return new Package(location, identifier);
// JLS7: 6.5.2.BL1.B3.1 Unnamed package class
// JLS7: 6.5.2.BL1.B3.2 Unnamed package interface
// JLS7: 7.4.2
{
IClass unnamedPackageType = this.findTypeByName(location, identifier);
if (unnamedPackageType != null) return new SimpleType(location, unnamedPackageType);
}
// 6.5.2.BL1.B1.B2.1 (JLS7: 6.5.2.BL1.B3.3) Local class.
{
LocalClassDeclaration lcd = UnitCompiler.findLocalClassDeclaration(scope, identifier);
if (lcd != null) return new SimpleType(location, this.resolve(lcd));
}
// 6.5.2.BL1.B1.B2.2 (JLS7: 6.5.2.BL1.B3.4) Member type.
if (scopeTypeDeclaration != null) {
IClass memberType = this.findMemberType(
this.resolve(scopeTypeDeclaration),
identifier,
null, // typeArguments
location
);
if (memberType != null) return new SimpleType(location, memberType);
}
// 6.5.2.BL1.B1.B3.1 (JLS7: 6.5.2.BL1.B1.B4.1) Single type import.
{
IClass iClass = this.importSingleType(identifier, location);
if (iClass != null) return new SimpleType(location, iClass);
}
// 6.5.2.BL1.B1.B3.2 (JLS7: 6.5.2.BL1.B1.B3.1) Package member class/interface declared in this compilation
// unit.
// Notice that JLS2 looks this up AFTER local class, member type, single type import, while JLS3 looks this up
// BEFORE local class, member type, single type import.
{
PackageMemberTypeDeclaration pmtd = scopeCompilationUnit.getPackageMemberTypeDeclaration(identifier);
if (pmtd != null) return new SimpleType(location, this.resolve(pmtd));
}
// 6.5.2.BL1.B1.B4 Class or interface declared in same package.
// Notice: Why is this missing in JLS3?
{
PackageDeclaration opd = scopeCompilationUnit.packageDeclaration;
String className = opd == null ? identifier : opd.packageName + '.' + identifier;
IClass result = this.findTypeByName(location, className);
if (result != null) return new SimpleType(location, result);
}
// 6.5.2.BL1.B1.B5 (JLS7: 6.5.2.BL1.B1.B4.2), 6.5.2.BL1.B1.B6 Type-import-on-demand.
{
IClass importedClass = this.importTypeOnDemand(identifier, location);
if (importedClass != null) return new SimpleType(location, importedClass);
}
// JLS7 6.5.2.BL1.B1.B4.3 Type imported through single static import.
{
Iterator
it = Iterables.filterByClass(this.importSingleStatic(identifier).iterator(), IClass.class);
if (it.hasNext()) return new SimpleType(location, (IClass) it.next());
}
// JLS7 6.5.2.BL1.B1.B4.4 Member type imported through static-import-on-demand.
for (IClass mt : Iterables.filterByClass(this.importStaticOnDemand(identifier), IClass.class)) {
if (this.isAccessible(mt, scope)) return new SimpleType(location, mt);
}
// 6.5.2.BL1.B1.B7 Package name
return new Package(location, identifier);
}
/**
* Imports a member class, member interface, static field or static method via the compilation unit's single
* static import declarations.
*
* @return A list of {@link IField}s, {@link IMethod}s and/or {@link IClass}es with that simpleName ;
* may be empty
*/
private List
importSingleStatic(String simpleName) throws CompileException {
List result = new ArrayList<>();
for (SingleStaticImportDeclaration ssid : Iterables.filterByClass(
this.abstractCompilationUnit.importDeclarations,
SingleStaticImportDeclaration.class
)) {
if (simpleName.equals(UnitCompiler.last(ssid.identifiers))) {
IClass declaringIClass = this.findTypeByName(
ssid.getLocation(),
Java.join(UnitCompiler.allButLast(ssid.identifiers), ".")
);
if (declaringIClass != null) {
this.importStatic(declaringIClass, simpleName, result, ssid.getLocation());
}
}
}
return result;
}
/**
* Finds all members (member classes, member interfaces, static fields and/or static methods) of the
* declaringIClass with the given simpleName and adds them to the result .
*
* @param declaringIClass The class or interface that declares the members
* @param result Results ({@link IClass}es, {@link IField}s and/or {@link IMethod}s) are added to this
* collection
*/
private void
importStatic(IClass declaringIClass, String simpleName, Collection result, Location location)
throws CompileException {
// Member type?
for (IClass memberIClass : declaringIClass.findMemberType(simpleName)) {
if (memberIClass.getDeclaringIClass() == declaringIClass) result.add(memberIClass);
}
// Static field?
{
IField iField = declaringIClass.getDeclaredIField(simpleName);
if (iField != null) {
if (!iField.isStatic()) {
this.compileError(
"Field \"" + simpleName + "\" of \"" + declaringIClass + "\" must be static",
location
);
}
result.add(iField);
}
}
// Static method?
for (IMethod iMethod : declaringIClass.getDeclaredIMethods(simpleName)) {
if (!iMethod.isStatic()) {
this.compileError(
"method \"" + iMethod + "\" of \"" + declaringIClass + "\" must be static",
location
);
}
result.add(iMethod);
}
}
/**
* @return Either the {@link FieldAccess} or an {@link ArrayLength}
*/
private Rvalue
determineValue(FieldAccessExpression fae) throws CompileException {
if (fae.value != null) return fae.value;
IType lhsType = this.getType(fae.lhs);
Rvalue value;
if (fae.fieldName.equals("length") && UnitCompiler.isArray(lhsType)) {
value = new ArrayLength(
fae.getLocation(),
this.toRvalueOrCompileException(fae.lhs)
);
} else {
IClass.IField iField = this.findIField(UnitCompiler.rawTypeOf(lhsType), fae.fieldName, fae.getLocation());
if (iField == null) {
this.compileError(
"\"" + this.getType(fae.lhs).toString() + "\" has no field \"" + fae.fieldName + "\"",
fae.getLocation()
);
value = new Rvalue(fae.getLocation()) {
@Override @Nullable public R
accept(RvalueVisitor visitor) { return null; }
@Override public String
toString() { return "???"; }
};
} else {
value = new FieldAccess(
fae.getLocation(),
fae.lhs,
iField
);
}
}
value.setEnclosingScope(fae.getEnclosingScope());
return (fae.value = value);
}
/**
* "super.fld", "Type.super.fld"
*/
private Rvalue
determineValue(SuperclassFieldAccessExpression scfae) throws CompileException {
if (scfae.value != null) return scfae.value;
Rvalue lhs;
{
ThisReference tr = new ThisReference(scfae.getLocation());
tr.setEnclosingScope(scfae.getEnclosingScope());
IType type;
if (scfae.qualification != null) {
type = this.getType(scfae.qualification);
} else
{
type = this.getType(tr);
}
IType superclass = UnitCompiler.getSuperclass(type);
if (superclass == null) {
throw new CompileException("Cannot use \"super\" on \"" + type + "\"", scfae.getLocation());
}
lhs = new Cast(scfae.getLocation(), new SimpleType(scfae.getLocation(), superclass), tr);
}
Rvalue value;
IClass.IField iField = this.findIField(UnitCompiler.rawTypeOf(this.getType(lhs)), scfae.fieldName, scfae.getLocation());
if (iField == null) {
this.compileError("Class has no field \"" + scfae.fieldName + "\"", scfae.getLocation());
value = new Rvalue(scfae.getLocation()) {
@Override @Nullable public R
accept(RvalueVisitor visitor) { return null; }
@Override public String
toString() { return "???"; }
};
} else {
value = new FieldAccess(
scfae.getLocation(),
lhs,
iField
);
}
value.setEnclosingScope(scfae.getEnclosingScope());
return (scfae.value = value);
}
/**
* Finds methods of the mi {@code .}{@link MethodInvocation#target
* target} named mi {@code .}{@link Invocation#methodName methodName},
* examines the argument types and chooses the most specific method. Checks that only the
* allowed exceptions are thrown.
*
* Notice that the returned {@link IClass.IMethod} may be declared in an enclosing type.
*
*
* @return The selected {@link IClass.IMethod}
*/
public IClass.IMethod
findIMethod(MethodInvocation mi) throws CompileException {
IClass.IMethod iMethod;
FIND_METHOD: {
Atom ot = mi.target;
if (ot == null) {
// Method invocation by simple method name... method must be declared by an enclosing type declaration.
for (Scope s = mi.getEnclosingScope(); !(s instanceof CompilationUnit); s = s.getEnclosingScope()) {
if (s instanceof TypeDeclaration) {
TypeDeclaration td = (TypeDeclaration) s;
// Find methods with specified name.
iMethod = this.findIMethod(
this.resolve(td), // targetType
mi // invocation
);
if (iMethod != null) break FIND_METHOD;
}
}
} else
{
// Method invocation by "target": "expr.meth(arguments)" -- method must be declared by the target's
// type.
iMethod = this.findIMethod(
this.getType(ot), // targetType
mi // invocable
);
if (iMethod != null) break FIND_METHOD;
}
// Static method declared through single static import?
{
IMethod[] candidates = (IMethod[]) Iterables.toArray(
Iterables.filterByClass(this.importSingleStatic(mi.methodName), IMethod.class),
IMethod.class
);
if (candidates.length > 0) {
iMethod = (IMethod) this.findMostSpecificIInvocable(
mi,
candidates,
mi.arguments,
mi.getEnclosingScope()
);
break FIND_METHOD;
}
}
// Static method declared through static-import-on-demand?
{
IMethod[] candidates = (IMethod[]) Iterables.toArray(
Iterables.filterByClass(this.importStaticOnDemand(mi.methodName), IMethod.class),
IMethod.class
);
if (candidates.length > 0) {
iMethod = (IMethod) this.findMostSpecificIInvocable(
mi,
candidates,
mi.arguments,
mi.getEnclosingScope()
);
break FIND_METHOD;
}
}
this.compileError((
"A method named \""
+ mi.methodName
+ "\" is not declared in any enclosing class nor any supertype, nor through a static import"
), mi.getLocation());
return this.fakeIMethod(this.iClassLoader.TYPE_java_lang_Object, mi.methodName, mi.arguments);
}
assert iMethod != null; // Don't know why JAVAC thinks "iMethod" could be null here!?
this.checkThrownExceptions(mi, iMethod);
return iMethod;
}
/**
* Finds a {@link IClass.IMethod} in the given targetType , its superclasses or superinterfaces which is
* applicable with the given invocation . If more than one such method exists, chooses the most
* specific one (JLS7 15.11.2).
*
* @return {@code null} if no appropriate method could be found
*/
@Nullable private IClass.IMethod
findIMethod(IType targetType, Invocation invocation) throws CompileException {
IClass rawTargetType = UnitCompiler.rawTypeOf(targetType);
// Get all methods.
List ms = new ArrayList<>();
this.getIMethods(rawTargetType, invocation.methodName, ms);
// Interfaces inherit the methods declared in 'Object'.
if (rawTargetType.isInterface()) {
IClass.IMethod[] oms = this.iClassLoader.TYPE_java_lang_Object.getDeclaredIMethods(invocation.methodName);
for (IMethod om : oms) {
if (!om.isStatic() && om.getAccess() == Access.PUBLIC) ms.add(om);
}
}
if (ms.size() == 0) return null;
// Determine arguments' types, choose the most specific method.
return (IClass.IMethod) this.findMostSpecificIInvocable(
invocation, // locatable
(IClass.IMethod[]) ms.toArray(new IClass.IMethod[ms.size()]), // iInvocables
invocation.arguments, // arguments
invocation.getEnclosingScope() // contextScope
);
}
private IMethod
fakeIMethod(IClass targetType, final String name, Rvalue[] arguments) throws CompileException {
final IClass[] pts = new IClass[arguments.length];
for (int i = 0; i < arguments.length; ++i) pts[i] = UnitCompiler.rawTypeOf(this.getType(arguments[i]));
return targetType.new IMethod() {
@Override public IAnnotation[] getAnnotations() { return new IAnnotation[0]; }
@Override public Access getAccess() { return Access.PUBLIC; }
@Override public boolean isStatic() { return false; }
@Override public boolean isAbstract() { return false; }
@Override public IClass getReturnType() { return IClass.INT; }
@Override public String getName() { return name; }
@Override public IClass[] getParameterTypes2() { return pts; }
@Override public boolean isVarargs() { return false; }
@Override public IClass[] getThrownExceptions2() { return new IClass[0]; }
};
}
/**
* Adds all methods with the given methodName that are declared by the type , its superclasses
* and all their superinterfaces to the result list v .
*/
public void
getIMethods(IClass type, String methodName, List v) throws CompileException {
// Check methods declared by this type.
{
IClass.IMethod[] ims = type.getDeclaredIMethods(methodName);
for (IMethod im : ims) v.add(im);
}
// Check superclass.
IClass superclass = type.getSuperclass();
if (superclass != null) this.getIMethods(superclass, methodName, v);
// Check superinterfaces.
IClass[] interfaces = type.getInterfaces();
for (IClass interfacE : interfaces) this.getIMethods(interfacE, methodName, v);
}
/**
* @return The {@link IClass.IMethod} that implements the superclassMethodInvocation
*/
public IClass.IMethod
findIMethod(SuperclassMethodInvocation superclassMethodInvocation) throws CompileException {
AbstractClassDeclaration declaringClass;
for (Scope s = superclassMethodInvocation.getEnclosingScope();; s = s.getEnclosingScope()) {
if (s instanceof FunctionDeclarator) {
FunctionDeclarator fd = (FunctionDeclarator) s;
if (fd instanceof MethodDeclarator && ((MethodDeclarator) fd).isStatic()) {
this.compileError(
"Superclass method cannot be invoked in static context",
superclassMethodInvocation.getLocation()
);
}
}
if (s instanceof AbstractClassDeclaration) {
declaringClass = (AbstractClassDeclaration) s;
break;
}
}
IClass superclass = this.resolve(declaringClass).getSuperclass();
if (superclass == null) {
throw new CompileException(
"\"" + declaringClass + "\" has no superclass",
superclassMethodInvocation.getLocation()
);
}
IMethod iMethod = this.findIMethod(
superclass, // targetType
superclassMethodInvocation // invocation
);
if (iMethod == null) {
this.compileError(
"Class \"" + superclass + "\" has no method named \"" + superclassMethodInvocation.methodName + "\"",
superclassMethodInvocation.getLocation()
);
return this.fakeIMethod(
superclass,
superclassMethodInvocation.methodName,
superclassMethodInvocation.arguments
);
}
this.checkThrownExceptions(superclassMethodInvocation, iMethod);
return iMethod;
}
/**
* Determines the arguments' types, determine the applicable invocables and choose the most specific invocable
* and adjust arguments as needed (for varargs case).
*
* @param iInvocables Length must be greater than zero
* @return The selected {@link IClass.IInvocable}
*/
private IClass.IInvocable
findMostSpecificIInvocable(
Locatable locatable,
final IInvocable[] iInvocables,
final Rvalue[] arguments,
Scope contextScope
) throws CompileException {
// Determine arguments' types.
final IClass[] argumentTypes = new IClass[arguments.length];
for (int i = 0; i < arguments.length; ++i) argumentTypes[i] = UnitCompiler.rawTypeOf(this.getType(arguments[i]));
// Determine most specific invocable WITHOUT boxing.
IInvocable ii = this.findMostSpecificIInvocable(locatable, iInvocables, argumentTypes, false, contextScope);
if (ii != null) return ii;
// Determine most specific invocable WITH boxing.
ii = this.findMostSpecificIInvocable(locatable, iInvocables, argumentTypes, true, contextScope);
if (ii != null) return ii;
// Report a nice compile error.
StringBuilder sb = new StringBuilder("No applicable constructor/method found for ");
if (argumentTypes.length == 0) {
sb.append("zero actual parameters");
} else {
sb.append("actual parameters \"").append(argumentTypes[0]);
for (int i = 1; i < argumentTypes.length; ++i) {
sb.append(", ").append(argumentTypes[i]);
}
sb.append("\"");
}
sb.append("; candidates are: \"").append(iInvocables[0]).append('"');
for (int i = 1; i < iInvocables.length; ++i) {
sb.append(", \"").append(iInvocables[i]).append('"');
}
this.compileError(sb.toString(), locatable.getLocation());
// Well, returning a "fake" IInvocable is a bit tricky, because the iInvocables can be of different types.
if (iInvocables[0] instanceof IClass.IConstructor) {
return iInvocables[0].getDeclaringIClass().new IConstructor() {
@Override public boolean isVarargs() { return false; }
@Override public IClass[] getParameterTypes2() { return argumentTypes; }
@Override public Access getAccess() { return Access.PUBLIC; }
@Override public IClass[] getThrownExceptions2() { return new IClass[0]; }
@Override public IAnnotation[] getAnnotations() { return new IAnnotation[0]; }
};
} else
if (iInvocables[0] instanceof IClass.IMethod) {
final String methodName = ((IClass.IMethod) iInvocables[0]).getName();
return iInvocables[0].getDeclaringIClass().new IMethod() {
@Override public IAnnotation[] getAnnotations() { return new IAnnotation[0]; }
@Override public Access getAccess() { return Access.PUBLIC; }
@Override public boolean isStatic() { return true; }
@Override public boolean isAbstract() { return false; }
@Override public IClass getReturnType() { return IClass.INT; }
@Override public String getName() { return methodName; }
@Override public IClass[] getParameterTypes2() { return argumentTypes; }
@Override public boolean isVarargs() { return false; }
@Override public IClass[] getThrownExceptions2() { return new IClass[0]; }
};
} else
{
return iInvocables[0];
}
}
/**
* Determines the applicable invocables and choose the most specific invocable.
*
* @return The maximally specific {@link IClass.IInvocable} or {@code null} if no {@link IClass.IInvocable} is
* applicable
*/
@Nullable public IClass.IInvocable
findMostSpecificIInvocable(
Locatable locatable,
final IInvocable[] iInvocables,
IClass[] argumentTypes,
boolean boxingPermitted,
Scope contextScope
) throws CompileException {
if (UnitCompiler.LOGGER.isLoggable(Level.FINER)) {
UnitCompiler.LOGGER.entering(null, "findMostSpecificIInvocable", new Object[] {
locatable, Arrays.toString(iInvocables), Arrays.toString(argumentTypes), boxingPermitted, contextScope
});
}
// Select applicable methods (15.12.2.1).
List applicableIInvocables = new ArrayList<>();
List varargApplicables = new ArrayList<>();
NEXT_METHOD:
for (IClass.IInvocable ii : iInvocables) {
boolean argsNeedAdjust = false;
// Ignore inaccessible invocables.
if (!this.isAccessible(ii, contextScope)) continue;
// Check parameter count.
final IClass[] parameterTypes = ii.getParameterTypes();
int formalParamCount = parameterTypes.length;
int nUncheckedArg = argumentTypes.length;
final boolean isVarargs = ii.isVarargs();
// Match the last formal parameter with all args starting from that index (or none).
VARARGS:
if (isVarargs) {
// Decrement the count to get the index.
formalParamCount--;
final IClass lastParamType = parameterTypes[formalParamCount].getComponentType();
assert lastParamType != null;
final int lastActualArg = nUncheckedArg - 1;
// If the two have the same argCount and the last actual arg is an array of the same type accept it
// (e.g. "void foo(int a, double...b) VS foo(1, new double[0]").
if (
formalParamCount == lastActualArg
&& argumentTypes[lastActualArg].isArray()
&& this.isMethodInvocationConvertible(
(IClass) UnitCompiler.assertNonNull(argumentTypes[lastActualArg].getComponentType()),
lastParamType,
boxingPermitted
)
) {
nUncheckedArg--;
} else {
for (int idx = lastActualArg; idx >= formalParamCount; --idx) {
// Is method invocation conversion possible (5.3)?
UnitCompiler.LOGGER.log(
Level.FINE,
"{0} <=> {1}",
new Object[] { lastParamType, argumentTypes[idx] }
);
if (!this.isMethodInvocationConvertible(argumentTypes[idx], lastParamType, boxingPermitted)) {
formalParamCount++;
break VARARGS;
}
nUncheckedArg--;
}
argsNeedAdjust = true;
}
}
if (formalParamCount == nUncheckedArg) {
for (int j = 0; j < nUncheckedArg; ++j) {
UnitCompiler.LOGGER.log(
Level.FINE,
"{0}: {1} <=> {2}",
new Object[] { j, parameterTypes[j], argumentTypes[j] }
);
// Is method invocation conversion possible (5.3)?
if (!this.isMethodInvocationConvertible(argumentTypes[j], parameterTypes[j], boxingPermitted)) {
continue NEXT_METHOD;
}
}
// Applicable!
UnitCompiler.LOGGER.fine("Applicable!");
// Varargs has lower priority.
if (isVarargs) {
ii.setArgsNeedAdjust(argsNeedAdjust);
varargApplicables.add(ii);
} else {
applicableIInvocables.add(ii);
}
}
}
// Choose the most specific invocable (15.12.2.2).
if (applicableIInvocables.size() == 1) {
return (IInvocable) applicableIInvocables.get(0);
}
// No method found by previous phase(s).
if (applicableIInvocables.size() == 0 && !varargApplicables.isEmpty()) {
//TODO: 15.12.2.3 (type-conversion?)
// 15.12.2.4 : Phase 3: Identify Applicable Variable Arity Methods
applicableIInvocables = varargApplicables;
if (applicableIInvocables.size() == 1) {
return (IInvocable) applicableIInvocables.get(0);
}
}
if (applicableIInvocables.size() == 0) return null;
// 15.12.2.5. Determine the "maximally specific invocables".
List maximallySpecificIInvocables = new ArrayList<>();
for (IClass.IInvocable applicableIInvocable : applicableIInvocables) {
int moreSpecific = 0, lessSpecific = 0;
for (IClass.IInvocable mostSpecificIInvocable : maximallySpecificIInvocables) {
if (applicableIInvocable.isMoreSpecificThan(mostSpecificIInvocable)) {
++moreSpecific;
} else
if (applicableIInvocable.isLessSpecificThan(mostSpecificIInvocable)) {
++lessSpecific;
}
}
if (moreSpecific == maximallySpecificIInvocables.size()) {
maximallySpecificIInvocables.clear();
maximallySpecificIInvocables.add(applicableIInvocable);
} else
if (lessSpecific < maximallySpecificIInvocables.size()) {
maximallySpecificIInvocables.add(applicableIInvocable);
} else
{
;
}
UnitCompiler.LOGGER.log(Level.FINE, "maximallySpecificIInvocables={0}", maximallySpecificIInvocables);
}
if (maximallySpecificIInvocables.size() == 1) return (IInvocable) maximallySpecificIInvocables.get(0);
ONE_NON_ABSTRACT_INVOCABLE:
if (maximallySpecificIInvocables.size() > 1 && iInvocables[0] instanceof IClass.IMethod) {
// Check if all methods have the same signature (i.e. the types of all their parameters are identical) and
// exactly one of the methods is non-abstract (JLS7 15.12.2.2.BL2.B1).
IClass.IMethod theNonAbstractMethod = null;
{
Iterator it = maximallySpecificIInvocables.iterator();
IClass.IMethod m = (IClass.IMethod) it.next();
final IClass[] parameterTypesOfFirstMethod = m.getParameterTypes();
for (;;) {
if (!m.isAbstract() && !m.getDeclaringIClass().isInterface()) {
if (theNonAbstractMethod == null) {
theNonAbstractMethod = m;
} else {
IClass declaringIClass = m.getDeclaringIClass();
IClass theNonAbstractMethodDeclaringIClass = theNonAbstractMethod.getDeclaringIClass();
if (declaringIClass == theNonAbstractMethodDeclaringIClass) {
if (m.getReturnType() == theNonAbstractMethod.getReturnType()) {
// JLS8 15.12.2.5.B9: "Otherwise, the method invocation is ambiguous, and a
// compile-time error occurs."
throw new InternalCompilerException(locatable.getLocation(), (
"Two non-abstract methods \""
+ m
+ "\" have the same parameter types, declaring type and return type"
));
} else
// For compatibility with SCALA, also allow for boxing conversion, e.g. return types
// "int" and "Object" are comnpatible, and "int" is more specific. See issue #182.
// if (m.getReturnType().isAssignableFrom(theNonAbstractMethod.getReturnType())) {
if (this.isMethodInvocationConvertible(theNonAbstractMethod.getReturnType(), m.getReturnType(), boxingPermitted)) {
;
} else
// For compatibility with SCALA, also allow for boxing conversion, e.g. return types
// "int" and "Object" are comnpatible, and "int" is more specific. See issue #182.
// if (theNonAbstractMethod.getReturnType().isAssignableFrom(m.getReturnType())) {
if (this.isMethodInvocationConvertible(m.getReturnType(), theNonAbstractMethod.getReturnType(), boxingPermitted)) {
theNonAbstractMethod = m;
} else
{
throw new InternalCompilerException(locatable.getLocation(), "Incompatible return types");
}
} else
if (declaringIClass.isAssignableFrom(theNonAbstractMethodDeclaringIClass)) {
;
} else
if (theNonAbstractMethodDeclaringIClass.isAssignableFrom(declaringIClass)) {
theNonAbstractMethod = m;
} else
{
this.compileError(
"Ambiguous static method import: \""
+ theNonAbstractMethod
+ "\" vs. \""
+ m
+ "\""
);
}
}
}
if (!it.hasNext()) break;
m = (IClass.IMethod) it.next();
IClass[] pts = m.getParameterTypes();
for (int i = 0; i < pts.length; ++i) {
if (pts[i] != parameterTypesOfFirstMethod[i]) break ONE_NON_ABSTRACT_INVOCABLE;
}
}
}
// JLS7 15.12.2.2.BL2.B1.B1
if (theNonAbstractMethod != null) return theNonAbstractMethod;
// JLS7 15.12.2.2.BL2.B1.B2
// Check "that exception [te1] is declared in the THROWS clause of each of the maximally specific methods".
Set s = new HashSet<>();
{
IClass[][] tes = new IClass[maximallySpecificIInvocables.size()][];
Iterator it = maximallySpecificIInvocables.iterator();
for (int i = 0; i < tes.length; ++i) {
tes[i] = ((IClass.IMethod) it.next()).getThrownExceptions();
}
for (int i = 0; i < tes.length; ++i) {
EACH_EXCEPTION:
for (IClass te1 : tes[i]) {
EACH_METHOD:
for (int k = 0; k < tes.length; ++k) {
if (k == i) continue;
for (IClass te2 : tes[k]) {
if (te2.isAssignableFrom(te1)) continue EACH_METHOD;
}
continue EACH_EXCEPTION;
}
s.add(te1);
}
}
}
// JLS7 1.12.2.5.BL2.B1.BL.B2: "... the most specific method is chosen arbitrarily among the subset of the
// maximally specific methods that have the most specific return type".
final IClass.IMethod im;
{
Iterator it2 = maximallySpecificIInvocables.iterator();
IClass.IMethod methodWithMostSpecificReturnType = (IMethod) it2.next();
while (it2.hasNext()) {
IMethod im2 = (IMethod) it2.next();
if (methodWithMostSpecificReturnType.getReturnType().isAssignableFrom(im2.getReturnType())) {
methodWithMostSpecificReturnType = im2;
}
}
im = methodWithMostSpecificReturnType;
}
// Return a "dummy" method.
final IClass[] tes = (IClass[]) s.toArray(new IClass[s.size()]);
return im.getDeclaringIClass().new IMethod() {
@Override public IAnnotation[] getAnnotations() { return im.getAnnotations(); }
@Override public Access getAccess() { return im.getAccess(); }
// JLS8 15.12.2.5.B8.B2: "In this case, the most specific method is considered to be abstract"
@Override public boolean isAbstract() { return true; }
@Override public boolean isStatic() { return im.isStatic(); }
@Override public IClass getReturnType() throws CompileException { return im.getReturnType(); }
@Override public String getName() { return im.getName(); }
@Override public IClass[] getParameterTypes2() throws CompileException { return im.getParameterTypes(); }
@Override public boolean isVarargs() { return im.isVarargs(); }
@Override public IClass[] getThrownExceptions2() { return tes; }
};
}
if (!boxingPermitted) return null; // To try again.
// JLS7 15.12.2.2.BL2.B2
{
StringBuilder sb = new StringBuilder("Invocation of constructor/method with argument type(s) \"");
for (int i = 0; i < argumentTypes.length; ++i) {
if (i > 0) sb.append(", ");
sb.append(Descriptor.toString(argumentTypes[i].getDescriptor()));
}
sb.append("\" is ambiguous: ");
for (int i = 0; i < maximallySpecificIInvocables.size(); ++i) {
if (i > 0) sb.append(" vs. ");
sb.append("\"" + maximallySpecificIInvocables.get(i) + "\"");
}
this.compileError(sb.toString(), locatable.getLocation());
}
return iInvocables[0];
}
private static T
assertNonNull(@Nullable T subject) {
assert subject != null;
return subject;
}
/**
* Checks if "method invocation conversion" (5.3) is possible.
*/
private boolean
isMethodInvocationConvertible(
IClass sourceType,
IClass targetType,
boolean boxingPermitted
) throws CompileException {
// 5.3 Identity conversion.
if (sourceType == targetType) return true;
// 5.3 Widening primitive conversion.
if (this.isWideningPrimitiveConvertible(sourceType, targetType)) return true;
// 5.3 Widening reference conversion.
if (this.isWideningReferenceConvertible(sourceType, targetType)) return true;
// JLS7 5.3 A boxing conversion (JLS7 5.1.7) optionally followed by widening reference conversion.
if (boxingPermitted) {
IClass boxedType = this.isBoxingConvertible(sourceType);
if (boxedType != null) {
return (
this.isIdentityConvertible(boxedType, targetType)
|| this.isWideningReferenceConvertible(boxedType, targetType)
);
}
}
// JLS7 5.3 An unboxing conversion (JLS7 5.1.8) optionally followed by a widening primitive conversion.
if (boxingPermitted) {
IClass unboxedType = this.isUnboxingConvertible(sourceType);
if (unboxedType != null) {
return (
this.isIdentityConvertible(unboxedType, targetType)
|| this.isWideningPrimitiveConvertible(unboxedType, targetType)
);
}
}
// 5.3 TODO: FLOAT or DOUBLE value set conversion
return false;
}
/**
* @throws CompileException if the {@link Invocation} throws exceptions that are disallowed in the given scope
*/
private void
checkThrownExceptions(Invocation in, IMethod iMethod) throws CompileException {
IClass[] thrownExceptions = iMethod.getThrownExceptions();
for (IClass thrownException : thrownExceptions) {
this.checkThrownException(
in, // locatable
thrownException, // type
in.getEnclosingScope() // scope
);
}
}
/**
* @throws CompileException The exception with the given type must not be thrown in the given
* scope
*/
private void
checkThrownException(Locatable locatable, IType type, Scope scope) throws CompileException {
// Thrown object must be assignable to "Throwable".
if (!(type instanceof IClass) || !this.iClassLoader.TYPE_java_lang_Throwable.isAssignableFrom((IClass) type)) {
this.compileError(
"Thrown object of type \"" + type + "\" is not assignable to \"Throwable\"",
locatable.getLocation()
);
}
IClass rawType = (IClass) type;
// "RuntimeException" and "Error" are never checked.
if (
this.iClassLoader.TYPE_java_lang_RuntimeException.isAssignableFrom(rawType)
|| this.iClassLoader.TYPE_java_lang_Error.isAssignableFrom(rawType)
) return;
for (;; scope = scope.getEnclosingScope()) {
// Match against enclosing "try...catch" blocks.
if (scope instanceof TryStatement) {
TryStatement ts = (TryStatement) scope;
for (int i = 0; i < ts.catchClauses.size(); ++i) {
CatchClause cc = (CatchClause) ts.catchClauses.get(i);
for (Type ct : cc.catchParameter.types) {
IClass caughtType = this.getRawType(ct);
if (caughtType.isAssignableFrom(rawType)) {
// This catch clause definitely catches the exception.
cc.reachable = true;
return;
}
CATCH_SUBTYPE:
if (rawType.isAssignableFrom(caughtType)) {
// This catch clause catches only a subtype of the exception type.
for (int j = 0; j < i; ++j) {
for (Type ct2 : ((CatchClause) ts.catchClauses.get(j)).catchParameter.types) {
if (this.getRawType(ct2).isAssignableFrom(caughtType)) {
// A preceding catch clause is more general than this catch clause.
break CATCH_SUBTYPE;
}
}
}
// This catch clause catches PART OF the actual exceptions.
cc.reachable = true;
}
}
}
} else
// Match against "throws" clause of declaring function.
if (scope instanceof FunctionDeclarator) {
FunctionDeclarator fd = (FunctionDeclarator) scope;
for (Type thrownException : fd.thrownExceptions) {
if (this.getRawType(thrownException).isAssignableFrom(rawType)) return;
}
break;
} else
if (scope instanceof TypeBodyDeclaration) {
break;
}
}
this.compileError((
"Thrown exception of type \""
+ type
+ "\" is neither caught by a \"try...catch\" block "
+ "nor declared in the \"throws\" clause of the declaring function"
), locatable.getLocation());
}
private IType
getTargetIType(QualifiedThisReference qtr) throws CompileException {
if (qtr.targetIType != null) return qtr.targetIType;
// Determine target type.
return (qtr.targetIType = this.getType(qtr.qualification));
}
/**
* Checks whether the operand is an {@code int} local variable.
*
* @return {@code null} iff c is not an {@code int} local variable
*/
@Nullable LocalVariable
isIntLv(Crement c) throws CompileException {
if (!(c.operand instanceof AmbiguousName)) return null;
AmbiguousName an = (AmbiguousName) c.operand;
Atom rec = this.reclassify(an);
if (!(rec instanceof LocalVariableAccess)) return null;
LocalVariableAccess lva = (LocalVariableAccess) rec;
LocalVariable lv = lva.localVariable;
if (lv.finaL) this.compileError("Must not increment or decrement \"final\" local variable", lva.getLocation());
return lv.type == IClass.INT ? lv : null;
}
private IClass
resolve(final TypeDeclaration td) {
final AbstractTypeDeclaration atd = (AbstractTypeDeclaration) td;
if (atd.resolvedType != null) return atd.resolvedType;
return (atd.resolvedType = new IClass() {
@Override protected ITypeVariable[]
getITypeVariables2() throws CompileException {
if (!(atd instanceof NamedTypeDeclaration)) return new ITypeVariable[0];
NamedTypeDeclaration ntd = (NamedTypeDeclaration) atd;
final Java.TypeParameter[] typeParameters = ntd.getOptionalTypeParameters();
if (typeParameters == null) return new ITypeVariable[0];
ITypeVariable[] result = new ITypeVariable[typeParameters.length];
for (int i = 0; i < result.length; i++) {
final TypeParameter tp = typeParameters[i];
final ITypeVariableOrIClass[]
bounds = new ITypeVariableOrIClass[tp.bound == null ? 0 : tp.bound.length];
for (int j = 0; j < bounds.length; j++) {
IType b = UnitCompiler.this.getType(tp.bound[j]);
assert b instanceof ITypeVariableOrIClass;
bounds[j] = (ITypeVariableOrIClass) b;
}
result[i] = new ITypeVariable() {
@Override public String
getName() { return tp.name; }
@Override public ITypeVariableOrIClass[]
getBounds() { return bounds; }
};
}
return result;
}
@Override protected IClass.IMethod[]
getDeclaredIMethods2() {
List res = new ArrayList<>(atd.getMethodDeclarations().size());
for (MethodDeclarator md : atd.getMethodDeclarations()) {
res.add(UnitCompiler.this.toIMethod(md));
}
if (td instanceof EnumDeclaration) {
res.add(new IMethod() {
@Override public IAnnotation[] getAnnotations() { return new IAnnotation[0]; }
@Override public Access getAccess() { return Access.PUBLIC; }
@Override public boolean isStatic() { return true; }
@Override public boolean isAbstract() { return false; }
@Override public String getName() { return "values"; }
@Override public IClass[] getParameterTypes2() { return new IClass[0]; }
@Override public boolean isVarargs() { return false; }
@Override public IClass[] getThrownExceptions2() { return new IClass[0]; }
@Override public IClass
getReturnType() {
IClass rt = atd.resolvedType;
assert rt != null;
return UnitCompiler.this.iClassLoader.getArrayIClass(rt);
}
});
res.add(new IMethod() {
@Override public IAnnotation[] getAnnotations() { return new IAnnotation[0]; }
@Override public Access getAccess() { return Access.PUBLIC; }
@Override public boolean isStatic() { return true; }
@Override public boolean isAbstract() { return false; }
@Override public String getName() { return "valueOf"; }
@Override public IClass[] getParameterTypes2() { return new IClass[] { UnitCompiler.this.iClassLoader.TYPE_java_lang_String }; }
@Override public boolean isVarargs() { return false; }
@Override public IClass[] getThrownExceptions2() { return new IClass[0]; }
@Override public IClass
getReturnType() {
IClass rt = atd.resolvedType;
assert rt != null;
return rt;
}
});
}
return (IClass.IMethod[]) res.toArray(new IClass.IMethod[res.size()]);
}
@Nullable private IClass[] declaredClasses;
@Override protected IClass[]
getDeclaredIClasses2() {
if (this.declaredClasses != null) return this.declaredClasses;
Collection mtds = td.getMemberTypeDeclarations();
IClass[] mts = new IClass[mtds.size()];
int i = 0;
for (MemberTypeDeclaration mtd : mtds) {
mts[i++] = UnitCompiler.this.resolve(mtd);
}
return (this.declaredClasses = mts);
}
@Override @Nullable protected IClass
getDeclaringIClass2() {
Scope s = atd;
for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope()) {
if (s instanceof CompilationUnit) return null;
}
return UnitCompiler.this.resolve((AbstractTypeDeclaration) s.getEnclosingScope());
}
@Override @Nullable protected IClass
getOuterIClass2() {
AbstractTypeDeclaration oc = (AbstractTypeDeclaration) UnitCompiler.getOuterClass(atd);
if (oc == null) return null;
return UnitCompiler.this.resolve(oc);
}
@Override protected String
getDescriptor2() { return Descriptor.fromClassName(atd.getClassName()); }
@Override public boolean
isArray() { return false; }
@Override protected IClass
getComponentType2() { throw new InternalCompilerException(td.getLocation(), "SNO: Non-array type has no component type"); }
@Override public boolean
isPrimitive() { return false; }
@Override public boolean
isPrimitiveNumeric() { return false; }
@Override protected IConstructor[]
getDeclaredIConstructors2() {
if (atd instanceof AbstractClassDeclaration) {
AbstractClassDeclaration acd = (AbstractClassDeclaration) atd;
ConstructorDeclarator[] cs = acd.getConstructors();
IClass.IConstructor[] result = new IClass.IConstructor[cs.length];
for (int i = 0; i < cs.length; ++i) result[i] = UnitCompiler.this.toIConstructor(cs[i]);
return result;
}
return new IClass.IConstructor[0];
}
@Override protected IField[]
getDeclaredIFields2() {
if (atd instanceof AbstractClassDeclaration) {
AbstractClassDeclaration cd = (AbstractClassDeclaration) atd;
List l = new ArrayList<>();
// Determine variable declarators of type declaration.
for (FieldDeclaration fd : Iterables.filterByClass(
cd.fieldDeclarationsAndInitializers,
FieldDeclaration.class
)) l.addAll(Arrays.asList(UnitCompiler.this.compileFields(fd)));
if (atd instanceof EnumDeclaration) {
EnumDeclaration ed = (EnumDeclaration) atd;
for (EnumConstant ec : ed.getConstants()) {
l.add(UnitCompiler.this.compileField(
ed, // declaringType
new Annotation[0], // annotations
Access.PUBLIC, // access
true, // statiC
true, // finaL
new SimpleType(ed.getLocation(), UnitCompiler.this.resolve(ed)), // type
0, // brackets
ec.name, // name
null // initializer
));
}
}
return (IField[]) l.toArray(new IClass.IField[l.size()]);
} else
if (atd instanceof InterfaceDeclaration) {
InterfaceDeclaration id = (InterfaceDeclaration) atd;
List l = new ArrayList<>();
// Determine static fields.
for (FieldDeclaration fd : Iterables.filterByClass(
id.constantDeclarations,
FieldDeclaration.class
)) l.addAll(Arrays.asList(UnitCompiler.this.compileFields(fd)));
return (IClass.IField[]) l.toArray(new IClass.IField[l.size()]);
} else
{
throw new InternalCompilerException(
td.getLocation(),
"SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration"
);
}
}
@Override public IField[]
getSyntheticIFields() {
if (atd instanceof AbstractClassDeclaration) {
Collection c = ((AbstractClassDeclaration) atd).syntheticFields.values();
return (IField[]) c.toArray(new IField[c.size()]);
}
return new IField[0];
}
@Override @Nullable protected IClass
getSuperclass2() throws CompileException {
if (atd instanceof EnumDeclaration) return UnitCompiler.this.iClassLoader.TYPE_java_lang_Enum;
if (atd instanceof AnonymousClassDeclaration) {
IClass bt = UnitCompiler.this.getRawType(((AnonymousClassDeclaration) atd).baseType);
return UnitCompiler.isInterface(bt) ? UnitCompiler.this.iClassLoader.TYPE_java_lang_Object : bt;
}
if (atd instanceof NamedClassDeclaration) {
NamedClassDeclaration ncd = (NamedClassDeclaration) atd;
Type oet = ncd.extendedType;
if (oet == null) return UnitCompiler.this.iClassLoader.TYPE_java_lang_Object;
IClass superclass = UnitCompiler.this.getRawType(oet);
if (superclass.isInterface()) {
UnitCompiler.this.compileError(
"\"" + superclass.toString() + "\" is an interface; classes can only extend a class",
td.getLocation()
);
}
return superclass;
}
return null;
}
@Override public Access
getAccess() {
if (atd instanceof MemberClassDeclaration) return ((MemberClassDeclaration) atd).getAccess();
if (atd instanceof PackageMemberClassDeclaration) return ((PackageMemberClassDeclaration) atd).getAccess();
if (atd instanceof MemberInterfaceDeclaration) return ((MemberInterfaceDeclaration) atd).getAccess();
if (atd instanceof PackageMemberInterfaceDeclaration) return ((PackageMemberInterfaceDeclaration) atd).getAccess();
if (atd instanceof AnonymousClassDeclaration) return Access.PUBLIC;
if (atd instanceof LocalClassDeclaration) return Access.PUBLIC;
throw new InternalCompilerException(td.getLocation(), atd.getClass().getName());
}
@Override public boolean
isFinal() { return atd instanceof NamedClassDeclaration && ((NamedClassDeclaration) atd).isFinal(); }
@Override protected IClass[]
getInterfaces2() throws CompileException {
if (atd instanceof AnonymousClassDeclaration) {
IClass bt = UnitCompiler.this.getRawType(((AnonymousClassDeclaration) atd).baseType);
return bt.isInterface() ? new IClass[] { bt } : new IClass[0];
} else
if (atd instanceof NamedClassDeclaration) {
NamedClassDeclaration ncd = (NamedClassDeclaration) atd;
IClass[] res = new IClass[ncd.implementedTypes.length];
for (int i = 0; i < res.length; ++i) {
res[i] = UnitCompiler.this.getRawType(ncd.implementedTypes[i]);
if (!res[i].isInterface()) {
UnitCompiler.this.compileError((
"\""
+ res[i].toString()
+ "\" is not an interface; classes can only implement interfaces"
), td.getLocation());
}
}
return res;
} else
if (atd instanceof InterfaceDeclaration) {
InterfaceDeclaration id = (InterfaceDeclaration) atd;
IClass[] res = new IClass[id.extendedTypes.length];
for (int i = 0; i < res.length; ++i) {
res[i] = UnitCompiler.this.getRawType(id.extendedTypes[i]);
if (!res[i].isInterface()) {
UnitCompiler.this.compileError((
"\""
+ res[i].toString()
+ "\" is not an interface; interfaces can only extend interfaces"
), td.getLocation());
}
}
return res;
} else {
throw new InternalCompilerException(
td.getLocation(),
"SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration"
);
}
}
@Override protected IAnnotation[]
getIAnnotations2() throws CompileException { return UnitCompiler.this.toIAnnotations(td.getAnnotations()); }
@Override public boolean
isAbstract() {
return (
atd instanceof InterfaceDeclaration
|| (atd instanceof NamedClassDeclaration && ((NamedClassDeclaration) atd).isAbstract())
);
}
@Override public boolean
isEnum() { return atd instanceof EnumDeclaration; }
@Override public boolean
isInterface() { return atd instanceof InterfaceDeclaration; }
});
}
private void
referenceThis(
Locatable locatable,
AbstractTypeDeclaration declaringType,
TypeBodyDeclaration declaringTypeBodyDeclaration,
IType targetIType
) throws CompileException {
List path = UnitCompiler.getOuterClasses(declaringType);
if (UnitCompiler.isStaticContext(declaringTypeBodyDeclaration)) {
this.compileError("No current instance available in static context", locatable.getLocation());
}
int j;
TARGET_FOUND: {
for (j = 0; j < path.size(); ++j) {
// Notice: JLS7 15.9.2.BL1.B3.B1.B2 seems to be wrong: Obviously, JAVAC does not only allow
//
// O is the nth lexically enclosing class
//
// , but also
//
// O is assignable from the nth lexically enclosing class
//
// However, this strategy bears the risk of ambiguities, because "O" may be assignable from more than
// one enclosing class.
if (UnitCompiler.isAssignableFrom(targetIType, this.resolve((TypeDeclaration) path.get(j)))) {
break TARGET_FOUND;
}
}
this.compileError(
"\"" + declaringType + "\" is not enclosed by \"" + targetIType + "\"",
locatable.getLocation()
);
}
int i;
if (declaringTypeBodyDeclaration instanceof ConstructorDeclarator) {
if (j == 0) {
this.load(locatable, this.resolve(declaringType), 0);
return;
}
ConstructorDeclarator constructorDeclarator = (
(ConstructorDeclarator) declaringTypeBodyDeclaration
);
String spn = "this$" + (path.size() - 2);
LocalVariable syntheticParameter = (LocalVariable) constructorDeclarator.syntheticParameters.get(spn);
if (syntheticParameter == null) {
throw new InternalCompilerException(
locatable.getLocation(),
"SNO: Synthetic parameter \"" + spn + "\" not found"
);
}
this.load(locatable, syntheticParameter);
i = 1;
} else {
this.load(locatable, this.resolve(declaringType), 0);
i = 0;
}
for (; i < j; ++i) {
final InnerClassDeclaration inner = (InnerClassDeclaration) path.get(i);
final TypeDeclaration outer = (TypeDeclaration) path.get(i + 1);
SimpleIField sf = new SimpleIField(
this.resolve(inner), // declaringIClass
"this$" + (path.size() - i - 2), // name
this.resolve(outer) // type
);
inner.defineSyntheticField(sf);
this.getfield(locatable, sf);
}
}
/**
* Returns a list consisting of the given inner class and all its enclosing (outer) classes.
*
* @return {@link List} of {@link TypeDeclaration}; has length one iff inner has no enclosing instance
*/
private static List
getOuterClasses(TypeDeclaration inner) {
List path = new ArrayList<>();
for (TypeDeclaration ic = inner; ic != null; ic = UnitCompiler.getOuterClass(ic)) path.add(ic);
return path;
}
/**
* @return The {@link TypeDeclaration} that immediately encloses the typeDeclaration , or {@code null}
*/
@Nullable static TypeDeclaration
getOuterClass(TypeDeclaration typeDeclaration) {
// Package member class declaration.
if (typeDeclaration instanceof PackageMemberClassDeclaration) return null;
// Member enum declaration is always implicitly static (JLS8 8.9).
if (typeDeclaration instanceof MemberEnumDeclaration) return null;
// Local class declaration.
if (typeDeclaration instanceof LocalClassDeclaration) {
Scope s = typeDeclaration.getEnclosingScope();
for (; !(s instanceof FunctionDeclarator) && !(s instanceof Initializer); s = s.getEnclosingScope());
if (s instanceof MethodDeclarator && ((MethodDeclarator) s).isStatic()) return null;
if (s instanceof Initializer && ((Initializer) s).isStatic()) return null;
for (; !(s instanceof TypeDeclaration); s = s.getEnclosingScope());
TypeDeclaration immediatelyEnclosingTypeDeclaration = (TypeDeclaration) s;
return (
immediatelyEnclosingTypeDeclaration instanceof AbstractClassDeclaration
) ? immediatelyEnclosingTypeDeclaration : null;
}
// Member class declaration.
if (
typeDeclaration instanceof MemberClassDeclaration
&& ((MemberClassDeclaration) typeDeclaration).isStatic()
) return null;
// Anonymous class declaration, interface declaration.
Scope s = typeDeclaration;
for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope()) {
if (s instanceof ConstructorInvocation) return null;
if (s instanceof CompilationUnit) return null;
}
//if (!(s instanceof ClassDeclaration)) return null;
if (UnitCompiler.isStaticContext((TypeBodyDeclaration) s)) return null;
return (AbstractTypeDeclaration) s.getEnclosingScope();
}
private IClass
getIClass(ThisReference tr) throws CompileException {
if (tr.iClass != null) return tr.iClass;
// Compile error if in static function context.
Scope s;
for (
s = tr.getEnclosingScope();
s instanceof Statement || s instanceof CatchClause;
s = s.getEnclosingScope()
);
if (s instanceof FunctionDeclarator) {
FunctionDeclarator function = (FunctionDeclarator) s;
if (function instanceof MethodDeclarator && ((MethodDeclarator) function).isStatic()) {
this.compileError("No current instance available in static method", tr.getLocation());
}
}
// Determine declaring type.
while (!(s instanceof TypeDeclaration)) {
s = s.getEnclosingScope();
}
if (!(s instanceof AbstractClassDeclaration)) {
this.compileError("Only methods of classes can have a current instance", tr.getLocation());
}
return (tr.iClass = this.resolve((AbstractClassDeclaration) s));
}
private IType
getReturnType(FunctionDeclarator fd) throws CompileException {
if (fd.returnType != null) return fd.returnType;
return (fd.returnType = this.getType(fd.type));
}
/**
* @return the {@link IConstructor} that implements the constructorDeclarator
*/
IClass.IConstructor
toIConstructor(final ConstructorDeclarator constructorDeclarator) {
if (constructorDeclarator.iConstructor != null) return constructorDeclarator.iConstructor;
constructorDeclarator.iConstructor = this.resolve(constructorDeclarator.getDeclaringType()).new IConstructor() {
@Nullable private IAnnotation[] ias;
// Implement IMember.
@Override public Access
getAccess() { return constructorDeclarator.getAccess(); }
@Override public IAnnotation[]
getAnnotations() {
if (this.ias != null) return this.ias;
try {
return (this.ias = UnitCompiler.this.toIAnnotations(constructorDeclarator.getAnnotations()));
} catch (CompileException ce) {
throw new InternalCompilerException(constructorDeclarator.getLocation(), null, ce);
}
}
// Implement IInvocable.
@Override public MethodDescriptor
getDescriptor2() throws CompileException {
if (!(constructorDeclarator.getDeclaringClass() instanceof InnerClassDeclaration)) {
return super.getDescriptor2();
}
if (constructorDeclarator.getDeclaringClass() instanceof MemberEnumDeclaration) {
return super.getDescriptor2();
}
List parameterFds = new ArrayList<>();
// Convert enclosing instance reference into prepended constructor parameters.
IClass outerClass = UnitCompiler.this.resolve(
constructorDeclarator.getDeclaringClass()
).getOuterIClass();
if (outerClass != null) parameterFds.add(outerClass.getDescriptor());
// Convert synthetic fields into prepended constructor parameters.
for (IField sf : constructorDeclarator.getDeclaringClass().syntheticFields.values()) {
if (sf.getName().startsWith("val$")) parameterFds.add(sf.getType().getDescriptor());
}
// Process the 'normal' (declared) function parameters.
for (IClass pt : this.getParameterTypes2()) parameterFds.add(pt.getDescriptor());
return new MethodDescriptor(
Descriptor.VOID, // returnFd
(String[]) parameterFds.toArray(new String[parameterFds.size()]) // parameterFds
);
}
@Override public boolean
isVarargs() { return constructorDeclarator.formalParameters.variableArity; }
@Override public IClass[]
getParameterTypes2() throws CompileException {
FormalParameter[] parameters = constructorDeclarator.formalParameters.parameters;
IClass[] res = new IClass[parameters.length];
for (int i = 0; i < parameters.length; ++i) {
IClass parameterType = UnitCompiler.this.getRawType(parameters[i].type);
if (i == parameters.length - 1 && constructorDeclarator.formalParameters.variableArity) {
parameterType = UnitCompiler.this.iClassLoader.getArrayIClass(UnitCompiler.rawTypeOf(parameterType));
}
res[i] = parameterType;
}
return res;
}
@Override public IClass[]
getThrownExceptions2() throws CompileException {
IClass[] res = new IClass[constructorDeclarator.thrownExceptions.length];
for (int i = 0; i < res.length; ++i) {
res[i] = UnitCompiler.this.getRawType(constructorDeclarator.thrownExceptions[i]);
}
return res;
}
@Override public String
toString() {
StringBuilder sb = new StringBuilder().append(
constructorDeclarator.getDeclaringType().getClassName()
).append('(');
FormalParameter[] parameters = constructorDeclarator.formalParameters.parameters;
for (int i = 0; i < parameters.length; ++i) {
if (i != 0) sb.append(", ");
sb.append(parameters[i].toString(
i == parameters.length - 1
&& constructorDeclarator.formalParameters.variableArity
));
}
return sb.append(')').toString();
}
};
return constructorDeclarator.iConstructor;
}
/**
* @return The {@link IMethod} that implements the methodDeclarator
*/
public IClass.IMethod
toIMethod(final MethodDeclarator methodDeclarator) {
if (methodDeclarator.iMethod != null) return methodDeclarator.iMethod;
methodDeclarator.iMethod = this.resolve(methodDeclarator.getDeclaringType()).new IMethod() {
@Nullable IAnnotation[] ias;
// Implement IMember.
@Override public Access
getAccess() {
return (
methodDeclarator.getDeclaringType() instanceof InterfaceDeclaration && methodDeclarator.getAccess() == Access.DEFAULT
? Access.PUBLIC
: methodDeclarator.getAccess()
);
}
@Override public IAnnotation[]
getAnnotations() {
if (this.ias != null) return this.ias;
try {
return (this.ias = UnitCompiler.this.toIAnnotations(methodDeclarator.getAnnotations()));
} catch (CompileException ce) {
throw new InternalCompilerException(methodDeclarator.getLocation(), null, ce);
}
}
// Implement IInvocable.
@Override public boolean
isVarargs() { return methodDeclarator.formalParameters.variableArity; }
@Override public IClass[]
getParameterTypes2() throws CompileException {
FormalParameter[] parameters = methodDeclarator.formalParameters.parameters;
IClass[] res = new IClass[parameters.length];
for (int i = 0; i < parameters.length; ++i) {
IClass parameterType = UnitCompiler.this.getRawType(parameters[i].type);
if (i == parameters.length - 1 && methodDeclarator.formalParameters.variableArity) {
parameterType = UnitCompiler.this.iClassLoader.getArrayIClass(parameterType);
}
res[i] = parameterType;
}
return res;
}
@Override public IClass[]
getThrownExceptions2() throws CompileException {
List result = new ArrayList<>();
for (Type ti : methodDeclarator.thrownExceptions) {
// KLUDGE: Iff the exception type in the THROWS clause sounds like a type parameter, then
// ignore it. Otherwise we'd have to put
// try { ... } catch (Throwable t) { throw new AssertionError(t); }
// around all invocations of methods that use a type parameter for declaring their exception.
if (ti instanceof ReferenceType) {
String[] identifiers = ((ReferenceType) ti).identifiers;
if (
identifiers.length == 1
&& UnitCompiler.LOOKS_LIKE_TYPE_PARAMETER.matcher(identifiers[0]).matches()
) continue;
}
result.add(UnitCompiler.this.getRawType(ti));
}
return (IClass[]) result.toArray(new IClass[result.size()]);
}
// Implement IMethod.
@Override public boolean
isStatic() { return methodDeclarator.isStatic(); }
@Override public boolean
isAbstract() {
return (
(
methodDeclarator.getDeclaringType() instanceof InterfaceDeclaration
&& !methodDeclarator.isDefault()
&& methodDeclarator.getAccess() != Access.PRIVATE
)
|| methodDeclarator.isAbstract()
);
}
@Override public IClass
getReturnType() throws CompileException {
return UnitCompiler.rawTypeOf(UnitCompiler.this.getReturnType(methodDeclarator));
}
@Override public String
getName() { return methodDeclarator.name; }
};
return methodDeclarator.iMethod;
}
private IClass.IInvocable
toIInvocable(final FunctionDeclarator fd) {
IClass.IInvocable result = (IClass.IInvocable) fd.accept(
new FunctionDeclaratorVisitor() {
@Override public IInvocable visitMethodDeclarator(MethodDeclarator md) { return UnitCompiler.this.toIMethod((MethodDeclarator) fd); }
@Override public IInvocable visitConstructorDeclarator(ConstructorDeclarator cd) { return UnitCompiler.this.toIConstructor((ConstructorDeclarator) fd); }
}
);
assert result != null;
return result;
}
/**
* If the given name was declared in a simple type import, load that class.
*/
@Nullable private IClass
importSingleType(String simpleTypeName, Location location) throws CompileException {
String[] ss = this.getSingleTypeImport(simpleTypeName, location);
if (ss == null) return null;
IClass iClass = this.findTypeByFullyQualifiedName(location, ss);
if (iClass == null) {
this.compileError("Imported class \"" + Java.join(ss, ".") + "\" could not be loaded", location);
return this.iClassLoader.TYPE_java_lang_Object;
}
return iClass;
}
/**
* Checks if the given simple name was imported through a single type import.
*
* @param name The simple type name, e.g. {@code "Inner"}
* @return The fully qualified name, e.g. {@code { "pkg", "Outer", "Inner" }}, or {@code null}
*/
@Nullable public String[]
getSingleTypeImport(String name, Location location) throws CompileException {
// Resolve all single type imports (if not already done).
Map stis = this.singleTypeImports;
if (stis == null) {
// Collect all single type import declarations.
final List stids = new ArrayList<>();
for (ImportDeclaration id : this.abstractCompilationUnit.importDeclarations) {
id.accept(new ImportVisitor() {
@Override @Nullable public Void visitSingleTypeImportDeclaration(SingleTypeImportDeclaration stid) { stids.add(stid); return null; }
@Override @Nullable public Void visitTypeImportOnDemandDeclaration(TypeImportOnDemandDeclaration tiodd) { return null; }
@Override @Nullable public Void visitSingleStaticImportDeclaration(SingleStaticImportDeclaration ssid) { return null; }
@Override @Nullable public Void visitStaticImportOnDemandDeclaration(StaticImportOnDemandDeclaration siodd) { return null; }
});
}
// Resolve all single type imports.
stis = new HashMap<>();
for (SingleTypeImportDeclaration stid : stids) {
String[] ids = stid.identifiers;
String simpleName = UnitCompiler.last(ids);
// Check for re-import of same simple name.
String[] prev = (String[]) stis.put(simpleName, ids);
if (prev != null && !Arrays.equals(prev, ids)) {
UnitCompiler.this.compileError((
"Class \"" + simpleName + "\" was previously imported as "
+ "\"" + Java.join(prev, ".") + "\", now as \"" + Java.join(ids, ".") + "\""
), stid.getLocation());
}
if (this.findTypeByFullyQualifiedName(location, ids) == null) {
UnitCompiler.this.compileError(
"A class \"" + Java.join(ids, ".") + "\" could not be found",
stid.getLocation()
);
}
}
this.singleTypeImports = stis;
}
return (String[]) stis.get(name);
}
/**
* To be used only by {@link #getSingleTypeImport(String, Location)}; {@code null} means "not yet initialized"
*/
@Nullable private Map singleTypeImports;
/**
* 6.5.2.BL1.B1.B5, 6.5.2.BL1.B1.B6 Type-import-on-demand.
* 6.5.5.1.6 Type-import-on-demand declaration.
*
* @return {@code null} if the given simpleTypeName cannot be resolved through any of the
* type-import-on-demand declarations
*/
@Nullable public IClass
importTypeOnDemand(String simpleTypeName, Location location) throws CompileException {
IClass importedClass = (IClass) this.onDemandImportableTypes.get(simpleTypeName);
if (importedClass == null) {
importedClass = this.importTypeOnDemand2(simpleTypeName, location);
this.onDemandImportableTypes.put(simpleTypeName, importedClass);
}
return importedClass;
}
private final Map onDemandImportableTypes = new HashMap<>();
/**
* @return {@code null} if the given simpleTypeName cannot be resolved through any of the
* type-import-on-demand declarations
*/
@Nullable private IClass
importTypeOnDemand2(String simpleTypeName, Location location) throws CompileException {
IClass importedClass = null;
for (TypeImportOnDemandDeclaration tiodd : this.getTypeImportOnDemandImportDeclarations()) {
IClass iClass = this.findTypeByFullyQualifiedName(
location,
UnitCompiler.concat(tiodd.identifiers, simpleTypeName)
);
if (iClass == null) continue;
if (importedClass != null && importedClass != iClass) {
this.compileError(
"Ambiguous class name: \"" + importedClass + "\" vs. \"" + iClass + "\"",
location
);
}
importedClass = iClass;
}
if (importedClass == null) return null;
return importedClass;
}
private Collection
getTypeImportOnDemandImportDeclarations() {
Collection result = new ArrayList<>();
for (TypeImportOnDemandDeclaration tiodd : Iterables.filterByClass(
this.abstractCompilationUnit.importDeclarations,
TypeImportOnDemandDeclaration.class
)) result.add(tiodd);
result.add(new TypeImportOnDemandDeclaration(Location.NOWHERE, new String[] { "java", "lang" }));
return result;
}
// ============================================ BYTECODE INSTRUCTIONS ============================================
/**
* Pushes one value on the operand stack and pushes the respective {@link VerificationTypeInfo} operand to the
* stack map.
*
* value Operand stack value Verification type info
*
*
* {@link Character}
*
* {@link Byte}
*
* {@link Short}
*
* {@link Integer}
*
* {@link Boolean}
*
* {@code int}
* {@code integer_variable_info}
*
* {@link Float} {@code float} {@code float_variable_info}
* {@link Long} {@code msb+lsb} of {@code long} {@code long_variable_info}
* {@link Double} {@code msb+lsb} of {@code double} {@code double_variable_info}
* {@link String} {@code ConstantStringInfo} CP index {@code object_variable_info(String)}
* {@link IClass} {@code ConstantClassInfo} CP index {@code object_variable_info(iClass.descriptor())}
* {@code null} {@code null} {@code null_variable_info(iClass.descriptor())}
*
*
* @return The computational type of the value that was pushed, e.g. {@link IClass#INT} for {@link Byte} or {@link
* IClass#NULL} for {@code null}
*/
private IClass
consT(Locatable locatable, @Nullable Object value) throws CompileException {
if (value instanceof Character) {
this.consT(locatable, ((Character) value).charValue());
return IClass.INT;
}
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
this.consT(locatable, ((Number) value).intValue());
return IClass.INT;
}
if (Boolean.TRUE.equals(value)) {
this.consT(locatable, 1);
return IClass.BOOLEAN;
}
if (Boolean.FALSE.equals(value)) {
this.consT(locatable, 0);
return IClass.BOOLEAN;
}
if (value instanceof Float) {
this.consT(locatable, ((Float) value).floatValue());
return IClass.FLOAT;
}
if (value instanceof Long) {
this.consT(locatable, ((Long) value).longValue());
return IClass.LONG;
}
if (value instanceof Double) {
this.consT(locatable, ((Double) value).doubleValue());
return IClass.DOUBLE;
}
if (value instanceof String) {
String s = (String) value;
String[] ss = UnitCompiler.makeUtf8Able(s);
this.consT(locatable, ss[0]);
for (int i = 1; i < ss.length; ++i) {
this.consT(locatable, ss[i]);
this.invokeMethod(locatable, this.iClassLoader.METH_java_lang_String__concat__java_lang_String);
}
return this.iClassLoader.TYPE_java_lang_String;
}
if (value instanceof IClass) {
this.consT(locatable, (IClass) value);
return this.iClassLoader.TYPE_java_lang_Class;
}
if (value == null) {
this.aconstnull(locatable);
return IClass.NULL;
}
throw new InternalCompilerException(locatable.getLocation(), "Unknown literal \"" + value + "\"");
}
/**
* Only strings that can be UTF8-encoded into 65535 bytes can be stored as a constant string info.
*
* @param s The string to split into suitable chunks
* @return Strings that can be UTF8-encoded into 65535 bytes
*/
private static String[]
makeUtf8Able(String s) {
if (s.length() < (65536 / 3)) return new String[] { s };
int sLength = s.length(), utfLength = 0;
int from = 0;
List l = new ArrayList<>();
for (int i = 0;; i++) {
if (i == sLength) {
l.add(s.substring(from));
break;
}
if (utfLength >= 65532) {
l.add(s.substring(from, i));
if (i + (65536 / 3) > sLength) {
l.add(s.substring(i));
break;
}
from = i;
utfLength = 0;
}
int c = s.charAt(i);
if (c >= 0x0001 && c <= 0x007F) {
++utfLength;
} else
if (c > 0x07FF) {
utfLength += 3;
} else
{
utfLength += 2;
}
}
return (String[]) l.toArray(new String[l.size()]);
}
private void
consT(Locatable locatable, IClass t, int value) {
if (t == IClass.BYTE || t == IClass.CHAR || t == IClass.INT || t == IClass.SHORT || t == IClass.BOOLEAN) {
this.consT(locatable, value);
} else
if (t == IClass.LONG) {
this.consT(locatable, (long) value);
} else
if (t == IClass.FLOAT) {
this.consT(locatable, (float) value);
} else
if (t == IClass.DOUBLE) {
this.consT(locatable, (double) value);
} else
{
throw new AssertionError(t);
}
}
private void
consT(Locatable locatable, int value) {
this.addLineNumberOffset(locatable);
if (value >= -1 && value <= 5) {
this.write(Opcode.ICONST_0 + value);
} else
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
this.write(Opcode.BIPUSH);
this.writeByte(value);
} else
if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
this.write(Opcode.SIPUSH);
this.writeShort(value);
} else {
this.writeLdc(this.addConstantIntegerInfo(value));
}
this.getCodeContext().pushIntOperand();
}
private void
consT(Locatable locatable, long value) {
this.addLineNumberOffset(locatable);
if (value == 0L || value == 1L) {
this.write(Opcode.LCONST_0 + (int) value);
} else
{
this.writeLdc2(this.addConstantLongInfo(value));
}
this.getCodeContext().pushLongOperand();
}
private void
consT(Locatable locatable, float value) {
this.addLineNumberOffset(locatable);
if (
Float.floatToIntBits(value) == Float.floatToIntBits(0.0F) // POSITIVE zero!
|| value == 1.0F
|| value == 2.0F
) {
this.write(Opcode.FCONST_0 + (int) value);
} else {
this.writeLdc(this.addConstantFloatInfo(value));
}
this.getCodeContext().pushFloatOperand();
}
private void
consT(Locatable locatable, double value) {
this.addLineNumberOffset(locatable);
if (
Double.doubleToLongBits(value) == Double.doubleToLongBits(0.0D) // POSITIVE zero!
|| value == 1.0D
) {
this.write(Opcode.DCONST_0 + (int) value);
} else {
this.writeLdc2(this.addConstantDoubleInfo(value));
}
this.getCodeContext().pushDoubleOperand();
}
private void
consT(Locatable locatable, final String s) {
this.addLineNumberOffset(locatable);
this.writeLdc(this.addConstantStringInfo(s));
this.getCodeContext().pushObjectOperand(Descriptor.JAVA_LANG_STRING);
}
private void
consT(Locatable locatable, IClass iClass) {
this.addLineNumberOffset(locatable);
this.writeLdc(this.addConstantClassInfo(iClass));
this.getCodeContext().pushObjectOperand(Descriptor.JAVA_LANG_CLASS);
}
private void
castConversion(
Locatable locatable,
IType sourceType,
IType targetType,
@Nullable Object constantValue
) throws CompileException {
if (!this.tryCastConversion(locatable, sourceType, targetType, constantValue)) {
this.compileError(
"Cast conversion not possible from type \"" + sourceType + "\" to type \"" + targetType + "\"",
locatable.getLocation()
);
}
}
private boolean
tryCastConversion(
Locatable locatable,
IType sourceType,
IType targetType,
@Nullable Object constantValue
) throws CompileException {
return (
this.tryAssignmentConversion(locatable, sourceType, targetType, constantValue)
|| this.tryNarrowingPrimitiveConversion(locatable, sourceType, targetType)
|| this.tryNarrowingReferenceConversion(locatable, sourceType, targetType)
);
}
/**
* Implements "assignment conversion" (JLS7 5.2).
*/
private void
assignmentConversion(
Locatable locatable,
IType sourceType,
IType targetType,
@Nullable Object constantValue
) throws CompileException {
if (!this.tryAssignmentConversion(locatable, sourceType, targetType, constantValue)) {
this.compileError(
"Assignment conversion not possible from type \"" + sourceType + "\" to type \"" + targetType + "\"",
locatable.getLocation()
);
}
}
private boolean
tryAssignmentConversion(
Locatable locatable,
IType sourceType,
IType targetType,
@Nullable Object constantValue
) throws CompileException {
UnitCompiler.LOGGER.entering(
null,
"tryAssignmentConversion",
new Object[] { locatable, sourceType, targetType, constantValue }
);
// JLS7 5.1.1 Identity conversion.
if (this.tryIdentityConversion(sourceType, targetType)) return true;
// JLS7 5.1.2 Widening primitive conversion.
if (this.tryWideningPrimitiveConversion(locatable, sourceType, targetType)) return true;
// JLS7 5.1.4 Widening reference conversion.
if (this.isWideningReferenceConvertible(sourceType, targetType)) {
this.getCodeContext().popOperand(sourceType == IClass.NULL ? Descriptor.VOID : UnitCompiler.rawTypeOf(sourceType).getDescriptor());
this.getCodeContext().pushOperand(UnitCompiler.rawTypeOf(targetType).getDescriptor());
return true;
}
// A boxing conversion (JLS7 5.1.7) optionally followed by a widening reference conversion.
{
IClass boxedType = this.isBoxingConvertible(sourceType);
if (boxedType != null) {
if (this.tryIdentityConversion(boxedType, targetType)) {
this.boxingConversion(locatable, sourceType, boxedType);
return true;
}
if (this.isWideningReferenceConvertible(boxedType, targetType)) {
this.boxingConversion(locatable, sourceType, boxedType);
this.getCodeContext().popOperand(boxedType.getDescriptor());
this.getCodeContext().pushOperand(UnitCompiler.rawTypeOf(targetType).getDescriptor());
return true;
}
}
}
// An unboxing conversion (JLS7 5.1.8) optionally followed by a widening primitive conversion.
{
IClass unboxedType = this.isUnboxingConvertible(sourceType);
if (unboxedType != null) {
if (this.tryIdentityConversion(unboxedType, targetType)) {
this.unboxingConversion(locatable, sourceType, unboxedType);
return true;
}
if (this.isWideningPrimitiveConvertible(unboxedType, targetType)) {
this.unboxingConversion(locatable, sourceType, unboxedType);
this.tryWideningPrimitiveConversion(locatable, unboxedType, targetType);
return true;
}
}
}
// 5.2 Special narrowing primitive conversion.
if (constantValue != UnitCompiler.NOT_CONSTANT) {
if (this.tryConstantAssignmentConversion(
locatable,
constantValue, // constantValue
targetType // targetType
)) return true;
}
return false;
}
/**
* Implements "assignment conversion" (JLS7 5.2) on a constant value.
*
* @param value Must be a {@link Boolean}, {@link String}, {@link Byte}, {@link Short}, {@link Integer}, {@link
* Character}, {@link Long}, {@link Float}, {@link Double} or {@code null}
* @return A {@link Boolean}, {@link String}, {@link Byte}, {@link Short}, {@link Integer}, {@link Character},
* {@link Long}, {@link Float}, {@link Double} or {@code null} (representing the {@code null} literal)
*/
@Nullable private Object
constantAssignmentConversion(Locatable locatable, @Nullable Object value, IType targetType) throws CompileException {
if (value == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT;
if (targetType == IClass.BOOLEAN) {
if (value instanceof Boolean) return value;
} else
if (targetType == this.iClassLoader.TYPE_java_lang_String) {
if (value instanceof String || value == null) return value;
} else
if (targetType == IClass.BYTE) {
if (value instanceof Byte) {
return value;
} else
if (value instanceof Short || value instanceof Integer) {
assert value != null;
int x = ((Number) value).intValue();
if (x >= Byte.MIN_VALUE && x <= Byte.MAX_VALUE) return Byte.valueOf((byte) x);
} else
if (value instanceof Character) {
int x = ((Character) value).charValue();
if (x >= Byte.MIN_VALUE && x <= Byte.MAX_VALUE) return Byte.valueOf((byte) x);
}
} else
if (targetType == IClass.SHORT) {
if (value instanceof Byte) {
return Short.valueOf(((Number) value).shortValue());
} else
if (value instanceof Short) {
return value;
} else
if (value instanceof Character) {
int x = ((Character) value).charValue();
if (x >= Short.MIN_VALUE && x <= Short.MAX_VALUE) return Short.valueOf((short) x);
} else
if (value instanceof Integer) {
int x = ((Integer) value).intValue();
if (x >= Short.MIN_VALUE && x <= Short.MAX_VALUE) return Short.valueOf((short) x);
}
} else
if (targetType == IClass.CHAR) {
if (value instanceof Short) {
return value;
} else
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
assert value != null;
int x = ((Number) value).intValue();
if (x >= Character.MIN_VALUE && x <= Character.MAX_VALUE) return Character.valueOf((char) x);
}
} else
if (targetType == IClass.INT) {
if (value instanceof Integer) {
return value;
} else
if (value instanceof Byte || value instanceof Short) {
assert value != null;
return Integer.valueOf(((Number) value).intValue());
} else
if (value instanceof Character) {
return Integer.valueOf(((Character) value).charValue());
}
} else
if (targetType == IClass.LONG) {
if (value instanceof Long) {
return value;
} else
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
assert value != null;
return Long.valueOf(((Number) value).longValue());
} else
if (value instanceof Character) {
return Long.valueOf(((Character) value).charValue());
}
} else
if (targetType == IClass.FLOAT) {
if (value instanceof Float) {
return value;
} else
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) {
assert value != null;
return Float.valueOf(((Number) value).floatValue());
} else
if (value instanceof Character) {
return Float.valueOf(((Character) value).charValue());
}
} else
if (targetType == IClass.DOUBLE) {
if (value instanceof Double) {
return value;
} else
if (
value instanceof Byte
|| value instanceof Short
|| value instanceof Integer
|| value instanceof Long
|| value instanceof Float
) {
assert value != null;
return Double.valueOf(((Number) value).doubleValue());
} else
if (value instanceof Character) {
return Double.valueOf(((Character) value).charValue());
}
} else
if (value == null && !UnitCompiler.isPrimitive(targetType)) {
return null;
} else
if (value instanceof String && UnitCompiler.isAssignableFrom(targetType, this.iClassLoader.TYPE_java_lang_String)) {
return value;
}
if (value == null) {
this.compileError(
"Cannot convert 'null' to type \"" + targetType.toString() + "\"",
locatable.getLocation()
);
} else
{
this.compileError((
"Cannot convert constant of type \""
+ value.getClass().getName()
+ "\" to type \""
+ targetType.toString()
+ "\""
), locatable.getLocation());
}
return value;
}
/**
* Implements "unary numeric promotion" (JLS7 5.6.1).
*
* @return The promoted type
*/
private IClass
unaryNumericPromotion(Locatable locatable, IType type) throws CompileException {
type = this.convertToPrimitiveNumericType(locatable, type);
IClass promotedType = this.unaryNumericPromotionType(locatable, type);
this.numericPromotion(locatable, type, promotedType);
return promotedType;
}
private void
reverseUnaryNumericPromotion(Locatable locatable, IClass sourceType, IType targetType) {
IClass unboxedType = this.isUnboxingConvertible(targetType);
IType pt = unboxedType != null ? unboxedType : targetType;
if (
!this.tryIdentityConversion(sourceType, pt)
&& !this.tryNarrowingPrimitiveConversion(
locatable, // locatable
sourceType, // sourceType
pt // targetType
)
) throw new InternalCompilerException(locatable.getLocation(), "SNO: reverse unary numeric promotion failed");
if (unboxedType != null) this.boxingConversion(locatable, unboxedType, targetType);
}
/**
* If the given type is a primitive type, return that type. If the given type is a primitive wrapper class, unbox
* the operand on top of the operand stack and return the primitive type. Otherwise, issue a compile error.
*/
private IClass
convertToPrimitiveNumericType(Locatable locatable, IType type) throws CompileException {
if ((type instanceof IClass) && ((IClass) type).isPrimitiveNumeric()) return (IClass) type;
IClass unboxedType = this.isUnboxingConvertible(type);
if (unboxedType != null) {
this.unboxingConversion(locatable, type, unboxedType);
return unboxedType;
}
this.compileError(
"Object of type \"" + type.toString() + "\" cannot be converted to a numeric type",
locatable.getLocation()
);
return IClass.INT;
}
private void
numericPromotion(Locatable locatable, IType sourceType, IClass targetType) {
if (
!this.tryIdentityConversion(sourceType, targetType)
&& !this.tryWideningPrimitiveConversion(
locatable, // locatable
sourceType, // sourceType
targetType // targetType
)
) throw new InternalCompilerException(locatable.getLocation(), "SNO: Conversion failed");
}
private IClass
unaryNumericPromotionType(Locatable locatable, IType type) throws CompileException {
if (!(type instanceof IClass) || !((IClass) type).isPrimitiveNumeric()) {
this.compileError(
"Unary numeric promotion not possible on non-numeric-primitive type \"" + type + "\"",
locatable.getLocation()
);
}
return (
type == IClass.DOUBLE ? IClass.DOUBLE :
type == IClass.FLOAT ? IClass.FLOAT :
type == IClass.LONG ? IClass.LONG :
IClass.INT
);
}
private IClass
binaryNumericPromotionType(Locatable locatable, IType type1, IType type2) throws CompileException {
if (
!(type1 instanceof IClass)
|| !((IClass) type1).isPrimitiveNumeric()
|| !(type2 instanceof IClass)
|| !((IClass) type2).isPrimitiveNumeric()
) {
this.compileError(
"Binary numeric promotion not possible on types \"" + type1 + "\" and \"" + type2 + "\"",
locatable.getLocation()
);
}
return (
type1 == IClass.DOUBLE || type2 == IClass.DOUBLE ? IClass.DOUBLE :
type1 == IClass.FLOAT || type2 == IClass.FLOAT ? IClass.FLOAT :
type1 == IClass.LONG || type2 == IClass.LONG ? IClass.LONG :
IClass.INT
);
}
/**
* Checks whether "identity conversion" (5.1.1) is possible.
*
* @return Whether the conversion is possible
*/
@SuppressWarnings("static-method") private boolean
isIdentityConvertible(IType sourceType, IType targetType) { return sourceType == targetType; }
/**
* Implements "identity conversion" (5.1.1).
*
* @return Whether the conversion was possible
*/
@SuppressWarnings("static-method") private boolean
tryIdentityConversion(IType sourceType, IType targetType) { return sourceType == targetType; }
@SuppressWarnings("static-method") private boolean
isWideningPrimitiveConvertible(IClass sourceType, IType targetType) {
return UnitCompiler.PRIMITIVE_WIDENING_CONVERSIONS.get(
sourceType.getDescriptor() + UnitCompiler.rawTypeOf(targetType).getDescriptor()
) != null;
}
/**
* Implements "widening primitive conversion" (5.1.2).
*
* @return Whether the conversion succeeded
*/
private boolean
tryWideningPrimitiveConversion(Locatable locatable, IType sourceType, IType targetType) {
if (sourceType instanceof IParameterizedType) return false;
if (targetType instanceof IParameterizedType) return false;
IClass targetClass = (IClass) targetType;
int[] opcodes = (int[]) UnitCompiler.PRIMITIVE_WIDENING_CONVERSIONS.get(
UnitCompiler.rawTypeOf(sourceType).getDescriptor() + targetClass.getDescriptor()
);
if (opcodes != null) {
this.addLineNumberOffset(locatable);
for (int opcode : opcodes) this.write(opcode);
this.getCodeContext().popOperand();
this.getCodeContext().pushOperand(targetClass.getDescriptor());
return true;
}
return false;
}
private static final Map
PRIMITIVE_WIDENING_CONVERSIONS = new HashMap<>();
static { UnitCompiler.fillConversionMap(new Object[] {
new int[0],
Descriptor.BYTE + Descriptor.SHORT,
Descriptor.BYTE + Descriptor.INT,
Descriptor.SHORT + Descriptor.INT,
Descriptor.CHAR + Descriptor.INT,
new int[] { Opcode.I2L },
Descriptor.BYTE + Descriptor.LONG,
Descriptor.SHORT + Descriptor.LONG,
Descriptor.CHAR + Descriptor.LONG,
Descriptor.INT + Descriptor.LONG,
new int[] { Opcode.I2F },
Descriptor.BYTE + Descriptor.FLOAT,
Descriptor.SHORT + Descriptor.FLOAT,
Descriptor.CHAR + Descriptor.FLOAT,
Descriptor.INT + Descriptor.FLOAT,
new int[] { Opcode.L2F },
Descriptor.LONG + Descriptor.FLOAT,
new int[] { Opcode.I2D },
Descriptor.BYTE + Descriptor.DOUBLE,
Descriptor.SHORT + Descriptor.DOUBLE,
Descriptor.CHAR + Descriptor.DOUBLE,
Descriptor.INT + Descriptor.DOUBLE,
new int[] { Opcode.L2D },
Descriptor.LONG + Descriptor.DOUBLE,
new int[] { Opcode.F2D },
Descriptor.FLOAT + Descriptor.DOUBLE,
}, UnitCompiler.PRIMITIVE_WIDENING_CONVERSIONS); }
private static void
fillConversionMap(Object[] array, Map map) {
int[] opcodes = null;
for (Object o : array) {
if (o instanceof int[]) {
opcodes = (int[]) o;
} else {
map.put((String) o, opcodes);
}
}
}
/**
* Checks if "widening reference conversion" (5.1.4) is possible.
*
* @return Whether the conversion is possible
*/
@SuppressWarnings("static-method") private boolean
isWideningReferenceConvertible(IType sourceType, IType targetType) throws CompileException {
IClass sourceClass = UnitCompiler.rawTypeOf(sourceType);
IClass targetClass = UnitCompiler.rawTypeOf(targetType);
if (targetClass.isPrimitive() || sourceType == targetType) return false;
return targetClass.isAssignableFrom(sourceClass);
}
/**
* Checks whether "narrowing primitive conversion" (JLS7 5.1.3) is possible.
*/
@SuppressWarnings("static-method") private boolean
isNarrowingPrimitiveConvertible(IType sourceType, IType targetType) {
return UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS.containsKey(
UnitCompiler.rawTypeOf(sourceType).getDescriptor() + UnitCompiler.rawTypeOf(targetType).getDescriptor()
);
}
/**
* Implements "narrowing primitive conversion" (JLS7 5.1.3).
*
* @return Whether the conversion succeeded
*/
private boolean
tryNarrowingPrimitiveConversion(Locatable locatable, IType sourceType, IType targetType) {
if (!(sourceType instanceof IClass) || !(targetType instanceof IClass)) return false;
IClass sourceClass = (IClass) sourceType;
IClass targetClass = (IClass) targetType;
int[] opcodes = (int[]) UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS.get(
sourceClass.getDescriptor() + targetClass.getDescriptor()
);
if (opcodes != null) {
this.addLineNumberOffset(locatable);
for (int opcode : opcodes) this.write(opcode);
this.getCodeContext().popOperand();
this.getCodeContext().pushOperand(targetClass.getDescriptor());
return true;
}
return false;
}
private static final Map
PRIMITIVE_NARROWING_CONVERSIONS = new HashMap<>();
static { UnitCompiler.fillConversionMap(new Object[] {
new int[0],
Descriptor.BYTE + Descriptor.CHAR,
Descriptor.SHORT + Descriptor.CHAR,
Descriptor.CHAR + Descriptor.SHORT,
new int[] { Opcode.I2B },
Descriptor.SHORT + Descriptor.BYTE,
Descriptor.CHAR + Descriptor.BYTE,
Descriptor.INT + Descriptor.BYTE,
new int[] { Opcode.I2S },
Descriptor.INT + Descriptor.SHORT,
new int[] { Opcode.I2C },
Descriptor.INT + Descriptor.CHAR,
new int[] { Opcode.L2I, Opcode.I2B },
Descriptor.LONG + Descriptor.BYTE,
new int[] { Opcode.L2I, Opcode.I2S },
Descriptor.LONG + Descriptor.SHORT,
Descriptor.LONG + Descriptor.CHAR,
new int[] { Opcode.L2I },
Descriptor.LONG + Descriptor.INT,
new int[] { Opcode.F2I, Opcode.I2B },
Descriptor.FLOAT + Descriptor.BYTE,
new int[] { Opcode.F2I, Opcode.I2S },
Descriptor.FLOAT + Descriptor.SHORT,
Descriptor.FLOAT + Descriptor.CHAR,
new int[] { Opcode.F2I },
Descriptor.FLOAT + Descriptor.INT,
new int[] { Opcode.F2L },
Descriptor.FLOAT + Descriptor.LONG,
new int[] { Opcode.D2I, Opcode.I2B },
Descriptor.DOUBLE + Descriptor.BYTE,
new int[] { Opcode.D2I, Opcode.I2S },
Descriptor.DOUBLE + Descriptor.SHORT,
Descriptor.DOUBLE + Descriptor.CHAR,
new int[] { Opcode.D2I },
Descriptor.DOUBLE + Descriptor.INT,
new int[] { Opcode.D2L },
Descriptor.DOUBLE + Descriptor.LONG,
new int[] { Opcode.D2F },
Descriptor.DOUBLE + Descriptor.FLOAT,
}, UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS); }
/**
* Checks if "constant assignment conversion" (JLS7 5.2, paragraph 1) is possible.
*
* @param constantValue The constant value that is to be converted
* @param targetType The type to convert to
*/
private boolean
tryConstantAssignmentConversion(Locatable locatable, @Nullable Object constantValue, IType targetType) {
UnitCompiler.LOGGER.entering(
null,
"tryConstantAssignmentConversion",
new Object[] { locatable, constantValue, targetType }
);
int cv;
if (constantValue instanceof Byte) {
cv = ((Byte) constantValue).byteValue();
} else
if (constantValue instanceof Short) {
cv = ((Short) constantValue).shortValue();
} else
if (constantValue instanceof Integer) {
cv = ((Integer) constantValue).intValue();
} else
if (constantValue instanceof Character) {
cv = ((Character) constantValue).charValue();
} else
{
return false;
}
if (targetType == IClass.BYTE) return cv >= Byte.MIN_VALUE && cv <= Byte.MAX_VALUE;
if (targetType == IClass.SHORT) return cv >= Short.MIN_VALUE && cv <= Short.MAX_VALUE;
if (targetType == IClass.CHAR) return cv >= Character.MIN_VALUE && cv <= Character.MAX_VALUE;
IClassLoader icl = this.iClassLoader;
if (targetType == icl.TYPE_java_lang_Byte && cv >= Byte.MIN_VALUE && cv <= Byte.MAX_VALUE) {
this.boxingConversion(locatable, IClass.BYTE, targetType);
return true;
}
if (targetType == icl.TYPE_java_lang_Short && cv >= Short.MIN_VALUE && cv <= Short.MAX_VALUE) {
this.boxingConversion(locatable, IClass.SHORT, targetType);
return true;
}
if (targetType == icl.TYPE_java_lang_Character && cv >= Character.MIN_VALUE && cv <= Character.MAX_VALUE) {
this.boxingConversion(locatable, IClass.CHAR, targetType);
return true;
}
return false;
}
/**
* Checks whether "narrowing reference conversion" (JLS7 5.1.5) is possible.
*/
private boolean
isNarrowingReferenceConvertible(IType sourceType, IType targetType) throws CompileException {
if (UnitCompiler.rawTypeOf(sourceType).isPrimitive()) return false;
if (sourceType == targetType) return false;
IClass rawSourceType = UnitCompiler.rawTypeOf(sourceType);
IClass rawTargetType = UnitCompiler.rawTypeOf(targetType);
// 5.1.5.1
if (rawSourceType.isAssignableFrom(rawTargetType)) return true;
// 5.1.5.2
if (rawTargetType.isInterface() && !rawSourceType.isFinal() && !rawTargetType.isAssignableFrom(rawSourceType)) return true;
// 5.1.5.3
if (sourceType == this.iClassLoader.TYPE_java_lang_Object && rawTargetType.isArray()) return true;
// 5.1.5.4
if (sourceType == this.iClassLoader.TYPE_java_lang_Object && rawTargetType.isInterface()) return true;
// 5.1.5.5
if (rawSourceType.isInterface() && !rawTargetType.isFinal()) return true;
// 5.1.5.6
if (rawSourceType.isInterface() && rawTargetType.isFinal() && rawSourceType.isAssignableFrom(rawTargetType)) return true;
// 5.1.5.7
// TODO: Check for redefinition of methods with same signature but different return type.
if (rawSourceType.isInterface() && rawTargetType.isInterface() && !rawTargetType.isAssignableFrom(rawSourceType)) {
return true;
}
// 5.1.5.8
if (rawSourceType.isArray() && rawTargetType.isArray()) {
IType st = rawSourceType.getComponentType();
assert st != null;
IType tt = rawTargetType.getComponentType();
assert tt != null;
if (this.isNarrowingPrimitiveConvertible(st, tt) || this.isNarrowingReferenceConvertible(st, tt)) {
return true;
}
}
return false;
}
/**
* Implements "narrowing reference conversion" (5.1.5).
*
* @return Whether the conversion succeeded
*/
private boolean
tryNarrowingReferenceConversion(Locatable locatable, IType sourceType, IType targetType) throws CompileException {
if (!this.isNarrowingReferenceConvertible(sourceType, targetType)) return false;
this.checkcast(locatable, targetType);
return true;
}
/**
* JLS7 5.5
*/
private boolean
isCastReferenceConvertible(IType sourceType, IType targetType) throws CompileException {
return (
this.isIdentityConvertible(sourceType, targetType)
|| this.isWideningReferenceConvertible(sourceType, targetType)
|| this.isNarrowingReferenceConvertible(sourceType, targetType)
);
}
/**
* @return The boxed type or {@code null}
*/
@Nullable private IClass
isBoxingConvertible(IType sourceType) {
IClassLoader icl = this.iClassLoader;
if (sourceType == IClass.BOOLEAN) return icl.TYPE_java_lang_Boolean;
if (sourceType == IClass.BYTE) return icl.TYPE_java_lang_Byte;
if (sourceType == IClass.CHAR) return icl.TYPE_java_lang_Character;
if (sourceType == IClass.SHORT) return icl.TYPE_java_lang_Short;
if (sourceType == IClass.INT) return icl.TYPE_java_lang_Integer;
if (sourceType == IClass.LONG) return icl.TYPE_java_lang_Long;
if (sourceType == IClass.FLOAT) return icl.TYPE_java_lang_Float;
if (sourceType == IClass.DOUBLE) return icl.TYPE_java_lang_Double;
return null;
}
private boolean
tryBoxingConversion(Locatable locatable, IType sourceType, IType targetType) {
if (this.isBoxingConvertible(sourceType) == targetType) {
this.boxingConversion(locatable, sourceType, targetType);
return true;
}
return false;
}
/**
* @param sourceType a primitive type (except VOID)
* @param targetType the corresponding wrapper type
*/
private void
boxingConversion(Locatable locatable, IType sourceType, IType targetType) {
assert targetType instanceof IClass;
IClass targetClass = (IClass) targetType;
assert sourceType instanceof IClass;
IClass sourceClass = (IClass) sourceType;
this.invoke(
locatable, // locatable
Opcode.INVOKESTATIC, // opcode
targetClass, // declaringIClass
"valueOf", // methodName
new MethodDescriptor( // methodFD
targetClass.getDescriptor(), // returnFd
sourceClass.getDescriptor() // parameterFds...
),
false // useInterfaceMethodref
);
}
/**
* @return Iff sourceType is a primitive wrapper type, the unboxed type, otherwise {@code null}
*/
@Nullable private IClass
isUnboxingConvertible(IType sourceType) {
IClassLoader icl = this.iClassLoader;
if (sourceType == icl.TYPE_java_lang_Boolean) return IClass.BOOLEAN;
if (sourceType == icl.TYPE_java_lang_Byte) return IClass.BYTE;
if (sourceType == icl.TYPE_java_lang_Character) return IClass.CHAR;
if (sourceType == icl.TYPE_java_lang_Short) return IClass.SHORT;
if (sourceType == icl.TYPE_java_lang_Integer) return IClass.INT;
if (sourceType == icl.TYPE_java_lang_Long) return IClass.LONG;
if (sourceType == icl.TYPE_java_lang_Float) return IClass.FLOAT;
if (sourceType == icl.TYPE_java_lang_Double) return IClass.DOUBLE;
return null;
}
/**
* @return Whether the sourceType is a primitive numeric type, or a wrapper type of a primitive numeric
* type
*/
private boolean
isConvertibleToPrimitiveNumeric(IType sourceType) {
if (sourceType instanceof IClass && ((IClass) sourceType).isPrimitiveNumeric()) return true;
IClass unboxedType = this.isUnboxingConvertible(sourceType);
return unboxedType != null && unboxedType.isPrimitiveNumeric();
}
/**
* @param targetType a primitive type (except VOID)
* @param sourceType the corresponding wrapper type
*/
private void
unboxingConversion(Locatable locatable, IType sourceType, IClass targetType) {
assert sourceType instanceof IClass;
// "source.intValue()"
this.invoke(
locatable, // locatable
Opcode.INVOKEVIRTUAL, // opcode
(IClass) sourceType, // declaringIClass
targetType.toString() + "Value", // methodName
new MethodDescriptor(targetType.getDescriptor(), new String[0]), // methodMd
false // useInterfaceMethodref
);
}
/**
* Attempts to load an {@link IClass} by fully-qualified name through {@link #iClassLoader}.
*
* @param identifiers The fully qualified type name, e.g. '{@code { "pkg", "Outer", "Inner" }}'
* @return {@code null} if a class with the given name could not be loaded
* @throws CompileException The type exists, but a problem occurred when it was loaded
*/
@Nullable private IClass
findTypeByFullyQualifiedName(Location location, String[] identifiers) throws CompileException {
// Try all 'flavors', i.e. 'a.b.c', 'a.b$c', 'a$b$c'.
String className = Java.join(identifiers, ".");
for (;;) {
IClass iClass = UnitCompiler.this.findTypeByName(location, className);
if (iClass != null) return iClass;
int idx = className.lastIndexOf('.');
if (idx == -1) break;
className = className.substring(0, idx) + '$' + className.substring(idx + 1);
}
return null;
}
/**
* @param opIdx One of {@link #EQ}, {@link #NE}, {@link #LT}, {@link #GE}, {@link #GT} or {@link #LE}
* @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE}
*/
private void
ifNumeric(Locatable locatable, int opIdx, Offset dst, boolean orientation) {
assert opIdx >= UnitCompiler.EQ && opIdx <= UnitCompiler.LE;
VerificationTypeInfo topOperand = this.getCodeContext().peekOperand();
if (topOperand == StackMapTableAttribute.INTEGER_VARIABLE_INFO) {
this.if_icmpxx(locatable, orientation == UnitCompiler.JUMP_IF_FALSE ? opIdx ^ 1 : opIdx, dst);
} else
if (
topOperand == StackMapTableAttribute.LONG_VARIABLE_INFO
|| topOperand == StackMapTableAttribute.FLOAT_VARIABLE_INFO
|| topOperand == StackMapTableAttribute.DOUBLE_VARIABLE_INFO
) {
// Important: The opIdx here must be the *original* opIdx (and not the possibly negated!), because that is
// important for floating-point comparison! See PR #145.
this.cmp(locatable, opIdx);
this.ifxx(locatable, orientation == UnitCompiler.JUMP_IF_FALSE ? opIdx ^ 1 : opIdx, dst);
} else
{
throw new InternalCompilerException(locatable.getLocation(), "Unexpected computational type \"" + topOperand + "\"");
}
}
// ============================= BYTE CODE GENERATION METHODS, IN ALPHABETICAL ORDER =============================
private void
aconstnull(Locatable locatable) {
this.addLineNumberOffset(locatable);
this.write(Opcode.ACONST_NULL);
this.getCodeContext().pushNullOperand();
}
private void
add(Locatable locatable) { this.mulDivRemAddSub(locatable, "+"); }
private void
andOrXor(Locatable locatable, String operator) {
VerificationTypeInfo operand2 = this.getCodeContext().popIntOrLongOperand();
VerificationTypeInfo operand1 = this.getCodeContext().popIntOrLongOperand();
assert operand1 == operand2;
final int opcode = (
operator == "&" ? Opcode.IAND :
operator == "|" ? Opcode.IOR :
operator == "^" ? Opcode.IXOR :
Integer.MAX_VALUE
) + (operand1 == StackMapTableAttribute.LONG_VARIABLE_INFO ? 1 : 0);
this.addLineNumberOffset(locatable);
this.write(opcode);
this.getCodeContext().pushOperand(operand1);
}
private void
anewarray(Locatable locatable, IClass componentType) {
IClass arrayType = this.iClassLoader.getArrayIClass(componentType);
this.addLineNumberOffset(locatable);
this.getCodeContext().popIntOperand();
this.write(Opcode.ANEWARRAY);
this.writeConstantClassInfo(componentType);
this.getCodeContext().pushObjectOperand(arrayType.getDescriptor());
}
private void
arraylength(Locatable locatable) {
this.addLineNumberOffset(locatable);
try {
this.getCodeContext().popObjectOperand();
this.write(Opcode.ARRAYLENGTH);
this.getCodeContext().pushIntOperand();
} catch (AssertionError ae) {
throw new InternalCompilerException(locatable.getLocation(), null, ae);
}
}
private void
arraystore(Locatable locatable, IType lhsComponentType) {
this.addLineNumberOffset(locatable);
this.getCodeContext().popOperand();
this.getCodeContext().popOperand();
this.getCodeContext().popOperand();
this.write(Opcode.IASTORE + UnitCompiler.ilfdabcs(UnitCompiler.rawTypeOf(lhsComponentType)));
}
private void
athrow(Locatable locatable) {
this.addLineNumberOffset(locatable);
this.write(Opcode.ATHROW);
this.codeContext.currentInserter().setStackMap(null);
}
private void
checkcast(Locatable locatable, IType targetType) {
IClass rawTargetType = UnitCompiler.rawTypeOf(targetType);
this.addLineNumberOffset(locatable);
this.write(Opcode.CHECKCAST);
this.writeConstantClassInfo(rawTargetType);
this.getCodeContext().popOperand();
this.getCodeContext().pushObjectOperand(rawTargetType.getDescriptor());
}
/**
* @param opIdx One of {@link #EQ}, {@link #NE}, {@link #LT}, {@link #GE}, {@link #GT} or {@link #LE}
*/
private void
cmp(Locatable locatable, int opIdx) {
assert opIdx >= UnitCompiler.EQ && opIdx <= UnitCompiler.LE;
VerificationTypeInfo operand2 = this.getCodeContext().currentInserter().getStackMap().peekOperand();
this.getCodeContext().popOperand();
VerificationTypeInfo operand1 = this.getCodeContext().currentInserter().getStackMap().peekOperand();
this.getCodeContext().popOperand();
if (operand1 == StackMapTableAttribute.LONG_VARIABLE_INFO && operand2 == StackMapTableAttribute.LONG_VARIABLE_INFO) {
this.write(Opcode.LCMP);
} else
if (operand1 == StackMapTableAttribute.FLOAT_VARIABLE_INFO && operand2 == StackMapTableAttribute.FLOAT_VARIABLE_INFO) {
this.write(opIdx == UnitCompiler.GE || opIdx == UnitCompiler.GT ? Opcode.FCMPL : Opcode.FCMPG);
} else
if (operand1 == StackMapTableAttribute.DOUBLE_VARIABLE_INFO && operand2 == StackMapTableAttribute.DOUBLE_VARIABLE_INFO) {
this.write(opIdx == UnitCompiler.GE || opIdx == UnitCompiler.GT ? Opcode.DCMPL : Opcode.DCMPG);
} else
{
throw new AssertionError(operand1 + " and " + operand2);
}
this.getCodeContext().pushIntOperand();
}
/**
* Duplicates the top operand: ... a => ... a a
*/
private void
dup(Locatable locatable) {
VerificationTypeInfo topOperand = this.getCodeContext().peekOperand();
this.addLineNumberOffset(locatable);
this.write(topOperand.category() == 1 ? Opcode.DUP : Opcode.DUP2);
this.getCodeContext().pushOperand(topOperand);
}
/**
* Duplicates the top two operands: ... a b => ... a b a b.
* This works iff both the top operand and the top-but-one operand have size 1.
*/
private void
dup2(Locatable locatable) {
this.addLineNumberOffset(locatable);
VerificationTypeInfo topOperand = this.getCodeContext().popOperand();
assert topOperand.category() == 1;
VerificationTypeInfo topButOneOperand = this.getCodeContext().popOperand();
assert topButOneOperand.category() == 1;
this.write(Opcode.DUP2);
this.getCodeContext().pushOperand(topButOneOperand);
this.getCodeContext().pushOperand(topOperand);
this.getCodeContext().pushOperand(topButOneOperand);
this.getCodeContext().pushOperand(topOperand);
}
/**
* Duplicates the top n operands.
*
* n == 0 (nothing)
* n == 1 ... a => ... a a
* n == 2 ... a b => ... a b a b
*
*/
private void
dupn(Locatable locatable, int n) {
switch (n) {
case 0: ; break;
case 1: this.dup(locatable); break;
case 2: this.dup2(locatable); break;
default: throw new AssertionError(n);
}
}
/**
* Copies the top operand one position down: b a => a b a
*/
private void
dupx(Locatable locatable) {
VerificationTypeInfo topOperand = this.getCodeContext().popOperand();
VerificationTypeInfo topButOneOperand = this.getCodeContext().popOperand();
this.addLineNumberOffset(locatable);
this.write((
topOperand.category() == 1
? (topButOneOperand.category() == 1 ? Opcode.DUP_X1 : Opcode.DUP_X2)
: (topButOneOperand.category() == 1 ? Opcode.DUP2_X1 : Opcode.DUP2_X2)
));
this.getCodeContext().pushOperand(topOperand);
this.getCodeContext().pushOperand(topButOneOperand);
this.getCodeContext().pushOperand(topOperand);
}
/**
* Copies the top operand two positions down: c b a => a c b a.
* This works iff the top-but-one and top-but-two operands both have size 1.
*/
private void
dupx2(Locatable locatable) {
VerificationTypeInfo topOperand = this.getCodeContext().popOperand();
VerificationTypeInfo topButOneOperand = this.getCodeContext().popOperand();
VerificationTypeInfo topButTwoOperand = this.getCodeContext().popOperand();
assert topButOneOperand.category() == 1;
assert topButTwoOperand.category() == 1;
this.addLineNumberOffset(locatable);
this.write(topOperand.category() == 1 ? Opcode.DUP_X2 : Opcode.DUP2_X2);
this.getCodeContext().pushOperand(topOperand);
this.getCodeContext().pushOperand(topButTwoOperand);
this.getCodeContext().pushOperand(topButOneOperand);
this.getCodeContext().pushOperand(topOperand);
}
/**
* Copies the top operand positions down.
*
* n == 0 ... a => ... a a
* n == 1 ... b a => ... a b a
* n == 2 ... c b a => ... a c b a
*
*/
private void
dupxx(Locatable locatable, int positions) {
switch (positions) {
case 0: this.dup(locatable); break;
case 1: this.dupx(locatable); break;
case 2: this.dupx2(locatable); break;
default: throw new AssertionError(positions);
}
}
private void
getfield(Locatable locatable, IClass.IField iField) throws CompileException {
this.getfield(
locatable, // locatable
iField.getDeclaringIClass(), // declaringIClass
iField.getName(), // fieldName
iField.getType(), // fieldType
iField.isStatic() // statiC
);
}
private void
getfield(Locatable locatable, IClass declaringIClass, String fieldName, IClass fieldType, boolean statiC) {
this.addLineNumberOffset(locatable);
if (statiC) {
this.write(Opcode.GETSTATIC);
} else {
this.write(Opcode.GETFIELD);
this.getCodeContext().popOperand();
}
this.writeConstantFieldrefInfo(
declaringIClass, // iClass
fieldName, // fieldName
fieldType // fieldType
);
this.getCodeContext().pushOperand(fieldType.getDescriptor());
}
/**
* @param opcode One of IF* and GOTO
*/
private void
gotO(Locatable locatable, CodeContext.Offset dst) {
assert dst instanceof CodeContext.BasicBlock;
this.getCodeContext().writeBranch(Opcode.GOTO, dst);
this.getCodeContext().currentInserter().setStackMap(null);
}
/**
* @param opIdx {@link #EQ} or {@link #NE}
*/
private void
if_acmpxx(Locatable locatable, int opIdx, CodeContext.Offset dst) {
assert opIdx == UnitCompiler.EQ || opIdx == UnitCompiler.NE : opIdx;
this.addLineNumberOffset(locatable);
this.getCodeContext().writeBranch(Opcode.IF_ACMPEQ + opIdx, dst);
this.getCodeContext().popReferenceOperand();
this.getCodeContext().popReferenceOperand();
dst.setStackMap(this.getCodeContext().currentInserter().getStackMap());
}
/**
* @param opIdx One of {@link #EQ}, {@link #NE}, {@link #LT}, {@link #GE}, {@link #GT} or {@link #LE}
*/
private void
if_icmpxx(Locatable locatable, int opIdx, CodeContext.Offset dst) {
assert opIdx >= UnitCompiler.EQ && opIdx <= UnitCompiler.LE;
assert dst instanceof BasicBlock;
this.addLineNumberOffset(locatable);
this.getCodeContext().writeBranch(Opcode.IF_ICMPEQ + opIdx, dst);
this.getCodeContext().popIntOperand();
this.getCodeContext().popIntOperand();
dst.setStackMap(this.getCodeContext().currentInserter().getStackMap());
}
private static final int EQ = 0;
private static final int NE = 1;
private static final int LT = 2;
private static final int GE = 3;
private static final int GT = 4;
private static final int LE = 5;
private void
ifnonnull(Locatable locatable, CodeContext.Offset dst) {
this.getCodeContext().writeBranch(Opcode.IFNONNULL, dst);
this.getCodeContext().popReferenceOperand();
dst.setStackMap(this.getCodeContext().currentInserter().getStackMap());
}
private void
ifnull(Locatable locatable, CodeContext.Offset dst) {
this.getCodeContext().writeBranch(Opcode.IFNULL, dst);
this.getCodeContext().popReferenceOperand();
dst.setStackMap(this.getCodeContext().currentInserter().getStackMap());
}
/**
* @param opIdx One of {@link #EQ}, {@link #NE}, {@link #LT}, {@link #GE}, {@link #GT} or {@link #LE}
*/
private void
ifxx(Locatable locatable, int opIdx, CodeContext.Offset dst) {
assert opIdx >= UnitCompiler.EQ && opIdx <= UnitCompiler.LE;
this.addLineNumberOffset(locatable);
this.getCodeContext().writeBranch(Opcode.IFEQ + opIdx, dst);
this.getCodeContext().popIntOperand();
dst.setStackMap(this.getCodeContext().currentInserter().getStackMap());
}
/**
* @param operator Must be either "++" or "--", as an @link {@link String#intern() interned} string
*/
private void
iinc(Locatable locatable, LocalVariable lv, String operator) {
this.addLineNumberOffset(locatable);
if (lv.getSlotIndex() > 255) {
this.write(Opcode.WIDE);
this.write(Opcode.IINC);
this.writeShort(lv.getSlotIndex());
this.writeShort(operator == "++" ? 1 : -1); // SUPPRESS CHECKSTYLE StringLiteralEquality
} else {
this.write(Opcode.IINC);
this.writeByte(lv.getSlotIndex());
this.writeByte(operator == "++" ? 1 : -1); // SUPPRESS CHECKSTYLE StringLiteralEquality
}
}
private void
instanceoF(Locatable locatable, IType rhsType) {
this.addLineNumberOffset(locatable);
this.getCodeContext().popReferenceOperand();
this.write(Opcode.INSTANCEOF);
this.writeConstantClassInfo(UnitCompiler.rawTypeOf(rhsType));
this.getCodeContext().pushIntOperand();
}
/**
* Expects the target object and the arguments on the operand stack.
*/
private void
invoke(
Locatable locatable,
int opcode,
IClass declaringIClass,
String methodName,
MethodDescriptor methodDescriptor,
boolean useInterfaceMethodRef
) {
this.addLineNumberOffset(locatable);
// Pop the arguments off the operand stack.
for (int i = methodDescriptor.parameterFds.length - 1; i >= 0; i--) {
this.getCodeContext().popOperandAssignableTo(methodDescriptor.parameterFds[i]);
}
// Pop the target object off the operand stack.
if (opcode == Opcode.INVOKEINTERFACE || opcode == Opcode.INVOKESPECIAL || opcode == Opcode.INVOKEVIRTUAL) {
this.getCodeContext().popObjectOrUninitializedOrUninitializedThisOperand();
}
// Generate the instruction.
this.write(opcode);
if (useInterfaceMethodRef) {
this.writeConstantInterfaceMethodrefInfo(declaringIClass, methodName, methodDescriptor);
} else {
this.writeConstantMethodrefInfo(declaringIClass, methodName, methodDescriptor);
}
switch (opcode) {
case Opcode.INVOKEDYNAMIC:
this.writeByte(0);
this.writeByte(0);
break;
case Opcode.INVOKEINTERFACE:
int count = 1;
for (String pfd : methodDescriptor.parameterFds) count += Descriptor.size(pfd);
this.writeByte(count);
this.writeByte(0);
break;
case Opcode.INVOKESPECIAL:
case Opcode.INVOKESTATIC:
case Opcode.INVOKEVIRTUAL:
;
break;
default:
throw new AssertionError(opcode);
}
if (!methodDescriptor.returnFd.equals(Descriptor.VOID)) {
this.getCodeContext().pushOperand(methodDescriptor.returnFd);
}
}
private void
l2i(Locatable locatable) {
this.addLineNumberOffset(locatable);
this.getCodeContext().popLongOperand();
this.write(Opcode.L2I);
this.getCodeContext().pushIntOperand();
}
// Load the value of a local variable onto the stack and return its type.
private IType
load(Locatable locatable, LocalVariable localVariable) {
this.load(locatable, localVariable.type, localVariable.getSlotIndex());
return localVariable.type;
}
private void
load(Locatable locatable, IType localVariableType, int localVariableIndex) {
assert localVariableIndex >= 0 && localVariableIndex <= 65535;
this.addLineNumberOffset(locatable);
IClass rawClass = UnitCompiler.rawTypeOf(localVariableType);
if (localVariableIndex <= 3) {
this.write(Opcode.ILOAD_0 + 4 * UnitCompiler.ilfda(rawClass) + localVariableIndex);
} else
if (localVariableIndex <= 255) {
this.write(Opcode.ILOAD + UnitCompiler.ilfda(rawClass));
this.write(localVariableIndex);
} else
{
this.write(Opcode.WIDE);
this.write(Opcode.ILOAD + UnitCompiler.ilfda(rawClass));
this.writeUnsignedShort(localVariableIndex);
}
VerificationTypeInfo vti = this.getLocalVariableTypeInfo((short) localVariableIndex);
this.getCodeContext().pushOperand(vti);
}
private void
lookupswitch(
Locatable locatable,
SortedMap caseLabelMap,
Offset defaultLabelOffset
) {
CodeContext.Offset switchOffset = this.getCodeContext().newOffset();
this.addLineNumberOffset(locatable);
this.getCodeContext().popIntOperand();
StackMap smAtCase = this.getCodeContext().currentInserter().getStackMap();
this.write(Opcode.LOOKUPSWITCH); // lookupswitch
new Padder(this.getCodeContext()).set(); // 0-3 byte pad
defaultLabelOffset.setStackMap(smAtCase);
this.writeOffset(switchOffset, defaultLabelOffset); // defaultbyte1-4
this.writeInt(caseLabelMap.size()); // npairs1-4
for (Map.Entry me : caseLabelMap.entrySet()) {
Integer match = (Integer) me.getKey();
CodeContext.Offset offset = (CodeContext.Offset) me.getValue();
offset.setStackMap(smAtCase);
this.writeInt(match); // match
this.writeOffset(switchOffset, offset); // offset
}
}
private void
monitorenter(Locatable locatable) {
this.addLineNumberOffset(locatable);
this.getCodeContext().popReferenceOperand();
this.write(Opcode.MONITORENTER);
}
private void
monitorexit(Locatable locatable) {
this.addLineNumberOffset(locatable);
this.getCodeContext().popReferenceOperand();
this.write(Opcode.MONITOREXIT);
}
/**
* @param operator One of {@code * / % + -}
*/
private void
mulDivRemAddSub(Locatable locatable, String operator) {
VerificationTypeInfo operand2 = this.getCodeContext().popOperand();
VerificationTypeInfo operand1 = this.getCodeContext().popOperand();
assert operand1 == operand2 : operand1 + " vs. " + operand2;
final int opcode = (
operator == "*" ? Opcode.IMUL :
operator == "/" ? Opcode.IDIV :
operator == "%" ? Opcode.IREM :
operator == "+" ? Opcode.IADD :
operator == "-" ? Opcode.ISUB :
Integer.MAX_VALUE
) + UnitCompiler.ilfd(operand1);
this.addLineNumberOffset(locatable);
this.write(opcode);
this.getCodeContext().pushOperand(operand1);
}
private void
multianewarray(Locatable locatable, int dimExprCount, int dims, IType componentType) {
IClass arrayType = this.iClassLoader.getArrayIClass(
UnitCompiler.rawTypeOf(componentType),
dimExprCount + dims
);
this.addLineNumberOffset(locatable);
for (int i = 0; i < dimExprCount; i++) this.getCodeContext().popIntOperand();
this.write(Opcode.MULTIANEWARRAY);
this.writeConstantClassInfo(arrayType);
this.writeByte(dimExprCount);
this.getCodeContext().pushObjectOperand(arrayType.getDescriptor());
}
/**
* @param operandType One of BYTE, CHAR, INT, SHORT, LONG, BOOLEAN, LONG, FLOAT, DOUBLE
*/
private void
neg(Locatable locatable, IClass operandType) {
this.addLineNumberOffset(locatable);
this.write(Opcode.INEG + UnitCompiler.ilfd(operandType));
}
private void
neW(Locatable locatable, IType iType) {
this.addLineNumberOffset(locatable);
this.getCodeContext().pushUninitializedOperand();
this.write(Opcode.NEW);
this.writeConstantClassInfo(UnitCompiler.rawTypeOf(iType));
}
private void
newarray(Locatable locatable, IType componentType) {
IClass arrayType = this.iClassLoader.getArrayIClass(UnitCompiler.rawTypeOf(componentType));
this.addLineNumberOffset(locatable);
this.getCodeContext().popIntOperand();
this.write(Opcode.NEWARRAY);
this.writeByte((
componentType == IClass.BOOLEAN ? 4 :
componentType == IClass.CHAR ? 5 :
componentType == IClass.FLOAT ? 6 :
componentType == IClass.DOUBLE ? 7 :
componentType == IClass.BYTE ? 8 :
componentType == IClass.SHORT ? 9 :
componentType == IClass.INT ? 10 :
componentType == IClass.LONG ? 11 : -1
));
this.getCodeContext().pushObjectOperand(arrayType.getDescriptor());
}
private void
pop(Locatable locatable, IType type) {
if (type == IClass.VOID) return;
this.addLineNumberOffset(locatable);
this.write(type == IClass.LONG || type == IClass.DOUBLE ? Opcode.POP2 : Opcode.POP);
this.getCodeContext().popOperand(UnitCompiler.rawTypeOf(type).getDescriptor());
}
private void
putfield(Locatable locatable, IField iField) throws CompileException {
this.addLineNumberOffset(locatable);
this.getCodeContext().popOperand();
if (iField.isStatic()) {
this.write(Opcode.PUTSTATIC);
} else {
this.write(Opcode.PUTFIELD);
this.getCodeContext().popOperand();
}
this.writeConstantFieldrefInfo(
iField.getDeclaringIClass(), // iClass
iField.getName(), // fieldName
iField.getType() // fieldType
);
}
private void
returN(Locatable locatable) {
this.addLineNumberOffset(locatable);
this.write(Opcode.RETURN);
this.codeContext.currentInserter().setStackMap(null);
}
/**
* @param operator One of {@code << >> >>>}
*/
private void
shift(Locatable locatable, String operator) {
this.getCodeContext().popIntOperand();
VerificationTypeInfo operand1 = this.getCodeContext().popIntOrLongOperand();
final int iopcode = (
operator == "<<" ? Opcode.ISHL :
operator == ">>" ? Opcode.ISHR :
operator == ">>>" ? Opcode.IUSHR :
Integer.MAX_VALUE
);
int opcode = iopcode + UnitCompiler.il(operand1);
this.addLineNumberOffset(locatable);
this.write(opcode);
this.getCodeContext().pushOperand(operand1);
}
/**
* Assigns the top operand to the given local variable.
*/
private void
store(Locatable locatable, LocalVariable localVariable) {
this.store(
locatable, // locatable
localVariable.type, // lvType
localVariable.getSlotIndex() // lvIndex
);
}
/**
* @param lvIndex (two slots for LONG and DOUBLE local variables)
*/
private void
store(Locatable locatable, IType lvType, short lvIndex) {
this.addLineNumberOffset(locatable);
if (lvIndex <= 3) {
this.write(Opcode.ISTORE_0 + 4 * UnitCompiler.ilfda(lvType) + lvIndex);
} else
if (lvIndex <= 255) {
this.write(Opcode.ISTORE + UnitCompiler.ilfda(lvType));
this.write(lvIndex);
} else
{
this.write(Opcode.WIDE);
this.write(Opcode.ISTORE + UnitCompiler.ilfda(lvType));
this.writeUnsignedShort(lvIndex);
}
this.getCodeContext().popOperand();
this.updateLocalVariableInCurrentStackMap(lvIndex, this.verificationTypeInfo(lvType));
}
private void
sub(Locatable locatable) { this.mulDivRemAddSub(locatable, "-"); }
private void
swap(Locatable locatable) {
VerificationTypeInfo topOperand = this.getCodeContext().popOperand();
VerificationTypeInfo topButOneOperand = this.getCodeContext().popOperand();
this.addLineNumberOffset(locatable);
this.write(Opcode.SWAP);
this.getCodeContext().pushOperand(topOperand);
this.getCodeContext().pushOperand(topButOneOperand);
}
private void
tableswitch(
Locatable locatable,
SortedMap caseLabelMap,
Offset defaultLabelOffset
) {
assert defaultLabelOffset instanceof CodeContext.BasicBlock;
CodeContext.Offset switchOffset = this.getCodeContext().newOffset();
final int low = (Integer) caseLabelMap.firstKey();
final int high = (Integer) caseLabelMap.lastKey();
this.addLineNumberOffset(locatable);
this.getCodeContext().popIntOperand();
StackMap smAtCase = this.getCodeContext().currentInserter().getStackMap();
this.write(Opcode.TABLESWITCH);
new Padder(this.getCodeContext()).set();
defaultLabelOffset.setStackMap(smAtCase);
this.writeOffset(switchOffset, defaultLabelOffset);
this.writeInt(low);
this.writeInt(high);
int cur = low;
for (Map.Entry me : caseLabelMap.entrySet()) {
int caseLabelValue = (Integer) me.getKey();
CodeContext.Offset caseLabelOffset = (CodeContext.Offset) me.getValue();
assert caseLabelOffset instanceof BasicBlock;
caseLabelOffset.setStackMap(smAtCase);
while (cur < caseLabelValue) {
this.writeOffset(switchOffset, defaultLabelOffset);
++cur;
}
this.writeOffset(switchOffset, caseLabelOffset);
++cur;
}
}
private void
xaload(Locatable locatable, IType componentType) {
this.addLineNumberOffset(locatable);
IClass rawComponentType = UnitCompiler.rawTypeOf(componentType);
this.getCodeContext().popIntOperand();
this.getCodeContext().popReferenceOperand();
this.write(Opcode.IALOAD + UnitCompiler.ilfdabcs(rawComponentType));
this.getCodeContext().pushOperand(rawComponentType.getDescriptor());
}
private void
xor(Locatable locatable, int opcode) {
if (opcode != Opcode.IXOR && opcode != Opcode.LXOR) throw new AssertionError(opcode);
this.addLineNumberOffset(locatable);
this.write(opcode);
this.getCodeContext().popOperand();
}
private void
xreturn(Locatable locatable, IType returnType) {
this.addLineNumberOffset(locatable);
this.write(Opcode.IRETURN + UnitCompiler.ilfda(returnType));
this.codeContext.currentInserter().setStackMap(null);
}
/**
* @param t One of BYTE, CHAR, INT, SHORT, LONG, BOOLEAN, LONG, FLOAT, DOUBLE
*/
private static int
ilfd(final IType t) {
if (t == IClass.BYTE || t == IClass.CHAR || t == IClass.INT || t == IClass.SHORT || t == IClass.BOOLEAN) {
return 0;
}
if (t == IClass.LONG) return 1;
if (t == IClass.FLOAT) return 2;
if (t == IClass.DOUBLE) return 3;
throw new InternalCompilerException("Unexpected type \"" + t + "\"");
}
private static int
ilfd(VerificationTypeInfo vti) {
if (vti == StackMapTableAttribute.INTEGER_VARIABLE_INFO) return 0;
if (vti == StackMapTableAttribute.LONG_VARIABLE_INFO) return 1;
if (vti == StackMapTableAttribute.FLOAT_VARIABLE_INFO) return 2;
if (vti == StackMapTableAttribute.DOUBLE_VARIABLE_INFO) return 3;
throw new InternalCompilerException("Unexpected type \"" + vti + "\"");
}
private static int
ilfda(IType t) { return !UnitCompiler.isPrimitive(t) ? 4 : UnitCompiler.ilfd(t); }
private static int
il(VerificationTypeInfo vti) {
if (vti == StackMapTableAttribute.INTEGER_VARIABLE_INFO) return 0;
if (vti == StackMapTableAttribute.LONG_VARIABLE_INFO) return 1;
throw new AssertionError(vti);
}
private static int
ilfdabcs(IClass t) {
if (t == IClass.INT) return 0;
if (t == IClass.LONG) return 1;
if (t == IClass.FLOAT) return 2;
if (t == IClass.DOUBLE) return 3;
if (!t.isPrimitive()) return 4;
if (t == IClass.BOOLEAN) return 5;
if (t == IClass.BYTE) return 5;
if (t == IClass.CHAR) return 6;
if (t == IClass.SHORT) return 7;
throw new InternalCompilerException("Unexpected type \"" + t + "\"");
}
/**
* Finds a named field in the given {@link IClass}. Honors superclasses and interfaces. See JLS7 8.3.
*
* @return {@code null} if no field is found
*/
@Nullable private IClass.IField
findIField(IClass iClass, String name, Location location) throws CompileException {
// Search for a field with the given name in the current class.
IClass.IField f = iClass.getDeclaredIField(name);
if (f != null) return f;
// Examine superclass.
{
IClass superclass = iClass.getSuperclass();
if (superclass != null) f = this.findIField(superclass, name, location);
}
// Examine interfaces.
IClass[] ifs = iClass.getInterfaces();
for (IClass iF : ifs) {
IClass.IField f2 = this.findIField(iF, name, location);
if (f2 != null) {
if (f != null) {
throw new CompileException((
"Access to field \""
+ name
+ "\" is ambiguous - both \""
+ f.getDeclaringIClass()
+ "\" and \""
+ f2.getDeclaringIClass()
+ "\" declare it"
), location);
}
f = f2;
}
}
return f;
}
/**
* Finds a named type in the given {@link IClass} or {@link IParameterizedType}. Honors superclasses, interfaces
* and enclosing type declarations.
*
* @return {@code null} if no type with the given name is found
*/
@Nullable private IClass
findMemberType(IType iType, String name, @Nullable TypeArgument[] typeArguments, Location location)
throws CompileException {
IClass[] types = UnitCompiler.rawTypeOf(iType).findMemberType(name);
if (types.length == 0) return null;
if (types.length == 1) return types[0];
StringBuilder sb = new StringBuilder("Type \"").append(name).append("\" is ambiguous: ").append(types[0]);
for (int i = 1; i < types.length; ++i) sb.append(" vs. ").append(types[i].toString());
this.compileError(sb.toString(), location);
return types[0];
}
/**
* Finds one class or interface declaration in this compilation unit and resolves it into an {@link IClass}.
*
* @param className Fully qualified class name, e.g. "pkg1.pkg2.Outer$Inner"
* @return {@code null} if a class or an interface with that name is not declared in this compilation unit
*/
@Nullable public IClass
findClass(String className) {
AbstractCompilationUnit acu = this.abstractCompilationUnit;
if (!(acu instanceof CompilationUnit)) return null;
CompilationUnit cu = (CompilationUnit) acu;
// Examine package name.
PackageDeclaration opd = cu.packageDeclaration;
if (opd != null) {
String packageName = opd.packageName;
if (!className.startsWith(packageName + '.')) return null;
className = className.substring(packageName.length() + 1);
}
// Attempt to find the type declaration by name "as is", i.e. including any dollar signs.
TypeDeclaration td = cu.getPackageMemberTypeDeclaration(className);
if (td == null) {
int idx = className.indexOf('$');
if (idx == -1) return null;
StringTokenizer st = new StringTokenizer(className, "$");
td = cu.getPackageMemberTypeDeclaration(st.nextToken());
if (td == null) return null;
while (st.hasMoreTokens()) {
td = td.getMemberTypeDeclaration(st.nextToken());
if (td == null) return null;
}
}
return this.resolve(td);
}
/**
* Equivalent with {@link #compileError(String, Location)} with a {@code null} location argument.
*/
private void
compileError(String message) throws CompileException { this.compileError(message, null); }
/**
* Issues a compile error with the given message. This is done through the {@link ErrorHandler} that was installed
* through {@link #setCompileErrorHandler(ErrorHandler)}. Such a handler typically throws a {@link
* CompileException}, but it may as well decide to return normally. Consequently, the calling code must be prepared
* that {@link #compileError(String, Location)} returns normally, and must attempt to continue compiling.
*
* @param message The message to report
* @param location The location to report
*/
private void
compileError(String message, @Nullable Location location) throws CompileException {
++this.compileErrorCount;
if (this.compileErrorHandler != null) {
this.compileErrorHandler.handleError(message, location);
} else {
throw new CompileException(message, location);
}
}
/**
* Issues a warning with the given message an location an returns. This is done through a {@link WarningHandler}
* that was installed through {@link #setWarningHandler(WarningHandler)}.
*
* The handle argument qualifies the warning and is typically used by the {@link WarningHandler} to
* suppress individual warnings.
*
*/
private void
warning(String handle, String message, @Nullable Location location) throws CompileException {
if (this.warningHandler != null) {
this.warningHandler.handleWarning(handle, message, location);
}
}
/**
* By default, {@link CompileException}s are thrown on compile errors, but an application my install its own
* (thread-local) {@link ErrorHandler}.
*
* Be aware that a single problem during compilation often causes a bunch of compile errors, so a good {@link
* ErrorHandler} counts errors and throws a {@link CompileException} when a limit is reached.
*
*
* If the given {@link ErrorHandler} does not throw {@link CompileException}s, then {@link
* #compileUnit(boolean, boolean, boolean, ClassFileConsumer)} will throw one when the compilation of the unit is
* finished, and errors had occurred. In other words: The {@link ErrorHandler} may throw a {@link
* CompileException} or not, but {@link #compileUnit(boolean, boolean, boolean, ClassFileConsumer)} will
* definitely throw a {@link CompileException} if one or more compile errors have occurred.
*
*
* @param compileErrorHandler {@code null} to restore the default behavior (throwing a {@link
* CompileException})
*/
public void
setCompileErrorHandler(@Nullable ErrorHandler compileErrorHandler) {
this.compileErrorHandler = compileErrorHandler;
}
/**
* By default, warnings are discarded, but an application my install a custom {@link WarningHandler}.
*
* @param warningHandler {@code null} to indicate that no warnings be issued
*/
public void
setWarningHandler(@Nullable WarningHandler warningHandler) {
this.warningHandler = warningHandler;
}
@Nullable private CodeContext
replaceCodeContext(@Nullable CodeContext newCodeContext) {
CodeContext oldCodeContext = this.codeContext;
this.codeContext = newCodeContext;
return oldCodeContext;
}
private void
addLineNumberOffset(Locatable locatable) {
this.getCodeContext().addLineNumberOffset(locatable.getLocation().getLineNumber());
}
private void
write(int v) { this.getCodeContext().write((byte) v); }
private void
writeByte(int v) {
if (v > Byte.MAX_VALUE - Byte.MIN_VALUE) {
throw new InternalCompilerException("Byte value out of legal range");
}
this.getCodeContext().write((byte) v);
}
private void
writeShort(int v) {
if (v < Short.MIN_VALUE || v > Short.MAX_VALUE) {
throw new InternalCompilerException("Short value out of legal range");
}
this.getCodeContext().write((byte) (v >> 8), (byte) v);
}
private void
writeUnsignedShort(int v) {
if (v < 0 || v > 65535) {
throw new InternalCompilerException("Unsigned short value out of legal range");
}
this.getCodeContext().write((byte) (v >> 8), (byte) v);
}
private void
writeInt(int v) {
this.getCodeContext().write((byte) (v >> 24), (byte) (v >> 16), (byte) (v >> 8), (byte) v);
}
private void
writeLdc(short constantPoolIndex) {
if (constantPoolIndex >= 0 && constantPoolIndex <= 255) {
this.write(Opcode.LDC);
this.write(constantPoolIndex);
} else {
this.write(Opcode.LDC_W);
this.writeShort(constantPoolIndex);
}
}
private void
writeLdc2(short constantPoolIndex) {
this.write(Opcode.LDC2_W);
this.getCodeContext().writeShort(constantPoolIndex);
}
/**
* Invokes the iMethod ; assumes that {@code this} (unless iMethod is static) and the correct
* number and types of arguments are on the operand stack.
*/
private void
invokeMethod(Locatable locatable, IMethod iMethod) throws CompileException {
if (this.getTargetVersion() < 8 && iMethod.isStatic() && iMethod.getDeclaringIClass().isInterface()) {
this.compileError(
"Invocation of static interface methods only available for target version 8+",
locatable.getLocation()
);
}
int opcode = (
iMethod.isStatic() ? Opcode.INVOKESTATIC :
iMethod.getDeclaringIClass().isInterface() ? Opcode.INVOKEINTERFACE :
Opcode.INVOKEVIRTUAL
);
boolean useInterfaceMethodref = iMethod.getDeclaringIClass().isInterface();
this.invoke(
locatable, // locatable
opcode, // opcode
iMethod.getDeclaringIClass(), // declaringIClass
iMethod.getName(), // methodName
iMethod.getDescriptor(), // methodMd
useInterfaceMethodref // useInterfaceMethodref
);
}
/**
* Invokes the iConstructor ; assumes that {@code this} and the correct number and types of arguments are
* on the operand stack.
*/
private void
invokeConstructor(Locatable locatable, IConstructor iConstructor) throws CompileException {
this.invoke(
locatable, // locatable
Opcode.INVOKESPECIAL, // opcode
iConstructor.getDeclaringIClass(), // declaringIClass
"", // methodName
iConstructor.getDescriptor(), // methodMd
false // useInterfaceMethodref
);
}
private void
writeOffset(CodeContext.Offset src, final CodeContext.Offset dst) {
this.getCodeContext().writeOffset(src, dst);
}
// Wrappers for "ClassFile.addConstant...Info()". Saves us some coding overhead.
private short
addConstantStringInfo(String value) {
return this.getCodeContext().getClassFile().addConstantStringInfo(value);
}
private short
addConstantIntegerInfo(int value) {
return this.getCodeContext().getClassFile().addConstantIntegerInfo(value);
}
private short
addConstantLongInfo(long value) {
return this.getCodeContext().getClassFile().addConstantLongInfo(value);
}
private short
addConstantFloatInfo(float value) {
return this.getCodeContext().getClassFile().addConstantFloatInfo(value);
}
private short
addConstantDoubleInfo(double value) {
return this.getCodeContext().getClassFile().addConstantDoubleInfo(value);
}
private short
addConstantClassInfo(IClass iClass) {
return this.getCodeContext().getClassFile().addConstantClassInfo(iClass.getDescriptor());
}
private short
addConstantFieldrefInfo(IClass iClass, String fieldName, IClass fieldType) {
return this.getCodeContext().getClassFile().addConstantFieldrefInfo(iClass.getDescriptor(), fieldName, fieldType.getDescriptor());
}
private short
addConstantMethodrefInfo(IClass iClass, String methodName, String methodFd) {
return this.getCodeContext().getClassFile().addConstantMethodrefInfo(iClass.getDescriptor(), methodName, methodFd);
}
private short
addConstantInterfaceMethodrefInfo(IClass iClass, String methodName, String methodFd) {
return this.getCodeContext().getClassFile().addConstantInterfaceMethodrefInfo(iClass.getDescriptor(), methodName, methodFd);
}
/* UNUSED
private void writeConstantIntegerInfo(int value) {
this.getCodeContext().writeShort(-1, this.addConstantIntegerInfo(value));
}
*/
private void
writeConstantClassInfo(IClass iClass) {
this.writeShort(this.addConstantClassInfo(iClass));
}
private void
writeConstantFieldrefInfo(IClass iClass, String fieldName, IClass fieldType) {
this.writeShort(this.addConstantFieldrefInfo(iClass, fieldName, fieldType));
}
private void
writeConstantMethodrefInfo(IClass iClass, String methodName, MethodDescriptor methodMd) {
this.writeShort(this.addConstantMethodrefInfo(iClass, methodName, methodMd.toString()));
}
private void
writeConstantInterfaceMethodrefInfo(IClass iClass, String methodName, MethodDescriptor methodMd) {
this.writeShort(this.addConstantInterfaceMethodrefInfo(iClass, methodName, methodMd.toString()));
}
/* UNUSED
private void writeConstantStringInfo(String value) {
this.getCodeContext().writeShort(-1, this.addConstantStringInfo(value));
}
private void
writeConstantLongInfo(long value) {
this.getCodeContext().writeShort(this.addConstantLongInfo(value));
}
private void writeConstantFloatInfo(float value) {
this.getCodeContext().writeShort(-1, this.addConstantFloatInfo(value));
}
private void
writeConstantDoubleInfo(double value) {
this.getCodeContext().writeShort(this.addConstantDoubleInfo(value));
}
*/
private CodeContext.Offset
getWhereToBreak(BreakableStatement bs) {
Offset wtb = bs.whereToBreak;
if (wtb != null) {
StackMap saved = this.codeContext.currentInserter().getStackMap();
wtb.setStackMap();
this.codeContext.currentInserter().setStackMap(saved);
return wtb;
}
wtb = this.getCodeContext().new BasicBlock();
wtb.setStackMap(this.codeContext.currentInserter().getStackMap());
return (bs.whereToBreak = wtb);
}
private TypeBodyDeclaration
getDeclaringTypeBodyDeclaration(QualifiedThisReference qtr) throws CompileException {
if (qtr.declaringTypeBodyDeclaration != null) return qtr.declaringTypeBodyDeclaration;
// Compile error if in static function context.
Scope s;
for (
s = qtr.getEnclosingScope();
!(s instanceof TypeBodyDeclaration);
s = s.getEnclosingScope()
);
TypeBodyDeclaration result = (TypeBodyDeclaration) s;
if (UnitCompiler.isStaticContext(result)) {
this.compileError("No current instance available in static method", qtr.getLocation());
}
// Determine declaring type.
qtr.declaringClass = (AbstractClassDeclaration) result.getDeclaringType();
return (qtr.declaringTypeBodyDeclaration = result);
}
private AbstractClassDeclaration
getDeclaringClass(QualifiedThisReference qtr) throws CompileException {
if (qtr.declaringClass != null) return qtr.declaringClass;
this.getDeclaringTypeBodyDeclaration(qtr);
assert qtr.declaringClass != null;
return qtr.declaringClass;
}
private void
referenceThis(Locatable locatable, IClass currentIClass) {
this.load(
locatable,
currentIClass, // localVariableType
0 // localVariableIndex
);
}
/**
* Expects dimExprCount values of type {@code int} on the operand stack. Creates an array of
* dimExprCount {@code +} dims dimensions of componentType .
*
* @return The type of the created array
*/
private IClass
newArray(Locatable locatable, int dimExprCount, int dims, IType componentType) {
IClass rawComponentType = UnitCompiler.rawTypeOf(componentType);
if (dimExprCount == 1 && dims == 0 && rawComponentType.isPrimitive()) {
// "new []"
this.newarray(locatable, componentType);
} else
if (dimExprCount == 1) {
// "new []"
// "new [] []{dims}"
this.anewarray(locatable, this.iClassLoader.getArrayIClass(rawComponentType, dims));
} else
{
// "new []{dims}"
// "new []{dimexprCount}"
// "new []{dimexprCount} []{dims}"
this.multianewarray(locatable, dimExprCount, dims, componentType);
}
return this.iClassLoader.getArrayIClass(rawComponentType, dimExprCount + dims);
}
/**
* Short-hand implementation of {@link IClass.IField} that implements a non-constant, non-static,
* package-accessible field.
*/
public static
class SimpleIField extends IClass.IField {
private final String name;
private final IClass type;
public
SimpleIField(IClass declaringIClass, String name, IClass type) {
declaringIClass.super();
this.name = name;
this.type = type;
}
@Override public Object getConstantValue() { return UnitCompiler.NOT_CONSTANT; }
@Override public String getName() { return this.name; }
@Override public IClass getType() { return this.type; }
@Override public boolean isStatic() { return false; }
@Override public Access getAccess() { return Access.DEFAULT; }
@Override public IAnnotation[] getAnnotations() { return new IAnnotation[0]; }
}
private static String
last(String[] sa) {
if (sa.length == 0) throw new IllegalArgumentException("SNO: Empty string array");
return sa[sa.length - 1];
}
private static String[]
allButLast(String[] sa) {
if (sa.length == 0) throw new IllegalArgumentException("SNO: Empty string array");
String[] tmp = new String[sa.length - 1];
System.arraycopy(sa, 0, tmp, 0, tmp.length);
return tmp;
}
private static String[]
concat(String[] sa, String s) {
String[] tmp = new String[sa.length + 1];
System.arraycopy(sa, 0, tmp, 0, sa.length);
tmp[sa.length] = s;
return tmp;
}
private static CompileException
compileException(Locatable locatable, String message) {
return new CompileException(message, locatable.getLocation());
}
/**
* Decodes any escape sequences like {@code \n}, or {@code \377}, but not {@code \uxxxx}.
*
* @return s , with all escape sequences replaced with their literal values
* @throws CompileException s contains an invalid escape sequence
* @throws IndexOutOfBoundsException s ends with a backslash
* @see JLS8, section 3.10.6, "Escape Sequences for Character and String Literals"
*/
private static String
unescape(String s, @Nullable Location location) throws CompileException {
// Find the first backslash.
int i = s.indexOf('\\');
if (i == -1) {
// Subject string contains no backslash and thus no escape sequences; so return the original string.
return s;
}
StringBuilder sb = new StringBuilder().append(s, 0, i);
while (i < s.length()) {
char c = s.charAt(i++);
if (c != '\\') {
sb.append(c);
continue;
}
c = s.charAt(i++);
{
int idx = "btnfr\"'\\".indexOf(c);
if (idx != -1) {
sb.append("\b\t\n\f\r\"'\\".charAt(idx));
continue;
}
}
// Must be an an OctalEscape (JLS8, section 3.10.6).
int x = Character.digit(c, 8);
if (x == -1) throw new CompileException("Invalid escape sequence \"\\" + c + "\"", location);
if (i < s.length()) {
c = s.charAt(i);
int secondDigit = Character.digit(c, 8);
if (secondDigit != -1) {
x = 8 * x + secondDigit;
i++;
if (i < s.length() && x <= 037) {
c = s.charAt(i);
int thirdDigit = Character.digit(c, 8);
if (thirdDigit != -1) {
x = 8 * x + thirdDigit;
i++;
}
}
}
}
sb.append((char) x);
}
return sb.toString();
}
private short
accessFlags(Modifier[] modifiers) throws CompileException {
int result = 0;
for (Modifier m : modifiers) {
if (m instanceof AccessModifier) {
String kw = ((AccessModifier) m).keyword;
if ("public".equals(kw)) {
result |= Mod.PUBLIC;
} else
if ("private".equals(kw)) {
result |= Mod.PRIVATE;
} else
if ("protected".equals(kw)) {
result |= Mod.PROTECTED;
} else
if ("static".equals(kw)) {
result |= Mod.STATIC;
} else
if ("final".equals(kw)) {
result |= Mod.FINAL;
} else
if ("synchronized".equals(kw)) {
result |= Mod.SYNCHRONIZED;
} else
if ("volatile".equals(kw)) {
result |= Mod.VOLATILE;
} else
if ("transient".equals(kw)) {
result |= Mod.TRANSIENT;
} else
if ("native".equals(kw)) {
result |= Mod.NATIVE;
} else
if ("abstract".equals(kw)) {
result |= Mod.ABSTRACT;
} else
if ("strictfp".equals(kw)) {
result |= Mod.STRICTFP;
} else
if ("default".equals(kw)) {
;
} else {
this.compileError("Invalid modifier \"" + kw + "\"");
}
}
}
return (short) result;
}
private static Modifier[]
accessModifiers(Location location, String... keywords) {
Modifier[] result = new Modifier[keywords.length];
for (int i = 0; i < keywords.length; i++) {
result[i] = new AccessModifier(keywords[i], location);
}
return result;
}
private static short
changeAccessibility(short accessFlags, short newAccessibility) {
return (short) ((accessFlags & ~(Mod.PUBLIC | Mod.PROTECTED | Mod.PRIVATE)) | newAccessibility);
}
/**
* @param lvIndex (two slots for LONG and DOUBLE local variables)
*/
private VerificationTypeInfo
getLocalVariableTypeInfo(short lvIndex) {
StackMap cism = this.getCodeContext().currentInserter().getStackMap();
assert cism != null;
int nextLvIndex = 0;
for (VerificationTypeInfo vti : cism.locals()) {
if (nextLvIndex == lvIndex) return vti;
nextLvIndex += vti.category();
}
throw new InternalCompilerException("Invalid local variable index " + lvIndex);
}
private void
updateLocalVariableInCurrentStackMap(short lvIndex, VerificationTypeInfo vti) {
final Inserter ci = this.getCodeContext().currentInserter();
VerificationTypeInfo[] locals = ci.getStackMap().locals();
int nextLvIndex = 0;
for (int i = 0; i < locals.length; i++) {
VerificationTypeInfo vti2 = locals[i];
if (nextLvIndex == lvIndex) {
if (vti.equals(vti2)) { // Replace VTI with equal VTI?
return;
}
if (vti2.category() == vti.category()) { // Replace VTI with VTI of same category?
locals[i] = vti;
} else
if (vti2.category() == 1 && vti.category() == 2) { // Replace two category 1 VTIs with one category 2 VTI?
assert locals[i + 1].category() == 1;
locals[i] = vti;
System.arraycopy(locals, i + 2, locals, i + 1, locals.length - i - 2);
locals = (VerificationTypeInfo[]) Arrays.copyOf(locals, locals.length - 1);
} else
if (vti2.category() == 2 && vti.category() == 1) { // Replace one category 2 VTI with two category 1 VTIs?
locals = (VerificationTypeInfo[]) Arrays.copyOf(locals, locals.length + 1);
System.arraycopy(locals, i + 1, locals, i + 2, locals.length - i - 2);
locals[i] = vti;
locals[i + 1] = StackMapTableAttribute.TOP_VARIABLE_INFO;
} else
{
throw new AssertionError(vti2.category() + " vs. " + vti.category());
}
ci.setStackMap(new StackMap(locals, ci.getStackMap().operands()));
return;
}
nextLvIndex += vti2.category();
}
assert nextLvIndex <= lvIndex;
while (nextLvIndex < lvIndex) {
ci.setStackMap(ci.getStackMap().pushLocal(StackMapTableAttribute.TOP_VARIABLE_INFO));
nextLvIndex++;
}
ci.setStackMap(ci.getStackMap().pushLocal(vti));
}
// Used to write byte code while compiling one constructor/method.
@Nullable private CodeContext codeContext;
// Used for elaborate compile error handling.
@Nullable private ErrorHandler compileErrorHandler;
private int compileErrorCount;
// Used for elaborate warning handling.
@Nullable private WarningHandler warningHandler;
private final AbstractCompilationUnit abstractCompilationUnit;
private final IClassLoader iClassLoader;
/**
* Non-{@code null} while {@link #compileUnit(boolean, boolean, boolean, ClassFileConsumer)} is executing.
*/
@Nullable private ClassFileConsumer storesClassFiles;
private boolean debugSource;
private boolean debugLines;
private boolean debugVars;
}