
com.greenpepper.shaded.org.codehaus.janino.UnitCompiler Maven / Gradle / Ivy
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2013, Arno Unkrig
* All rights reserved.
*
* 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. The name of the author may not be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.greenpepper.shaded.org.codehaus.janino;
import java.io.DataOutputStream;
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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import com.greenpepper.shaded.org.codehaus.commons.compiler.CompileException;
import com.greenpepper.shaded.org.codehaus.commons.compiler.ErrorHandler;
import com.greenpepper.shaded.org.codehaus.commons.compiler.Location;
import com.greenpepper.shaded.org.codehaus.commons.compiler.UncheckedCompileException;
import com.greenpepper.shaded.org.codehaus.commons.compiler.WarningHandler;
import com.greenpepper.shaded.org.codehaus.janino.CodeContext.Inserter;
import com.greenpepper.shaded.org.codehaus.janino.CodeContext.Offset;
import com.greenpepper.shaded.org.codehaus.janino.IClass.IConstructor;
import com.greenpepper.shaded.org.codehaus.janino.IClass.IField;
import com.greenpepper.shaded.org.codehaus.janino.IClass.IInvocable;
import com.greenpepper.shaded.org.codehaus.janino.IClass.IMethod;
import com.greenpepper.shaded.org.codehaus.janino.Java.AbstractTypeDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.AlternateConstructorInvocation;
import com.greenpepper.shaded.org.codehaus.janino.Java.AmbiguousName;
import com.greenpepper.shaded.org.codehaus.janino.Java.Annotation;
import com.greenpepper.shaded.org.codehaus.janino.Java.AnonymousClassDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.ArrayAccessExpression;
import com.greenpepper.shaded.org.codehaus.janino.Java.ArrayInitializer;
import com.greenpepper.shaded.org.codehaus.janino.Java.ArrayInitializerOrRvalue;
import com.greenpepper.shaded.org.codehaus.janino.Java.ArrayLength;
import com.greenpepper.shaded.org.codehaus.janino.Java.ArrayType;
import com.greenpepper.shaded.org.codehaus.janino.Java.AssertStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.Assignment;
import com.greenpepper.shaded.org.codehaus.janino.Java.Atom;
import com.greenpepper.shaded.org.codehaus.janino.Java.BasicType;
import com.greenpepper.shaded.org.codehaus.janino.Java.BinaryOperation;
import com.greenpepper.shaded.org.codehaus.janino.Java.Block;
import com.greenpepper.shaded.org.codehaus.janino.Java.BlockStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.BooleanLiteral;
import com.greenpepper.shaded.org.codehaus.janino.Java.BooleanRvalue;
import com.greenpepper.shaded.org.codehaus.janino.Java.BreakStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.BreakableStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.Cast;
import com.greenpepper.shaded.org.codehaus.janino.Java.CatchClause;
import com.greenpepper.shaded.org.codehaus.janino.Java.CharacterLiteral;
import com.greenpepper.shaded.org.codehaus.janino.Java.ClassDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.ClassLiteral;
import com.greenpepper.shaded.org.codehaus.janino.Java.CompilationUnit;
import com.greenpepper.shaded.org.codehaus.janino.Java.CompilationUnit.ImportDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.CompilationUnit.SingleStaticImportDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.CompilationUnit.SingleTypeImportDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.CompilationUnit.StaticImportOnDemandDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.CompilationUnit.TypeImportOnDemandDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.ConditionalExpression;
import com.greenpepper.shaded.org.codehaus.janino.Java.ConstructorDeclarator;
import com.greenpepper.shaded.org.codehaus.janino.Java.ConstructorInvocation;
import com.greenpepper.shaded.org.codehaus.janino.Java.ContinuableStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.ContinueStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.Crement;
import com.greenpepper.shaded.org.codehaus.janino.Java.DoStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.DocCommentable;
import com.greenpepper.shaded.org.codehaus.janino.Java.EmptyStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.EnclosingScopeOfTypeDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.ExpressionStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.FieldAccess;
import com.greenpepper.shaded.org.codehaus.janino.Java.FieldAccessExpression;
import com.greenpepper.shaded.org.codehaus.janino.Java.FieldDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.FloatingPointLiteral;
import com.greenpepper.shaded.org.codehaus.janino.Java.ForEachStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.ForStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.FunctionDeclarator;
import com.greenpepper.shaded.org.codehaus.janino.Java.FunctionDeclarator.FormalParameter;
import com.greenpepper.shaded.org.codehaus.janino.Java.FunctionDeclarator.FormalParameters;
import com.greenpepper.shaded.org.codehaus.janino.Java.IfStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.Initializer;
import com.greenpepper.shaded.org.codehaus.janino.Java.InnerClassDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.Instanceof;
import com.greenpepper.shaded.org.codehaus.janino.Java.IntegerLiteral;
import com.greenpepper.shaded.org.codehaus.janino.Java.InterfaceDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.Invocation;
import com.greenpepper.shaded.org.codehaus.janino.Java.LabeledStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.Literal;
import com.greenpepper.shaded.org.codehaus.janino.Java.LocalClassDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.LocalClassDeclarationStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.LocalVariable;
import com.greenpepper.shaded.org.codehaus.janino.Java.LocalVariableAccess;
import com.greenpepper.shaded.org.codehaus.janino.Java.LocalVariableDeclarationStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.LocalVariableSlot;
import com.greenpepper.shaded.org.codehaus.janino.Java.Locatable;
import com.greenpepper.shaded.org.codehaus.janino.Java.Located;
import com.greenpepper.shaded.org.codehaus.janino.Java.Lvalue;
import com.greenpepper.shaded.org.codehaus.janino.Java.MemberClassDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.MemberInterfaceDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.MemberTypeDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.MethodDeclarator;
import com.greenpepper.shaded.org.codehaus.janino.Java.MethodInvocation;
import com.greenpepper.shaded.org.codehaus.janino.Java.Modifiers;
import com.greenpepper.shaded.org.codehaus.janino.Java.NamedClassDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.NamedTypeDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.NewAnonymousClassInstance;
import com.greenpepper.shaded.org.codehaus.janino.Java.NewArray;
import com.greenpepper.shaded.org.codehaus.janino.Java.NewClassInstance;
import com.greenpepper.shaded.org.codehaus.janino.Java.NewInitializedArray;
import com.greenpepper.shaded.org.codehaus.janino.Java.NullLiteral;
import com.greenpepper.shaded.org.codehaus.janino.Java.Package;
import com.greenpepper.shaded.org.codehaus.janino.Java.PackageMemberClassDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.PackageMemberInterfaceDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.PackageMemberTypeDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.Padder;
import com.greenpepper.shaded.org.codehaus.janino.Java.ParameterAccess;
import com.greenpepper.shaded.org.codehaus.janino.Java.ParenthesizedExpression;
import com.greenpepper.shaded.org.codehaus.janino.Java.QualifiedThisReference;
import com.greenpepper.shaded.org.codehaus.janino.Java.ReferenceType;
import com.greenpepper.shaded.org.codehaus.janino.Java.ReturnStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.Rvalue;
import com.greenpepper.shaded.org.codehaus.janino.Java.RvalueMemberType;
import com.greenpepper.shaded.org.codehaus.janino.Java.Scope;
import com.greenpepper.shaded.org.codehaus.janino.Java.SimpleConstant;
import com.greenpepper.shaded.org.codehaus.janino.Java.SimpleType;
import com.greenpepper.shaded.org.codehaus.janino.Java.Statement;
import com.greenpepper.shaded.org.codehaus.janino.Java.StringLiteral;
import com.greenpepper.shaded.org.codehaus.janino.Java.SuperConstructorInvocation;
import com.greenpepper.shaded.org.codehaus.janino.Java.SuperclassFieldAccessExpression;
import com.greenpepper.shaded.org.codehaus.janino.Java.SuperclassMethodInvocation;
import com.greenpepper.shaded.org.codehaus.janino.Java.SwitchStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.SynchronizedStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.ThisReference;
import com.greenpepper.shaded.org.codehaus.janino.Java.ThrowStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.TryStatement;
import com.greenpepper.shaded.org.codehaus.janino.Java.Type;
import com.greenpepper.shaded.org.codehaus.janino.Java.TypeBodyDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.TypeDeclaration;
import com.greenpepper.shaded.org.codehaus.janino.Java.TypeParameter;
import com.greenpepper.shaded.org.codehaus.janino.Java.UnaryOperation;
import com.greenpepper.shaded.org.codehaus.janino.Java.VariableDeclarator;
import com.greenpepper.shaded.org.codehaus.janino.Java.WhileStatement;
import com.greenpepper.shaded.org.codehaus.janino.Visitor.AtomVisitor;
import com.greenpepper.shaded.org.codehaus.janino.Visitor.BlockStatementVisitor;
import com.greenpepper.shaded.org.codehaus.janino.Visitor.ElementValueVisitor;
import com.greenpepper.shaded.org.codehaus.janino.Visitor.ImportVisitor;
import com.greenpepper.shaded.org.codehaus.janino.Visitor.LvalueVisitor;
import com.greenpepper.shaded.org.codehaus.janino.Visitor.RvalueVisitor;
import com.greenpepper.shaded.org.codehaus.janino.Visitor.TypeDeclarationVisitor;
import com.greenpepper.shaded.org.codehaus.janino.util.ClassFile;
/**
* This class actually implements the Java™ compiler. It is associated with exactly one compilation unit which it
* compiles.
*/
@SuppressWarnings({ "rawtypes", "unchecked" }) public
class UnitCompiler {
private static final boolean DEBUG = false;
/**
* 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 {@code 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 {@code 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;
public
UnitCompiler(CompilationUnit compilationUnit, IClassLoader iClassLoader) {
this.compilationUnit = compilationUnit;
this.iClassLoader = iClassLoader;
}
/** @return The {@link CompilationUnit} that this {@link UnitCompiler} compiles */
public CompilationUnit
getCompilationUnit() { return this.compilationUnit; }
private void
import2(SingleStaticImportDeclaration ssid) throws CompileException {
String name = UnitCompiler.last(ssid.identifiers);
List
importedObjects = (List) this.singleStaticImports.get(name);
if (importedObjects == null) {
importedObjects = new ArrayList();
this.singleStaticImports.put(name, importedObjects);
}
// Type?
{
IClass iClass = this.findTypeByFullyQualifiedName(ssid.getLocation(), ssid.identifiers);
if (iClass != null) {
importedObjects.add(iClass);
return;
}
}
String[] typeName = UnitCompiler.allButLast(ssid.identifiers);
IClass iClass = this.findTypeByFullyQualifiedName(ssid.getLocation(), typeName);
if (iClass == null) {
this.compileError("Could not load \"" + Java.join(typeName, ".") + "\"", ssid.getLocation());
return;
}
// Static field?
IField iField = iClass.getDeclaredIField(name);
if (iField != null) {
if (!iField.isStatic()) {
this.compileError(
"Field \"" + name + "\" of \"" + Java.join(typeName, ".") + "\" must be static",
ssid.getLocation()
);
}
importedObjects.add(iField);
return;
}
// Static method?
IMethod[] ms = iClass.getDeclaredIMethods(name);
if (ms.length > 0) {
importedObjects.addAll(Arrays.asList(ms));
return;
}
// Give up.
this.compileError(
"\"" + Java.join(typeName, ".") + "\" has no static member \"" + name + "\"",
ssid.getLocation()
);
}
private void
import2(StaticImportOnDemandDeclaration siodd) throws CompileException {
IClass iClass = this.findTypeByFullyQualifiedName(siodd.getLocation(), siodd.identifiers);
if (iClass == null) {
this.compileError("Could not load \"" + Java.join(siodd.identifiers, ".") + "\"", siodd.getLocation());
return;
}
this.staticImportsOnDemand.add(iClass);
}
/**
* Generates an array of {@link ClassFile} objects which represent the classes and interfaces declared in the
* compilation unit.
*/
public ClassFile[]
compileUnit(boolean debugSource, boolean debugLines, boolean debugVars) throws CompileException {
this.debugSource = debugSource;
this.debugLines = debugLines;
this.debugVars = debugVars;
// Compile static import declarations.
// Notice: The single-type and on-demand imports are needed BEFORE the unit is compiled, thus they are
// processed in 'getSingleTypeImport()' and 'importOnDemand()'.
for (ImportDeclaration id : this.compilationUnit.importDeclarations) {
try {
id.accept(new ImportVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitSingleTypeImportDeclaration(SingleTypeImportDeclaration stid) {}
@Override public void visitTypeImportOnDemandDeclaration(TypeImportOnDemandDeclaration tiodd) {}
@Override public void visitSingleStaticImportDeclaration(SingleStaticImportDeclaration ssid) { try { UnitCompiler.this.import2(ssid); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitStaticImportOnDemandDeclaration(StaticImportOnDemandDeclaration siodd) { try { UnitCompiler.this.import2(siodd); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
});
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
this.generatedClassFiles = new ArrayList();
for (PackageMemberTypeDeclaration pmtd : this.compilationUnit.packageMemberTypeDeclarations) {
this.compile(pmtd);
}
if (this.compileErrorCount > 0) {
throw new CompileException((
this.compileErrorCount
+ " error(s) while compiling unit \""
+ this.compilationUnit.optionalFileName
+ "\""
), null);
}
List l = this.generatedClassFiles;
return (ClassFile[]) l.toArray(new ClassFile[l.size()]);
}
// ------------ TypeDeclaration.compile() -------------
private void
compile(TypeDeclaration td) throws CompileException {
TypeDeclarationVisitor tdv = new TypeDeclarationVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitAnonymousClassDeclaration(AnonymousClassDeclaration acd) { try { UnitCompiler.this.compile2(acd); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLocalClassDeclaration(LocalClassDeclaration lcd) { try { UnitCompiler.this.compile2(lcd); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitPackageMemberClassDeclaration(PackageMemberClassDeclaration pmcd) { try { UnitCompiler.this.compile2((PackageMemberTypeDeclaration) pmcd); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitMemberInterfaceDeclaration(MemberInterfaceDeclaration mid) { try { UnitCompiler.this.compile2(mid); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitPackageMemberInterfaceDeclaration(PackageMemberInterfaceDeclaration pmid) { try { UnitCompiler.this.compile2((PackageMemberTypeDeclaration) pmid); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitMemberClassDeclaration(MemberClassDeclaration mcd) { try { UnitCompiler.this.compile2(mcd); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try {
td.accept(tdv);
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
private void
compile2(PackageMemberTypeDeclaration pmtd) throws CompileException {
CompilationUnit declaringCompilationUnit = pmtd.getDeclaringCompilationUnit();
// Check for conflict with single-type-import (7.6).
{
String[] ss = this.getSingleTypeImport(pmtd.getName(), pmtd.getLocation());
if (ss != null) {
this.compileError((
"Package member type declaration \""
+ pmtd.getName()
+ "\" conflicts with single-type-import \""
+ Java.join(ss, ".")
+ "\""
), pmtd.getLocation());
}
}
// Check for redefinition within compilation unit (7.6).
{
PackageMemberTypeDeclaration otherPmtd = declaringCompilationUnit.getPackageMemberTypeDeclaration(
pmtd.getName()
);
if (otherPmtd != pmtd) {
this.compileError((
"Redeclaration of type \""
+ pmtd.getName()
+ "\", previously declared in "
+ otherPmtd.getLocation()
), pmtd.getLocation());
}
}
if (pmtd instanceof NamedClassDeclaration) {
this.compile2((NamedClassDeclaration) pmtd);
} else
if (pmtd instanceof InterfaceDeclaration) {
this.compile2((InterfaceDeclaration) pmtd);
} else
{
throw new JaninoRuntimeException("PMTD of unexpected type " + pmtd.getClass().getName());
}
}
private void
compile2(ClassDeclaration cd) throws CompileException {
IClass iClass = this.resolve(cd);
// Check that all methods of the non-abstract class are implemented.
if (!Mod.isAbstract(cd.getModifierFlags())) {
IMethod[] ms = iClass.getIMethods();
for (IMethod base : ms) {
if (base.isAbstract()) {
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()
);
}
}
}
}
// Create "ClassFile" object.
ClassFile cf = new ClassFile(
(short) (cd.getModifierFlags() | Mod.SUPER), // accessFlags
iClass.getDescriptor(), // thisClassFD
iClass.getSuperclass().getDescriptor(), // superclassFD
IClass.getDescriptors(iClass.getInterfaces()) // interfaceFDs
);
// TODO: Add annotations with retention != SOURCE.
// for (Annotation a : cd.getAnnotations()) {
// assert false : "Class '" + iClass + "' has annotation '" + a + "'";
// }
// Add InnerClasses attribute entry for this class declaration.
if (cd.getEnclosingScope() instanceof CompilationUnit) {
;
} else
if (cd.getEnclosingScope() instanceof Block) {
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
cd.getModifierFlags() // innerClassAccessFlags
));
} else
if (cd.getEnclosingScope() instanceof TypeDeclaration) {
short innerClassInfoIndex = cf.addConstantClassInfo(iClass.getDescriptor());
short outerClassInfoIndex = cf.addConstantClassInfo(
this.resolve(((TypeDeclaration) cd.getEnclosingScope())).getDescriptor()
);
short innerNameIndex = cf.addConstantUtf8Info(((MemberTypeDeclaration) cd).getName());
assert cd.getAnnotations().length == 0 : "NYI";
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
outerClassInfoIndex, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
cd.getModifierFlags() // innerClassAccessFlags
));
}
// Set "SourceFile" attribute.
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 "Deprecated" attribute (JVMS 4.7.10).
if (cd instanceof DocCommentable) {
if (((DocCommentable) cd).hasDeprecatedDocTag()) cf.addDeprecatedAttribute();
}
// Optional: Generate and compile class initialization method.
{
List statements = new ArrayList();
for (BlockStatement vdoi : cd.variableDeclaratorsAndInitializers) {
if (((TypeBodyDeclaration) vdoi).isStatic()) statements.add(vdoi);
}
this.maybeCreateInitMethod(cd, cf, statements);
}
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();
{
int syntheticFieldCount = cd.syntheticFields.size();
ConstructorDeclarator[] ctords = cd.getConstructors();
for (ConstructorDeclarator ctord : ctords) {
this.compile(ctord, cf);
if (syntheticFieldCount != cd.syntheticFields.size()) {
throw new JaninoRuntimeException(
"SNO: Compilation of constructor \""
+ ctord
+ "\" ("
+ ctord.getLocation()
+ ") added synthetic fields!?"
);
}
}
}
// 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.
IMethod[] ms = iClass.getIMethods();
for (IMethod base : ms) {
if (!base.isStatic()) {
IMethod override = iClass.findIMethod(base.getName(), base.getParameterTypes());
// If we overrode the method but with a DIFFERENT return type.
if (
override != null
&& !base.getReturnType().equals(override.getReturnType())
) {
this.compileBridgeMethod(cf, base, override);
}
}
}
}
// Class and instance variables.
for (BlockStatement vdoi : cd.variableDeclaratorsAndInitializers) {
if (vdoi instanceof FieldDeclaration) this.addFields((FieldDeclaration) vdoi, cf);
}
// Synthetic fields.
for (IField f : cd.syntheticFields.values()) {
cf.addFieldInfo(
new Modifiers(Mod.PACKAGE), // modifiers
f.getName(), // fieldName
f.getType().getDescriptor(), // fieldTypeFD
null // optionalConstantValue
);
}
// Add the generated class file to a thread-local store.
this.generatedClassFiles.add(cf);
}
/** Creates {@link ClassFile.FieldInfo}s for all fields declared by the given {@link FieldDeclaration}. */
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 = UnitCompiler.NOT_CONSTANT;
if (Mod.isFinal(fd.modifiers.flags) && vd.optionalInitializer instanceof Rvalue) {
ocv = this.getConstantValue((Rvalue) vd.optionalInitializer);
}
ClassFile.FieldInfo fi;
if (Mod.isPrivateAccess(fd.modifiers.flags)) {
// 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
assert fd.modifiers.annotations.length == 0 : "NYI";
fi = cf.addFieldInfo(
fd.modifiers.changeAccess(Mod.PACKAGE), // modifiers
vd.name, // fieldName
this.getType(type).getDescriptor(), // fieldTypeFD
ocv == UnitCompiler.NOT_CONSTANT ? null : ocv // optionalConstantValue
);
} else
{
assert fd.modifiers.annotations.length == 0 : "NYI";
fi = cf.addFieldInfo(
fd.modifiers, // modifiers
vd.name, // fieldName
this.getType(type).getDescriptor(), // fieldTypeFD
ocv == UnitCompiler.NOT_CONSTANT ? null : ocv // optionalConstantValue
);
}
// 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 {
this.compile2((InnerClassDeclaration) acd);
}
private void
compile2(LocalClassDeclaration lcd) throws CompileException {
this.compile2((InnerClassDeclaration) lcd);
}
private void
compile2(InnerClassDeclaration icd) throws CompileException {
// Define a synthetic "this$..." field if there is an enclosing instance.
{
List ocs = UnitCompiler.getOuterClasses(icd);
final int nesting = ocs.size();
if (nesting >= 2) {
icd.defineSyntheticField(new SimpleIField(
this.resolve(icd),
"this$" + (nesting - 2),
this.resolve((TypeDeclaration) ocs.get(1))
));
}
}
// For classes that enclose surrounding scopes, trawl their field initializers looking for synthetic fields.
if (icd instanceof AnonymousClassDeclaration || icd instanceof LocalClassDeclaration) {
ClassDeclaration cd = (ClassDeclaration) icd;
// Compilation of field declarations can create synthetic variables, so we must not use an iterator.
List vdais = cd.variableDeclaratorsAndInitializers;
for (int i = 0; i < vdais.size(); i++) {
BlockStatement vdoi = (BlockStatement) vdais.get(i);
this.fakeCompile(vdoi);
}
}
this.compile2((ClassDeclaration) icd);
}
private void
compile2(final MemberClassDeclaration mcd) throws CompileException { this.compile2((InnerClassDeclaration) mcd); }
private void
compile2(InterfaceDeclaration id) throws CompileException {
final IClass iClass = this.resolve(id);
// Determine extended interfaces.
id.interfaces = new IClass[id.extendedTypes.length];
String[] interfaceDescriptors = new String[id.interfaces.length];
for (int i = 0; i < id.extendedTypes.length; ++i) {
id.interfaces[i] = this.getType(id.extendedTypes[i]);
interfaceDescriptors[i] = id.interfaces[i].getDescriptor();
}
// Create "ClassFile" object.
ClassFile cf = new ClassFile(
(short) (id.getModifierFlags() | Mod.SUPER | Mod.INTERFACE | Mod.ABSTRACT), // accessFlags
iClass.getDescriptor(), // thisClassFD
Descriptor.JAVA_LANG_OBJECT, // superclassFD
interfaceDescriptors // interfaceFDs
);
// TODO: Add annotations with retention != SOURCE.
// for (Annotation a : id.getAnnotations()) {
// assert false : "Interface '" + iClass + "' has annotation '" + a + "'";
// }
// 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.generatedClassFiles.add(cf);
}
/**
* Create class initialization method iff there is any initialization code.
*
* @param decl The type declaration
* @param cf The class file into which to put the method
* @param b The block for the method (possibly empty)
* @throws CompileException
*/
private void
maybeCreateInitMethod(
AbstractTypeDeclaration decl,
ClassFile cf,
List statements
) throws CompileException {
// Create interface initialization method iff there is any initialization code.
if (this.generatesCode2(statements)) {
MethodDeclarator md = new MethodDeclarator(
decl.getLocation(), // location
null, // optionalDocComment
new Modifiers((short) (Mod.STATIC | Mod.PUBLIC)), // modifiers
new BasicType( // type
decl.getLocation(),
BasicType.VOID
),
"", // name
new FormalParameters(), // formalParameters
new ReferenceType[0], // thrownExceptions
statements // optionalStatements
);
md.setDeclaringType(decl);
this.compile(md, cf);
}
}
/**
* Compile 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());
assert mtd.getAnnotations().length == 0;
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
outerClassInfoIndex, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
mtd.getModifierFlags() // innerClassAccessFlags
));
}
}
/**
* Compile all of the methods for this declaration
*
* NB: as a side effect this will fill in the synthetic field map
*
* @throws CompileException
*/
private void
compileDeclaredMethods(AbstractTypeDeclaration typeDeclaration, ClassFile cf) throws CompileException {
this.compileDeclaredMethods(typeDeclaration, cf, 0);
}
/**
* Compile methods for this declaration starting at {@code startPos}.
*
* @param startPos Starting parameter to fill in
* @throws CompileException
*/
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.ANNO_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 methodAnnotation) throws CompileException {
Annotation[] methodAnnotations = fd.modifiers.annotations;
for (Annotation ma : methodAnnotations) {
if (this.getType(ma.getType()) == methodAnnotation) 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 {@code method} overrides a method of {@code 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);
}
/** Compiles a bridge method which will add a method of the signature of base that delegates to override. */
private void
compileBridgeMethod(ClassFile cf, IMethod base, IMethod override) throws CompileException {
ClassFile.MethodInfo mi = cf.addMethodInfo(
new Modifiers((short) (Mod.PUBLIC | Mod.SYNTHETIC)),
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(), base.toString());
final CodeContext savedCodeContext = this.replaceCodeContext(codeContext);
// Allocate all our local variables.
codeContext.saveLocalVariables();
codeContext.allocateLocalVariable((short) 1, "this", override.getDeclaringIClass());
IClass[] paramTypes = override.getParameterTypes();
LocalVariableSlot[] locals = new LocalVariableSlot[paramTypes.length];
for (int i = 0; i < paramTypes.length; ++i) {
locals[i] = codeContext.allocateLocalVariable(
Descriptor.size(paramTypes[i].getDescriptor()),
"param" + i,
paramTypes[i]
);
}
this.writeOpcode(Located.NOWHERE, Opcode.ALOAD_0);
for (LocalVariableSlot l : locals) this.load(Located.NOWHERE, l.getType(), l.getSlotIndex());
this.invoke(Located.NOWHERE, override);
this.writeOpcode(Located.NOWHERE, Opcode.ARETURN);
this.replaceCodeContext(savedCodeContext);
codeContext.flowAnalysis(override.getName());
// Add the code context as a code attribute to the MethodInfo.
mi.addAttribute(new ClassFile.AttributeInfo(cf.addConstantUtf8Info("Code")) {
@Override protected void
storeBody(DataOutputStream dos) throws IOException {
codeContext.storeCodeAttributeBody(dos, (short) 0, (short) 0);
}
});
}
/** @return Whether this statement can complete normally (JLS7 14.1) */
private boolean
compile(BlockStatement bs) throws CompileException {
final boolean[] res = new boolean[1];
BlockStatementVisitor bsv = new BlockStatementVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitInitializer(Initializer i) { try { res[0] = UnitCompiler.this.compile2(i); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldDeclaration(FieldDeclaration fd) { try { res[0] = UnitCompiler.this.compile2(fd); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLabeledStatement(LabeledStatement ls) { try { res[0] = UnitCompiler.this.compile2(ls); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBlock(Block b) { try { res[0] = UnitCompiler.this.compile2(b); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitExpressionStatement(ExpressionStatement es) { try { res[0] = UnitCompiler.this.compile2(es); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitIfStatement(IfStatement is) { try { res[0] = UnitCompiler.this.compile2(is); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitForStatement(ForStatement fs) { try { res[0] = UnitCompiler.this.compile2(fs); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitForEachStatement(ForEachStatement fes) { try { res[0] = UnitCompiler.this.compile2(fes); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitWhileStatement(WhileStatement ws) { try { res[0] = UnitCompiler.this.compile2(ws); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitTryStatement(TryStatement ts) { try { res[0] = UnitCompiler.this.compile2(ts); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSwitchStatement(SwitchStatement ss) { try { res[0] = UnitCompiler.this.compile2(ss); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSynchronizedStatement(SynchronizedStatement ss) { try { res[0] = UnitCompiler.this.compile2(ss); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitDoStatement(DoStatement ds) { try { res[0] = UnitCompiler.this.compile2(ds); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { try { res[0] = UnitCompiler.this.compile2(lvds); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitReturnStatement(ReturnStatement rs) { try { res[0] = UnitCompiler.this.compile2(rs); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitThrowStatement(ThrowStatement ts) { try { res[0] = UnitCompiler.this.compile2(ts); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBreakStatement(BreakStatement bs) { try { res[0] = UnitCompiler.this.compile2(bs); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitContinueStatement(ContinueStatement cs) { try { res[0] = UnitCompiler.this.compile2(cs); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAssertStatement(AssertStatement as) { try { res[0] = UnitCompiler.this.compile2(as); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitEmptyStatement(EmptyStatement es) { res[0] = UnitCompiler.this.compile2(es); }
@Override public void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { try { res[0] = UnitCompiler.this.compile2(lcds); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { try { res[0] = UnitCompiler.this.compile2(aci); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { try { res[0] = UnitCompiler.this.compile2(sci); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try {
bs.accept(bsv);
return res[0];
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
/**
* Called to check whether the given {@link Rvalue} compiles or not.
*
* @return Whether the block statement can complete normally
*/
private boolean
fakeCompile(BlockStatement bs) throws CompileException {
Offset from = this.codeContext.newOffset();
boolean ccn = this.compile(bs);
Offset to = this.codeContext.newOffset();
this.codeContext.removeCode(from, to);
return ccn;
}
private boolean
compile2(Initializer i) throws CompileException {
return this.compile(i.block);
}
private boolean
compile2(Block b) throws CompileException {
this.codeContext.saveLocalVariables();
try {
return this.compileStatements(b.statements);
} finally {
this.codeContext.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;
}
previousStatementCanCompleteNormally = this.compile(bs);
}
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.codeContext.newOffset();
// Compile body.
ds.whereToContinue = null;
if (!this.compile(ds.body) && ds.whereToContinue == null) {
this.warning("DSNTC", "\"do\" statement never tests its condition", ds.getLocation());
if (ds.whereToBreak == null) return false;
ds.whereToBreak.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.codeContext.saveLocalVariables();
try {
// Compile initializer.
if (fs.optionalInit != null) this.compile(fs.optionalInit);
if (fs.optionalCondition == null) {
return this.compileUnconditionalLoop(fs, fs.body, fs.optionalUpdate);
} else
{
Object cvc = this.getConstantValue(fs.optionalCondition);
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, fs.optionalUpdate);
} else
{
this.warning("FSNR", "FOR statement never repeats", fs.getLocation());
}
}
}
CodeContext.Offset toCondition = this.codeContext.new Offset();
this.writeBranch(fs, Opcode.GOTO, toCondition);
// Compile body.
fs.whereToContinue = null;
final CodeContext.Offset bodyOffset = this.codeContext.newOffset();
boolean bodyCcn = this.compile(fs.body);
if (fs.whereToContinue != null) fs.whereToContinue.set();
// Compile update.
if (fs.optionalUpdate != null) {
if (!bodyCcn && fs.whereToContinue == null) {
this.warning("FUUR", "For update is unreachable", fs.getLocation());
} else
{
for (Rvalue rv : fs.optionalUpdate) this.compile(rv);
}
}
fs.whereToContinue = null;
// Compile condition.
toCondition.set();
this.compileBoolean(fs.optionalCondition, bodyOffset, UnitCompiler.JUMP_IF_TRUE);
} finally {
this.codeContext.restoreLocalVariables();
}
if (fs.whereToBreak != null) {
fs.whereToBreak.set();
fs.whereToBreak = null;
}
return true;
}
private boolean
compile2(ForEachStatement fes) throws CompileException {
IClass expressionType = this.getType(fes.expression);
if (expressionType.isArray()) {
this.codeContext.saveLocalVariables();
try {
// Allocate the local variable for the current element.
LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false);
elementLv.setSlot(this.codeContext.allocateLocalVariable(
Descriptor.size(elementLv.type.getDescriptor()),
fes.currentElement.name,
elementLv.type
));
// Compile initializer.
this.compileGetValue(fes.expression);
short expressionLv = this.codeContext.allocateLocalVariable((short) 1);
this.store(fes.expression, expressionType, expressionLv);
this.pushConstant(fes, 0);
LocalVariable indexLv = new LocalVariable(false, IClass.INT);
indexLv.setSlot(this.codeContext.allocateLocalVariable((short) 1, null, indexLv.type));
this.store(fes, indexLv);
CodeContext.Offset toCondition = this.codeContext.new Offset();
this.writeBranch(fes, Opcode.GOTO, toCondition);
// Compile the body.
fes.whereToContinue = null;
final CodeContext.Offset bodyOffset = this.codeContext.newOffset();
this.load(fes, expressionType, expressionLv);
this.load(fes, indexLv);
this.writeOpcode(fes, Opcode.IALOAD + UnitCompiler.ilfdabcs(expressionType.getComponentType()));
this.assignmentConversion(fes.currentElement, expressionType.getComponentType(), elementLv.type, null);
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());
} else {
this.crement(fes, indexLv, "++");
}
fes.whereToContinue = null;
// Compile condition.
toCondition.set();
this.load(fes, indexLv);
this.load(fes, expressionType, expressionLv);
this.writeOpcode(fes, Opcode.ARRAYLENGTH);
this.writeBranch(fes, Opcode.IF_ICMPLT, bodyOffset);
} finally {
this.codeContext.restoreLocalVariables();
}
if (fes.whereToBreak != null) {
fes.whereToBreak.set();
fes.whereToBreak = null;
}
} else
if (this.iClassLoader.TYPE_java_lang_Iterable.isAssignableFrom(expressionType)) {
this.codeContext.saveLocalVariables();
try {
// Allocate the local variable for the current element.
LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false);
elementLv.setSlot(this.codeContext.allocateLocalVariable(
(short) 1,
fes.currentElement.name,
elementLv.type
));
// Compile initializer.
this.compileGetValue(fes.expression);
this.invoke(fes.expression, this.iClassLoader.METH_java_lang_Iterable__iterator);
LocalVariable iteratorLv = new LocalVariable(false, this.iClassLoader.TYPE_java_util_Iterator);
iteratorLv.setSlot(this.codeContext.allocateLocalVariable((short) 1, null, iteratorLv.type));
this.store(fes, iteratorLv);
CodeContext.Offset toCondition = this.codeContext.new Offset();
this.writeBranch(fes, Opcode.GOTO, toCondition);
// Compile the body.
fes.whereToContinue = null;
final CodeContext.Offset bodyOffset = this.codeContext.newOffset();
this.load(fes, iteratorLv);
this.invoke(fes.expression, this.iClassLoader.METH_java_util_Iterator__next);
if (
!this.tryAssignmentConversion(
fes.currentElement,
this.iClassLoader.TYPE_java_lang_Object,
elementLv.type,
null
)
&& !this.tryNarrowingReferenceConversion(
fes.currentElement,
this.iClassLoader.TYPE_java_lang_Object,
elementLv.type
)
) throw new AssertionError();
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.invoke(fes.expression, this.iClassLoader.METH_java_util_Iterator__hasNext);
this.writeBranch(fes, Opcode.IFNE, bodyOffset);
} finally {
this.codeContext.restoreLocalVariables();
}
if (fes.whereToBreak != null) {
fes.whereToBreak.set();
fes.whereToBreak = null;
}
} else
{
this.compileError("Cannot iterate over '" + expressionType + "'");
}
return true;
}
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());
}
}
// Compile body.
ws.whereToContinue = this.codeContext.new Offset();
this.writeBranch(ws, Opcode.GOTO, ws.whereToContinue);
final CodeContext.Offset bodyOffset = this.codeContext.newOffset();
this.compile(ws.body); // Return value (CCN) is ignored.
ws.whereToContinue.set();
ws.whereToContinue = null;
// Compile condition.
this.compileBoolean(ws.condition, bodyOffset, UnitCompiler.JUMP_IF_TRUE);
if (ws.whereToBreak != null) {
ws.whereToBreak.set();
ws.whereToBreak = null;
}
return true;
}
private boolean
compileUnconditionalLoop(ContinuableStatement cs, BlockStatement body, Rvalue[] optionalUpdate)
throws CompileException {
if (optionalUpdate != null) return this.compileUnconditionalLoopWithUpdate(cs, body, optionalUpdate);
// Compile body.
cs.whereToContinue = this.codeContext.newOffset();
if (this.compile(body)) this.writeBranch(cs, Opcode.GOTO, cs.whereToContinue);
cs.whereToContinue = null;
if (cs.whereToBreak == null) return false;
cs.whereToBreak.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.codeContext.newOffset();
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.writeBranch(cs, Opcode.GOTO, bodyOffset);
}
cs.whereToContinue = null;
if (cs.whereToBreak == null) return false;
cs.whereToBreak.set();
cs.whereToBreak = null;
return true;
}
private boolean
compile2(LabeledStatement ls) throws CompileException {
boolean canCompleteNormally = this.compile(ls.body);
if (ls.whereToBreak == null) return canCompleteNormally;
ls.whereToBreak.set();
ls.whereToBreak = null;
return true;
}
private boolean
compile2(SwitchStatement ss) throws CompileException {
// Compute condition.
IClass switchExpressionType = this.compileGetValue(ss.condition);
this.assignmentConversion(
ss, // locatable
switchExpressionType, // sourceType
IClass.INT, // targetType
null // optionalConstantValue
);
// 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) {
SwitchStatement.SwitchBlockStatementGroup sbsg = (SwitchStatement.SwitchBlockStatementGroup) (
ss.sbsgs.get(i)
);
sbsgOffsets[i] = this.codeContext.new Offset();
for (Rvalue caseLabel : sbsg.caseLabels) {
// 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());
cv = new Integer(99);
}
// Verify that case label is assignable to the type of the switch expression.
IClass rvType = this.getType(caseLabel);
this.assignmentConversion(
ss, // locatable
rvType, // sourceType
switchExpressionType, // targetType
cv // optionalConstantValue
);
// Convert char, byte, short, int to "Integer".
Integer civ;
if (cv instanceof Integer) {
civ = (Integer) cv;
} else
if (cv instanceof Number) {
civ = new Integer(((Number) cv).intValue());
} else
if (cv instanceof Character) {
civ = new Integer(((Character) cv).charValue());
} else {
this.compileError(
"Value of case label must be a char, byte, short or int constant",
caseLabel.getLocation()
);
civ = new Integer(99);
}
// Store in case label map.
if (caseLabelMap.containsKey(civ)) {
this.compileError("Duplicate \"case\" switch label value", caseLabel.getLocation());
}
caseLabelMap.put(civ, sbsgOffsets[i]);
}
if (sbsg.hasDefaultLabel) {
if (defaultLabelOffset != null) {
this.compileError("Duplicate \"default\" switch label", sbsg.getLocation());
}
defaultLabelOffset = sbsgOffsets[i];
}
}
if (defaultLabelOffset == null) defaultLabelOffset = this.getWhereToBreak(ss);
// Generate TABLESWITCH or LOOKUPSWITCH instruction.
CodeContext.Offset switchOffset = this.codeContext.newOffset();
if (caseLabelMap.isEmpty()) {
// Special case: SWITCH statement without CASE labels (but maybe a DEFAULT label).
;
} else
if (
(Integer) caseLabelMap.firstKey() + caseLabelMap.size() // Beware of INT overflow!
>= (Integer) caseLabelMap.lastKey() - caseLabelMap.size()
) {
// The case label values are strictly consecutity or almost consecutive (at most 50% 'gaps'), so
// let's use a TABLESWITCH.
final int low = (Integer) caseLabelMap.firstKey();
final int high = (Integer) caseLabelMap.lastKey();
this.writeOpcode(ss, Opcode.TABLESWITCH);
new Padder(this.codeContext).set();
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();
while (cur < caseLabelValue) {
this.writeOffset(switchOffset, defaultLabelOffset);
++cur;
}
this.writeOffset(switchOffset, caseLabelOffset);
++cur;
}
} else
{
// The case label values are not 'consecutive enough', so use a LOOKUPSWOTCH.
this.writeOpcode(ss, Opcode.LOOKUPSWITCH);
new Padder(this.codeContext).set();
this.writeOffset(switchOffset, defaultLabelOffset);
this.writeInt(caseLabelMap.size());
for (Map.Entry me : caseLabelMap.entrySet()) {
this.writeInt((Integer) me.getKey());
this.writeOffset(switchOffset, (CodeContext.Offset) me.getValue());
}
}
// Compile statement groups.
boolean canCompleteNormally = true;
for (int i = 0; i < ss.sbsgs.size(); ++i) {
SwitchStatement.SwitchBlockStatementGroup sbsg = (
(SwitchStatement.SwitchBlockStatementGroup) ss.sbsgs.get(i)
);
sbsgOffsets[i].set();
canCompleteNormally = true;
for (BlockStatement bs : sbsg.blockStatements) {
if (!canCompleteNormally) {
this.compileError("Statement is unreachable", bs.getLocation());
break;
}
canCompleteNormally = this.compile(bs);
}
}
if (ss.whereToBreak == null) return canCompleteNormally;
ss.whereToBreak.set();
ss.whereToBreak = null;
return true;
}
private boolean
compile2(BreakStatement bs) throws CompileException {
// Find the broken statement.
BreakableStatement brokenStatement = null;
if (bs.optionalLabel == 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.optionalLabel)) {
brokenStatement = ls;
break;
}
}
}
if (brokenStatement == null) {
this.compileError((
"Statement \"break "
+ bs.optionalLabel
+ "\" is not enclosed by a breakable statement with label \""
+ bs.optionalLabel
+ "\""
), bs.getLocation());
return false;
}
}
this.leaveStatements(
bs.getEnclosingScope(), // from
brokenStatement.getEnclosingScope(), // to
null // optionalStackValueType
);
this.writeBranch(bs, Opcode.GOTO, this.getWhereToBreak(brokenStatement));
return false;
}
private boolean
compile2(ContinueStatement cs) throws CompileException {
// Find the continued statement.
ContinuableStatement continuedStatement = null;
if (cs.optionalLabel == 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.optionalLabel)) {
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.optionalLabel
+ "\" is not enclosed by a continuable statement with label \""
+ cs.optionalLabel
+ "\""
), cs.getLocation());
return false;
}
}
if (continuedStatement.whereToContinue == null) {
continuedStatement.whereToContinue = this.codeContext.new Offset();
}
this.leaveStatements(
cs.getEnclosingScope(), // from
continuedStatement.getEnclosingScope(), // to
null // optionalStackValueType
);
this.writeBranch(cs, Opcode.GOTO, continuedStatement.whereToContinue);
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.codeContext.new Offset();
try {
this.compileBoolean(as.expression1, end, UnitCompiler.JUMP_IF_TRUE);
this.writeOpcode(as, Opcode.NEW);
this.writeConstantClassInfo(Descriptor.JAVA_LANG_ASSERTIONERROR);
this.writeOpcode(as, Opcode.DUP);
Rvalue[] arguments = (
as.optionalExpression2 == null
? new Rvalue[0]
: new Rvalue[] { as.optionalExpression2 }
);
this.invokeConstructor(
as, // locatable
as, // scope
null, // optionalEnclosingInstance
this.iClassLoader.TYPE_java_lang_AssertionError, // targetClass
arguments // arguments
);
this.writeOpcode(as, Opcode.ATHROW);
} finally {
end.set();
}
return true;
}
@SuppressWarnings("static-method") private boolean
compile2(EmptyStatement es) { return true; }
private boolean
compile2(ExpressionStatement ee) throws CompileException {
this.compile(ee.rvalue);
return true;
}
private boolean
compile2(FieldDeclaration fd) throws CompileException {
for (VariableDeclarator vd : fd.variableDeclarators) {
ArrayInitializerOrRvalue initializer = this.getNonConstantFinalInitializer(fd, vd);
if (initializer == null) continue;
assert fd.modifiers.annotations.length == 0;
if (!Mod.isStatic(fd.modifiers.flags)) this.writeOpcode(fd, Opcode.ALOAD_0);
IClass fieldType = this.getType(fd.type);
if (initializer instanceof Rvalue) {
Rvalue rvalue = (Rvalue) initializer;
IClass initializerType = this.compileGetValue(rvalue);
fieldType = fieldType.getArrayIClass(vd.brackets, this.iClassLoader.TYPE_java_lang_Object);
this.assignmentConversion(
fd, // locatable
initializerType, // sourceType
fieldType, // targetType
this.getConstantValue(rvalue) // optionalConstantValue
);
} else
if (initializer instanceof ArrayInitializer) {
this.compileGetValue((ArrayInitializer) initializer, fieldType);
} else
{
throw new JaninoRuntimeException(
"Unexpected array initializer or rvalue class "
+ initializer.getClass().getName()
);
}
// No need to check accessibility here.
;
assert fd.modifiers.annotations.length == 0;
this.putfield(fd, this.resolve(fd.getDeclaringType()).getDeclaredIField(vd.name));
}
return true;
}
private boolean
compile2(IfStatement is) throws CompileException {
Object cv = this.getConstantValue(is.condition);
BlockStatement es = (
is.optionalElseStatement != null
? is.optionalElseStatement
: new EmptyStatement(is.thenStatement.getLocation())
);
if (cv instanceof Boolean) {
// Constant condition.
this.fakeCompile(is.condition);
BlockStatement seeingStatement, blindStatement;
if (((Boolean) cv).booleanValue()) {
seeingStatement = is.thenStatement;
blindStatement = es;
} else {
seeingStatement = es;
blindStatement = is.thenStatement;
}
// Compile the seeing statement.
final CodeContext.Inserter ins = this.codeContext.newInserter();
boolean ssccn = this.compile(seeingStatement);
boolean bsccn = this.fakeCompile(blindStatement);
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.codeContext.newOffset();
this.codeContext.pushInserter(ins);
try {
this.pushConstant(is, Boolean.FALSE);
this.writeBranch(is, Opcode.IFNE, off);
} finally {
this.codeContext.popInserter();
}
return true;
}
// Non-constant condition.
if (this.generatesCode(is.thenStatement)) {
if (this.generatesCode(es)) {
// if (expression) statement else statement
CodeContext.Offset eso = this.codeContext.new Offset();
CodeContext.Offset end = this.codeContext.new Offset();
this.compileBoolean(is.condition, eso, UnitCompiler.JUMP_IF_FALSE);
boolean tsccn = this.compile(is.thenStatement);
if (tsccn) this.writeBranch(is, Opcode.GOTO, end);
eso.set();
boolean esccn = this.compile(es);
end.set();
return tsccn || esccn;
} else {
// if (expression) statement else ;
CodeContext.Offset end = this.codeContext.new Offset();
this.compileBoolean(is.condition, end, UnitCompiler.JUMP_IF_FALSE);
this.compile(is.thenStatement);
end.set();
return true;
}
} else {
if (this.generatesCode(es)) {
// if (expression) ; else statement
CodeContext.Offset end = this.codeContext.new Offset();
this.compileBoolean(is.condition, end, UnitCompiler.JUMP_IF_TRUE);
this.compile(es);
end.set();
return true;
} else {
// if (expression) ; else ;
IClass 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 != 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. */
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).optionalStatements
);
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 {
assert lvds.modifiers.annotations.length == 0;
if ((lvds.modifiers.flags & ~Mod.FINAL) != 0) {
this.compileError(
"The only allowed modifier in local variable declarations is \"final\"",
lvds.getLocation()
);
}
for (VariableDeclarator vd : lvds.variableDeclarators) {
LocalVariable lv = this.getLocalVariable(lvds, vd);
lv.setSlot(
this.codeContext.allocateLocalVariable(Descriptor.size(lv.type.getDescriptor()), vd.name, lv.type)
);
if (vd.optionalInitializer != null) {
if (vd.optionalInitializer instanceof Rvalue) {
Rvalue rhs = (Rvalue) vd.optionalInitializer;
this.assignmentConversion(
lvds, // locatable
this.compileGetValue(rhs), // sourceType
lv.type, // targetType
this.getConstantValue(rhs) // optionalConstantValue
);
} else
if (vd.optionalInitializer instanceof ArrayInitializer) {
this.compileGetValue((ArrayInitializer) vd.optionalInitializer, lv.type);
} else
{
throw new JaninoRuntimeException(
"Unexpected rvalue or array initialized class "
+ vd.optionalInitializer.getClass().getName()
);
}
this.store(lvds, lv);
}
}
return true;
}
/** @return The {@link LocalVariable} corresponding with the local variable declaration/declarator */
public LocalVariable
getLocalVariable(LocalVariableDeclarationStatement lvds, VariableDeclarator vd) throws CompileException {
if (vd.localVariable == null) {
// Determine variable type.
Type variableType = lvds.type;
for (int k = 0; k < vd.brackets; ++k) variableType = new ArrayType(variableType);
assert lvds.modifiers.annotations.length == 0;
vd.localVariable = new LocalVariable(
Mod.isFinal(lvds.modifiers.flags), // finaL
this.getType(variableType) // type
);
}
return vd.localVariable;
}
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;
}
IClass returnType = this.getReturnType(enclosingFunction);
if (returnType == IClass.VOID) {
if (rs.optionalReturnValue != null) this.compileError("Method must not return a value", rs.getLocation());
this.leaveStatements(
rs.getEnclosingScope(), // from
enclosingFunction, // to
null // optionalStackValueType
);
this.writeOpcode(rs, Opcode.RETURN);
return false;
}
if (rs.optionalReturnValue == null) {
this.compileError("Method must return a value", rs.getLocation());
return false;
}
IClass type = this.compileGetValue(rs.optionalReturnValue);
this.assignmentConversion(
rs, // locatable
type, // sourceType
returnType, // targetType
this.getConstantValue(rs.optionalReturnValue) // optionalConstantValue
);
this.leaveStatements(
rs.getEnclosingScope(), // from
enclosingFunction, // to
returnType // optionalStackValueType
);
this.writeOpcode(rs, Opcode.IRETURN + UnitCompiler.ilfda(returnType));
return false;
}
private boolean
compile2(SynchronizedStatement ss) throws CompileException {
// Evaluate monitor object expression.
if (!this.iClassLoader.TYPE_java_lang_Object.isAssignableFrom(this.compileGetValue(ss.expression))) {
this.compileError(
"Monitor object of \"synchronized\" statement is not a subclass of \"Object\"",
ss.getLocation()
);
}
this.codeContext.saveLocalVariables();
boolean canCompleteNormally = false;
try {
// Allocate a local variable for the monitor object.
ss.monitorLvIndex = this.codeContext.allocateLocalVariable((short) 1);
// Store the monitor object.
this.writeOpcode(ss, Opcode.DUP);
this.store(ss, this.iClassLoader.TYPE_java_lang_Object, ss.monitorLvIndex);
// Create lock on the monitor object.
this.writeOpcode(ss, Opcode.MONITORENTER);
// Compile the statement body.
final CodeContext.Offset monitorExitOffset = this.codeContext.new Offset();
final CodeContext.Offset beginningOfBody = this.codeContext.newOffset();
canCompleteNormally = this.compile(ss.body);
if (canCompleteNormally) {
this.writeBranch(ss, Opcode.GOTO, monitorExitOffset);
}
// Generate the exception handler.
CodeContext.Offset here = this.codeContext.newOffset();
this.codeContext.addExceptionTableEntry(
beginningOfBody, // startPC
here, // endPC
here, // handlerPC
null // catchTypeFD
);
this.leave(ss, this.iClassLoader.TYPE_java_lang_Throwable);
this.writeOpcode(ss, Opcode.ATHROW);
// Unlock monitor object.
if (canCompleteNormally) {
monitorExitOffset.set();
this.leave(ss, null);
}
} finally {
this.codeContext.restoreLocalVariables();
}
return canCompleteNormally;
}
private boolean
compile2(ThrowStatement ts) throws CompileException {
IClass expressionType = this.compileGetValue(ts.expression);
this.checkThrownException(
ts, // locatable
expressionType, // type
ts.getEnclosingScope() // scope
);
this.writeOpcode(ts, Opcode.ATHROW);
return false;
}
private boolean
compile2(TryStatement ts) throws CompileException {
if (ts.optionalFinally != null) ts.finallyOffset = this.codeContext.new Offset();
final CodeContext.Offset beginningOfBody = this.codeContext.newOffset();
final CodeContext.Offset afterStatement = this.codeContext.new Offset();
this.codeContext.saveLocalVariables();
try {
// Allocate a LV for the JSR of the FINALLY clause.
//
// Notice:
// For unclear reasons, this variable must not overlap with any of the body's variables (although the
// body's variables are out of scope when it comes to the FINALLY clause!?), otherwise you get
// java.lang.VerifyError: ... Accessing value from uninitialized local variable 4
// See bug #56.
final short pcLvIndex = (
ts.optionalFinally != null
? this.codeContext.allocateLocalVariable((short) 1)
: (short) 0
);
// Initialize all catch clauses as "unreachable" only to check later that they ARE indeed reachable.
for (CatchClause catchClause : ts.catchClauses) {
IClass caughtExceptionType = this.getType(catchClause.caughtException.type);
catchClause.reachable = (
// Superclass or subclass of "java.lang.Error"?
this.iClassLoader.TYPE_java_lang_Error.isAssignableFrom(caughtExceptionType)
|| caughtExceptionType.isAssignableFrom(this.iClassLoader.TYPE_java_lang_Error)
// Superclass or subclass of "java.lang.RuntimeException"?
|| this.iClassLoader.TYPE_java_lang_RuntimeException.isAssignableFrom(caughtExceptionType)
|| caughtExceptionType.isAssignableFrom(this.iClassLoader.TYPE_java_lang_RuntimeException)
);
}
boolean canCompleteNormally = this.compile(ts.body);
CodeContext.Offset afterBody = this.codeContext.newOffset();
if (canCompleteNormally) {
this.writeBranch(ts, Opcode.GOTO, afterStatement);
}
if (beginningOfBody.offset != afterBody.offset) { // Avoid zero-length exception table entries.
this.codeContext.saveLocalVariables();
try {
for (int i = 0; i < ts.catchClauses.size(); ++i) {
try {
this.codeContext.saveLocalVariables();
CatchClause catchClause = (CatchClause) ts.catchClauses.get(i);
IClass caughtExceptionType = this.getType(catchClause.caughtException.type);
// Verify that the CATCH clause is reachable.
if (!catchClause.reachable) {
this.compileError("Catch clause is unreachable", catchClause.getLocation());
}
// Allocate the "exception variable".
LocalVariableSlot exceptionVarSlot = this.codeContext.allocateLocalVariable(
(short) 1,
catchClause.caughtException.name,
caughtExceptionType
);
final short evi = exceptionVarSlot.getSlotIndex();
// Kludge: Treat the exception variable like a local variable of the catch clause body.
this.getLocalVariable(catchClause.caughtException).setSlot(exceptionVarSlot);
this.codeContext.addExceptionTableEntry(
beginningOfBody, // startPC
afterBody, // endPC
this.codeContext.newOffset(), // handlerPC
caughtExceptionType.getDescriptor() // catchTypeFD
);
this.store(
catchClause, // locatable
caughtExceptionType, // lvType
evi // lvIndex
);
if (this.compile(catchClause.body)) {
canCompleteNormally = true;
if (
i < ts.catchClauses.size() - 1
|| ts.optionalFinally != null
) this.writeBranch(catchClause, Opcode.GOTO, afterStatement);
}
} finally {
this.codeContext.restoreLocalVariables();
}
}
} finally {
this.codeContext.restoreLocalVariables();
}
}
if (ts.optionalFinally != null) {
CodeContext.Offset here = this.codeContext.newOffset();
this.codeContext.addExceptionTableEntry(
beginningOfBody, // startPC
here, // endPC
here, // handlerPC
null // catchTypeFD
);
this.codeContext.saveLocalVariables();
try {
// Save the exception object in an anonymous local variable.
short evi = this.codeContext.allocateLocalVariable((short) 1);
this.store(
ts.optionalFinally, // locatable
this.iClassLoader.TYPE_java_lang_Object, // lvType
evi // lvIndex
);
this.writeBranch(ts.optionalFinally, Opcode.JSR, ts.finallyOffset);
this.load(
ts.optionalFinally, // locatable
this.iClassLoader.TYPE_java_lang_Object, // type
evi // index
);
this.writeOpcode(ts.optionalFinally, Opcode.ATHROW);
// Compile the "finally" body.
ts.finallyOffset.set();
this.store(
ts.optionalFinally, // locatable
this.iClassLoader.TYPE_java_lang_Object, // lvType
pcLvIndex // lvIndex
);
if (this.compile(ts.optionalFinally)) {
if (pcLvIndex > 255) {
this.writeOpcode(ts.optionalFinally, Opcode.WIDE);
this.writeOpcode(ts.optionalFinally, Opcode.RET);
this.writeShort(pcLvIndex);
} else {
this.writeOpcode(ts.optionalFinally, Opcode.RET);
this.writeByte(pcLvIndex);
}
}
} 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.codeContext.restoreLocalVariables();
}
}
afterStatement.set();
if (canCompleteNormally) this.leave(ts, null);
return canCompleteNormally;
} finally {
this.codeContext.restoreLocalVariables();
}
}
// ------------ FunctionDeclarator.compile() -------------
private void
compile(FunctionDeclarator fd, final ClassFile classFile) throws CompileException {
ClassFile.MethodInfo mi;
if (Mod.isPrivateAccess(fd.modifiers.flags)) {
if (fd instanceof MethodDeclarator && !fd.isStatic()) {
// 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 modifiers = Mod.changeAccess(
fd.modifiers.flags, // modifiers
Mod.PACKAGE // newAccess
);
modifiers |= Mod.STATIC;
mi = classFile.addMethodInfo(
new Modifiers(modifiers, fd.modifiers.annotations), // modifiersAnd
fd.name + '$', // methodName
MethodDescriptor.prependParameter( // methodMD
this.toIMethod((MethodDeclarator) fd).getDescriptor(), // md
this.resolve(fd.getDeclaringType()).getDescriptor() // parameterFD
)
);
} else
{
// To make the static private 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
assert fd.modifiers.annotations.length == 0 : "NYI";
short modifiers = Mod.changeAccess(fd.modifiers.flags, Mod.PACKAGE);
mi = classFile.addMethodInfo(
new Modifiers(modifiers, fd.modifiers.annotations), // modifiers
fd.name, // methodName
this.toIInvocable(fd).getDescriptor() // methodMD
);
}
} else {
// Non-PRIVATE function.
mi = classFile.addMethodInfo(
fd.modifiers, // modifiers
fd.name, // methodName
this.toIInvocable(fd).getDescriptor() // methodMD
);
}
// Add "Exceptions" attribute (JVMS 4.7.4).
{
if (fd.thrownExceptions.length > 0) {
final short eani = classFile.addConstantUtf8Info("Exceptions");
short[] tecciis = new short[fd.thrownExceptions.length];
for (int i = 0; i < fd.thrownExceptions.length; ++i) {
tecciis[i] = classFile.addConstantClassInfo(this.getType(fd.thrownExceptions[i]).getDescriptor());
}
mi.addAttribute(new ClassFile.ExceptionsAttribute(eani, tecciis));
}
}
// Add "Deprecated" attribute (JVMS 4.7.10)
if (fd.hasDeprecatedDocTag()) {
mi.addAttribute(new ClassFile.DeprecatedAttribute(classFile.addConstantUtf8Info("Deprecated")));
}
if (Mod.isAbstract(fd.modifiers.flags) || Mod.isNative(fd.modifiers.flags)) return;
// Create CodeContext.
final CodeContext codeContext = new CodeContext(mi.getClassFile(), mi.getDescriptor());
CodeContext savedCodeContext = this.replaceCodeContext(codeContext);
try {
this.codeContext.saveLocalVariables();
// Define special parameter "this".
if (!Mod.isStatic(fd.modifiers.flags)) {
this.codeContext.allocateLocalVariable((short) 1, "this", this.resolve(fd.getDeclaringType()));
}
if (fd instanceof ConstructorDeclarator) {
ConstructorDeclarator constructorDeclarator = (ConstructorDeclarator) fd;
// Reserve space for synthetic parameters ("this$...", "val$...").
for (IField sf : constructorDeclarator.getDeclaringClass().syntheticFields.values()) {
LocalVariable lv = new LocalVariable(true, sf.getType());
lv.setSlot(this.codeContext.allocateLocalVariable(Descriptor.size(sf.getDescriptor()), null, null));
constructorDeclarator.syntheticParameters.put(sf.getName(), lv);
}
}
this.buildLocalVariableMap(fd);
// Compile the constructor preamble.
if (fd instanceof ConstructorDeclarator) {
ConstructorDeclarator cd = (ConstructorDeclarator) fd;
if (cd.optionalConstructorInvocation != null) {
this.compile(cd.optionalConstructorInvocation);
if (cd.optionalConstructorInvocation instanceof SuperConstructorInvocation) {
this.assignSyntheticParametersToSyntheticFields(cd);
this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd);
}
} else {
// Determine qualification for superconstructor invocation.
IClass outerClassOfSuperclass = this.resolve(
cd.getDeclaringClass()
).getSuperclass().getOuterIClass();
QualifiedThisReference qualification = null;
if (outerClassOfSuperclass != null) {
qualification = new QualifiedThisReference(
cd.getLocation(), // location
new SimpleType(cd.getLocation(), outerClassOfSuperclass) // qualification
);
}
// Invoke the superconstructor.
SuperConstructorInvocation sci = new SuperConstructorInvocation(
cd.getLocation(), // location
qualification, // optionalQualification
new Rvalue[0] // arguments
);
sci.setEnclosingScope(fd);
this.compile(sci);
this.assignSyntheticParametersToSyntheticFields(cd);
this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd);
}
}
// Compile the function body.
if (fd.optionalStatements == null) {
this.compileError("Method must have a body", fd.getLocation());
return;
}
if (this.compileStatements(fd.optionalStatements)) {
if (this.getReturnType(fd) != IClass.VOID) {
this.compileError("Method must return a value", fd.getLocation());
}
this.writeOpcode(fd, Opcode.RETURN);
}
} finally {
this.codeContext.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();
// Do flow analysis.
if (UnitCompiler.DEBUG) {
try {
codeContext.flowAnalysis(fd.toString());
} catch (RuntimeException ex) {
ex.printStackTrace();
;
}
} else {
codeContext.flowAnalysis(fd.toString());
}
final short lntani;
if (this.debugLines) {
lntani = classFile.addConstantUtf8Info("LineNumberTable");
} else {
lntani = 0;
}
final short lvtani;
if (this.debugVars) {
UnitCompiler.makeLocalVariableNames(codeContext, mi);
lvtani = classFile.addConstantUtf8Info("LocalVariableTable");
} else {
lvtani = 0;
}
// Add the code context as a code attribute to the MethodInfo.
mi.addAttribute(new ClassFile.AttributeInfo(classFile.addConstantUtf8Info("Code")) {
@Override protected void
storeBody(DataOutputStream dos) throws IOException {
codeContext.storeCodeAttributeBody(dos, lntani, lvtani);
}
});
}
/** 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()) {
if (slot.getName() != null) {
String typeName = slot.getType().getDescriptor();
cf.addConstantUtf8Info(typeName);
cf.addConstantUtf8Info(slot.getName());
}
}
}
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];
IClass parameterIClass = this.getType(fp.type);
LocalVariable lv = this.getLocalVariable(
fp,
i == fd.formalParameters.parameters.length - 1 && fd.formalParameters.variableArity
);
lv.setSlot(this.codeContext.allocateLocalVariable(
Descriptor.size(lv.type.getDescriptor()),
fp.name,
parameterIClass
));
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;
if (cd.optionalConstructorInvocation != null) {
UnitCompiler.buildLocalVariableMap(cd.optionalConstructorInvocation, localVars);
}
}
if (fd.optionalStatements != null) {
for (BlockStatement bs : fd.optionalStatements) localVars = this.buildLocalVariableMap(bs, localVars);
}
}
/** Computes and fills in the 'local variable map' for the given {@code blockStatement}. */
private Map
buildLocalVariableMap(BlockStatement blockStatement, final Map localVars)
throws CompileException {
final Map[] resVars = new Map[] { localVars };
BlockStatementVisitor bsv = new BlockStatementVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
// Basic statements that use the default handlers.
@Override public void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { UnitCompiler.buildLocalVariableMap(aci, localVars); }
@Override public void visitBreakStatement(BreakStatement bs) { UnitCompiler.buildLocalVariableMap(bs, localVars); }
@Override public void visitContinueStatement(ContinueStatement cs) { UnitCompiler.buildLocalVariableMap(cs, localVars); }
@Override public void visitAssertStatement(AssertStatement as) { UnitCompiler.buildLocalVariableMap(as, localVars); }
@Override public void visitEmptyStatement(EmptyStatement es) { UnitCompiler.buildLocalVariableMap(es, localVars); }
@Override public void visitExpressionStatement(ExpressionStatement es) { UnitCompiler.buildLocalVariableMap(es, localVars); }
@Override public void visitFieldDeclaration(FieldDeclaration fd) { UnitCompiler.buildLocalVariableMap(fd, localVars); }
@Override public void visitReturnStatement(ReturnStatement rs) { UnitCompiler.buildLocalVariableMap(rs, localVars); }
@Override public void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { UnitCompiler.buildLocalVariableMap(sci, localVars); }
@Override public void visitThrowStatement(ThrowStatement ts) { UnitCompiler.buildLocalVariableMap(ts, localVars); }
@Override public void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { UnitCompiler.buildLocalVariableMap(lcds, localVars); }
// More complicated statements with specialized handlers, but don't add new variables in this scope.
@Override public void visitBlock(Block b) { try { UnitCompiler.this.buildLocalVariableMap(b, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitDoStatement(DoStatement ds) { try { UnitCompiler.this.buildLocalVariableMap(ds, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitForStatement(ForStatement fs) { try { UnitCompiler.this.buildLocalVariableMap(fs, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitForEachStatement(ForEachStatement fes) { try { UnitCompiler.this.buildLocalVariableMap(fes, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitIfStatement(IfStatement is) { try { UnitCompiler.this.buildLocalVariableMap(is, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitInitializer(Initializer i) { try { UnitCompiler.this.buildLocalVariableMap(i, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSwitchStatement(SwitchStatement ss) { try { UnitCompiler.this.buildLocalVariableMap(ss, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSynchronizedStatement(SynchronizedStatement ss) { try { UnitCompiler.this.buildLocalVariableMap(ss, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitTryStatement(TryStatement ts) { try { UnitCompiler.this.buildLocalVariableMap(ts, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitWhileStatement(WhileStatement ws) { try { UnitCompiler.this.buildLocalVariableMap(ws, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// More complicated statements with specialized handlers, that can add variables in this scope.
@Override public void visitLabeledStatement(LabeledStatement ls) { try { resVars[0] = UnitCompiler.this.buildLocalVariableMap(ls, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { try { resVars[0] = UnitCompiler.this.buildLocalVariableMap(lvds, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try { blockStatement.accept(bsv); } catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
return resVars[0];
}
// 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.optionalInit != null) {
inner = this.buildLocalVariableMap(fs.optionalInit, 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.optionalElseStatement != null) {
this.buildLocalVariableMap(is.optionalElseStatement, 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 (SwitchStatement.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.optionalFinally != null) {
this.buildLocalVariableMap(ts.optionalFinally, 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 {@code localVars} to the 'local variable map' of the given {@code catchClause}. */
protected void
buildLocalVariableMap(CatchClause catchClause, Map localVars) throws CompileException {
Map vars = new HashMap();
vars.putAll(localVars);
LocalVariable lv = this.getLocalVariable(catchClause.caughtException);
vars.put(catchClause.caughtException.name, lv);
this.buildLocalVariableMap(catchClause.body, vars);
}
/** @return The {@link LocalVariable} corresponding with the {@code parameter} */
public LocalVariable
getLocalVariable(FormalParameter parameter) throws CompileException {
return this.getLocalVariable(parameter, false);
}
/**
* @param isVariableArityParameter Whether the {@code parameter} is the last parameter of a 'variable arity'
* (a.k.a. 'varargs') method declaration
* @return The {@link LocalVariable} corresponding with the {@code parameter}
*/
public LocalVariable
getLocalVariable(FormalParameter parameter, boolean isVariableArityParameter)
throws CompileException {
if (parameter.localVariable == null) {
assert parameter.type != null;
IClass parameterType = this.getType(parameter.type);
if (isVariableArityParameter) {
parameterType = parameterType.getArrayIClass(this.iClassLoader.TYPE_java_lang_Object);
}
parameter.localVariable = new LocalVariable(parameter.finaL, parameterType);
}
return parameter.localVariable;
}
// ------------------ Rvalue.compile() ----------------
/** Called to check whether the given {@link Rvalue} compiles or not. */
private void
fakeCompile(Rvalue rv) throws CompileException {
final Offset from = this.codeContext.newOffset();
this.compileContext(rv);
this.compileGet(rv);
Offset to = this.codeContext.newOffset();
this.codeContext.removeCode(from, to);
}
/** Some {@link Rvalue}s compile more efficiently when their value is not needed, e.g. "i++". */
private void
compile(Rvalue rv) throws CompileException {
RvalueVisitor rvv = new RvalueVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitArrayLength(ArrayLength al) { try { UnitCompiler.this.compile2(al); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAssignment(Assignment a) { try { UnitCompiler.this.compile2(a); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitUnaryOperation(UnaryOperation uo) { try { UnitCompiler.this.compile2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBinaryOperation(BinaryOperation bo) { try { UnitCompiler.this.compile2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCast(Cast c) { try { UnitCompiler.this.compile2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitClassLiteral(ClassLiteral cl) { try { UnitCompiler.this.compile2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitConditionalExpression(ConditionalExpression ce) { try { UnitCompiler.this.compile2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCrement(Crement c) { try { UnitCompiler.this.compile2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitInstanceof(Instanceof io) { try { UnitCompiler.this.compile2(io); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitMethodInvocation(MethodInvocation mi) { try { UnitCompiler.this.compile2(mi); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { UnitCompiler.this.compile2(smi); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitIntegerLiteral(IntegerLiteral il) { try { UnitCompiler.this.compile2(il); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { UnitCompiler.this.compile2(fpl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBooleanLiteral(BooleanLiteral bl) { try { UnitCompiler.this.compile2(bl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCharacterLiteral(CharacterLiteral cl) { try { UnitCompiler.this.compile2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitStringLiteral(StringLiteral sl) { try { UnitCompiler.this.compile2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNullLiteral(NullLiteral nl) { try { UnitCompiler.this.compile2(nl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSimpleConstant(SimpleConstant sl) { try { UnitCompiler.this.compile2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { try { UnitCompiler.this.compile2(naci); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewArray(NewArray na) { try { UnitCompiler.this.compile2(na); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewInitializedArray(NewInitializedArray nia) { try { UnitCompiler.this.compile2(nia); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewClassInstance(NewClassInstance nci) { try { UnitCompiler.this.compile2(nci); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitParameterAccess(ParameterAccess pa) { try { UnitCompiler.this.compile2(pa); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { UnitCompiler.this.compile2(qtr); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitThisReference(ThisReference tr) { try { UnitCompiler.this.compile2(tr); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAmbiguousName(AmbiguousName an) { try { UnitCompiler.this.compile2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { UnitCompiler.this.compile2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccess(FieldAccess fa) { try { UnitCompiler.this.compile2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { UnitCompiler.this.compile2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compile2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLocalVariableAccess(LocalVariableAccess lva) { try { UnitCompiler.this.compile2(lva); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { UnitCompiler.this.compile2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try {
rv.accept(rvv);
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
private void
compile2(Rvalue rv) throws CompileException {
this.pop(rv, this.compileGetValue(rv));
}
private void
compile2(Assignment a) throws CompileException {
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) // optionalConstantValue
);
this.compileSet(a.lhs);
return;
}
// Implement "|= ^= &= *= /= %= += -= <<= >>= >>>=".
int lhsCs = this.compileContext(a.lhs);
this.dup(a, lhsCs);
IClass lhsType = this.compileGet(a.lhs);
IClass 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)
&& !this.tryBoxingConversion(a, resultType, lhsType) // Java 5
) this.compileError("Operand types unsuitable for '" + a.operator + "'", a.getLocation());
// Assign the result to the left operand.
this.compileSet(a.lhs);
}
private void
compile2(Crement c) throws CompileException {
// Optimized crement of integer local variable.
LocalVariable lv = this.isIntLv(c);
if (lv != null) {
this.compileLocalVariableCrement(c, lv);
return;
}
int cs = this.compileContext(c.operand);
this.dup(c, cs);
IClass type = this.compileGet(c.operand);
IClass promotedType = this.unaryNumericPromotion(c, type);
this.writeOpcode(c, UnitCompiler.ilfd(
promotedType,
Opcode.ICONST_1,
Opcode.LCONST_1,
Opcode.FCONST_1,
Opcode.DCONST_1
));
if (c.operator == "++") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.writeOpcode(c, Opcode.IADD + UnitCompiler.ilfd(promotedType));
} else
if (c.operator == "--") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.writeOpcode(c, Opcode.ISUB + UnitCompiler.ilfd(promotedType));
} else {
this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation());
}
this.reverseUnaryNumericPromotion(c, promotedType, type);
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.writeOpcode(aci, Opcode.ALOAD_0);
if (declaringIClass.getOuterIClass() != null) this.writeOpcode(aci, Opcode.ALOAD_1);
this.invokeConstructor(
aci, // locatable
declaringConstructor, // scope
(Rvalue) null, // optionalEnclosingInstance
declaringIClass, // targetClass
aci.arguments // arguments
);
return true;
}
private boolean
compile2(SuperConstructorInvocation sci) throws CompileException {
ConstructorDeclarator declaringConstructor = (ConstructorDeclarator) sci.getEnclosingScope();
this.writeOpcode(sci, Opcode.ALOAD_0);
ClassDeclaration declaringClass = declaringConstructor.getDeclaringClass();
IClass superclass = this.resolve(declaringClass).getSuperclass();
Rvalue optionalEnclosingInstance;
if (sci.optionalQualification != null) {
optionalEnclosingInstance = sci.optionalQualification;
} else {
IClass outerIClassOfSuperclass = superclass.getOuterIClass();
if (outerIClassOfSuperclass == null) {
optionalEnclosingInstance = null;
} else {
optionalEnclosingInstance = new QualifiedThisReference(
sci.getLocation(), // location
new SimpleType(sci.getLocation(), outerIClassOfSuperclass) // qualification
);
optionalEnclosingInstance.setEnclosingBlockStatement(sci);
}
}
this.invokeConstructor(
sci, // locatable
declaringConstructor, // scope
optionalEnclosingInstance, // optionalEnclosingInstance
superclass, // targetClass
sci.arguments // arguments
);
return true;
}
/**
* Some {@link Rvalue}s compile more efficiently when their value is the condition for a branch.
*
* Notice that if "this" is a constant, then either {@code dst} is never branched to, or it is unconditionally
* branched to. "Unexamined code" errors may result during bytecode validation.
*
* @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 {
RvalueVisitor rvv = new RvalueVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitArrayLength(ArrayLength al) { try { UnitCompiler.this.compileBoolean2(al, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAssignment(Assignment a) { try { UnitCompiler.this.compileBoolean2(a, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitUnaryOperation(UnaryOperation uo) { try { UnitCompiler.this.compileBoolean2(uo, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBinaryOperation(BinaryOperation bo) { try { UnitCompiler.this.compileBoolean2(bo, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCast(Cast c) { try { UnitCompiler.this.compileBoolean2(c, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitClassLiteral(ClassLiteral cl) { try { UnitCompiler.this.compileBoolean2(cl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitConditionalExpression(ConditionalExpression ce) { try { UnitCompiler.this.compileBoolean2(ce, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCrement(Crement c) { try { UnitCompiler.this.compileBoolean2(c, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitInstanceof(Instanceof io) { try { UnitCompiler.this.compileBoolean2(io, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitMethodInvocation(MethodInvocation mi) { try { UnitCompiler.this.compileBoolean2(mi, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { UnitCompiler.this.compileBoolean2(smi, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitIntegerLiteral(IntegerLiteral il) { try { UnitCompiler.this.compileBoolean2(il, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { UnitCompiler.this.compileBoolean2(fpl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBooleanLiteral(BooleanLiteral bl) { try { UnitCompiler.this.compileBoolean2(bl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCharacterLiteral(CharacterLiteral cl) { try { UnitCompiler.this.compileBoolean2(cl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitStringLiteral(StringLiteral sl) { try { UnitCompiler.this.compileBoolean2(sl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNullLiteral(NullLiteral nl) { try { UnitCompiler.this.compileBoolean2(nl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSimpleConstant(SimpleConstant sl) { try { UnitCompiler.this.compileBoolean2(sl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { try { UnitCompiler.this.compileBoolean2(naci, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewArray(NewArray na) { try { UnitCompiler.this.compileBoolean2(na, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewInitializedArray(NewInitializedArray nia) { try { UnitCompiler.this.compileBoolean2(nia, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewClassInstance(NewClassInstance nci) { try { UnitCompiler.this.compileBoolean2(nci, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitParameterAccess(ParameterAccess pa) { try { UnitCompiler.this.compileBoolean2(pa, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { UnitCompiler.this.compileBoolean2(qtr, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitThisReference(ThisReference tr) { try { UnitCompiler.this.compileBoolean2(tr, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAmbiguousName(AmbiguousName an) { try { UnitCompiler.this.compileBoolean2(an, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { UnitCompiler.this.compileBoolean2(aae, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccess(FieldAccess fa) { try { UnitCompiler.this.compileBoolean2(fa, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { UnitCompiler.this.compileBoolean2(fae, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compileBoolean2(scfae, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLocalVariableAccess(LocalVariableAccess lva) { try { UnitCompiler.this.compileBoolean2(lva, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { UnitCompiler.this.compileBoolean2(pe, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try {
rv.accept(rvv);
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
/**
* @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 {
IClass 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.writeBranch(rv, orientation == UnitCompiler.JUMP_IF_TRUE ? Opcode.IFNE : Opcode.IFEQ, 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.op == "|" || bo.op == "^" || bo.op == "&") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileBoolean2((Rvalue) bo, dst, orientation);
return;
}
if (bo.op == "||" || bo.op == "&&") { // SUPPRESS CHECKSTYLE StringLiteralEquality
Object lhsCv = this.getConstantValue(bo.lhs);
if (lhsCv instanceof Boolean) {
if (((Boolean) lhsCv).booleanValue() ^ bo.op == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality
// "true && a", "false || a"
this.compileBoolean(
bo.rhs,
dst,
UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE
);
} else {
// "false && a", "true || a"
this.compileBoolean(
bo.lhs,
dst,
UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE
);
this.fakeCompile(bo.rhs);
}
return;
}
Object rhsCv = this.getConstantValue(bo.rhs);
if (rhsCv instanceof Boolean) {
if (((Boolean) rhsCv).booleanValue() ^ bo.op == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality
// "a && true", "a || false"
this.compileBoolean(
bo.lhs,
dst,
UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE
);
} else {
// "a && false", "a || true"
this.pop(bo.lhs, this.compileGetValue(bo.lhs));
this.compileBoolean(
bo.rhs,
dst,
UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE
);
}
return;
}
// SUPPRESS CHECKSTYLE StringLiteralEquality
if (bo.op == "||" ^ orientation == UnitCompiler.JUMP_IF_FALSE) {
this.compileBoolean(bo.lhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE);
this.compileBoolean(bo.rhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE);
} else {
CodeContext.Offset end = this.codeContext.new Offset();
this.compileBoolean(bo.lhs, end, UnitCompiler.JUMP_IF_FALSE ^ orientation == UnitCompiler.JUMP_IF_FALSE); // SUPPRESS CHECKSTYLE LineLength
this.compileBoolean(bo.rhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE);
end.set();
}
return;
}
if (
bo.op == "==" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "!=" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "<=" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == ">=" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "<" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == ">" // SUPPRESS CHECKSTYLE StringLiteralEquality
) {
int opIdx = (
bo.op == "==" ? 0 : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.op == "!=" ? 1 : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.op == "<" ? 2 : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.op == ">=" ? 3 : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.op == ">" ? 4 : // SUPPRESS CHECKSTYLE StringLiteralEquality
bo.op == "<=" ? 5 : // SUPPRESS CHECKSTYLE StringLiteralEquality
Integer.MIN_VALUE
);
if (orientation == UnitCompiler.JUMP_IF_FALSE) opIdx ^= 1;
// Comparison with "null".
{
boolean lhsIsNull = this.getConstantValue(bo.lhs) == null;
boolean rhsIsNull = this.getConstantValue(bo.rhs) == null;
if (lhsIsNull || rhsIsNull) {
if (bo.op != "==" && bo.op != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileError(
"Operator \"" + bo.op + "\" not allowed on operand \"null\"",
bo.getLocation()
);
}
if (!lhsIsNull) {
// x == null
IClass lhsType = this.compileGetValue(bo.lhs);
if (lhsType.isPrimitive()) {
this.compileError(
"Cannot compare primitive type \"" + lhsType.toString() + "\" with \"null\"",
bo.getLocation()
);
}
} else
if (!rhsIsNull) {
// null == x
IClass rhsType = this.compileGetValue(bo.rhs);
if (rhsType.isPrimitive()) {
this.compileError(
"Cannot compare \"null\" with primitive type \"" + rhsType.toString() + "\"",
bo.getLocation()
);
}
} else
{
// null == null
this.pushConstant(bo, null);
}
this.writeBranch(bo, Opcode.IFNULL + opIdx, dst);
return;
}
}
IClass lhsType = this.compileGetValue(bo.lhs);
CodeContext.Inserter convertLhsInserter = this.codeContext.newInserter();
IClass rhsType = this.compileGetValue(bo.rhs);
// 15.20.1 Numerical comparison.
if (
this.getUnboxedType(lhsType).isPrimitiveNumeric()
&& this.getUnboxedType(rhsType).isPrimitiveNumeric()
&& !(
(bo.op == "==" || bo.op == "!=") // SUPPRESS CHECKSTYLE StringLiteralEquality
&& !lhsType.isPrimitive()
&& !rhsType.isPrimitive()
)
) {
IClass promotedType = this.binaryNumericPromotion(bo, lhsType, convertLhsInserter, rhsType);
if (promotedType == IClass.INT) {
this.writeBranch(bo, Opcode.IF_ICMPEQ + opIdx, dst);
} else
if (promotedType == IClass.LONG) {
this.writeOpcode(bo, Opcode.LCMP);
this.writeBranch(bo, Opcode.IFEQ + opIdx, dst);
} else
if (promotedType == IClass.FLOAT) {
if (bo.op == ">" || bo.op == ">=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.writeOpcode(bo, Opcode.FCMPL);
} else {
this.writeOpcode(bo, Opcode.FCMPG);
}
this.writeBranch(bo, Opcode.IFEQ + opIdx, dst);
} else
if (promotedType == IClass.DOUBLE) {
if (bo.op == ">" || bo.op == ">=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.writeOpcode(bo, Opcode.DCMPL);
} else {
this.writeOpcode(bo, Opcode.DCMPG);
}
this.writeBranch(bo, Opcode.IFEQ + opIdx, dst);
} else
{
throw new JaninoRuntimeException("Unexpected promoted type \"" + promotedType + "\"");
}
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.op != "==" && bo.op != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileError("Operator \"" + bo.op + "\" not allowed on boolean operands", bo.getLocation());
}
IClassLoader icl = this.iClassLoader;
// Unbox LHS if necessary.
if (lhsType == icl.TYPE_java_lang_Boolean) {
this.codeContext.pushInserter(convertLhsInserter);
try {
this.unboxingConversion(bo, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN);
} finally {
this.codeContext.popInserter();
}
}
// Unbox RHS if necessary.
if (rhsType == icl.TYPE_java_lang_Boolean) {
this.unboxingConversion(bo, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN);
}
this.writeBranch(bo, Opcode.IF_ICMPEQ + opIdx, dst);
return;
}
// Reference comparison.
// Note: Comparison with "null" is already handled above.
if (!lhsType.isPrimitive() && !rhsType.isPrimitive()) {
if (bo.op != "==" && bo.op != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.compileError("Operator \"" + bo.op + "\" 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.writeBranch(bo, Opcode.IF_ACMPEQ + 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 size of the context on the operand stack
*/
private int
compileContext(Rvalue rv) throws CompileException {
final int[] res = new int[1];
RvalueVisitor rvv = new RvalueVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitArrayLength(ArrayLength al) { try { res[0] = UnitCompiler.this.compileContext2(al); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAssignment(Assignment a) { res[0] = UnitCompiler.this.compileContext2(a); }
@Override public void visitUnaryOperation(UnaryOperation uo) { res[0] = UnitCompiler.this.compileContext2(uo); }
@Override public void visitBinaryOperation(BinaryOperation bo) { res[0] = UnitCompiler.this.compileContext2(bo); }
@Override public void visitCast(Cast c) { res[0] = UnitCompiler.this.compileContext2(c); }
@Override public void visitClassLiteral(ClassLiteral cl) { res[0] = UnitCompiler.this.compileContext2(cl); }
@Override public void visitConditionalExpression(ConditionalExpression ce) { res[0] = UnitCompiler.this.compileContext2(ce); }
@Override public void visitCrement(Crement c) { res[0] = UnitCompiler.this.compileContext2(c); }
@Override public void visitInstanceof(Instanceof io) { res[0] = UnitCompiler.this.compileContext2(io); }
@Override public void visitMethodInvocation(MethodInvocation mi) { res[0] = UnitCompiler.this.compileContext2(mi); }
@Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.compileContext2(smi); }
@Override public void visitIntegerLiteral(IntegerLiteral il) { res[0] = UnitCompiler.this.compileContext2(il); }
@Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { res[0] = UnitCompiler.this.compileContext2(fpl); }
@Override public void visitBooleanLiteral(BooleanLiteral bl) { res[0] = UnitCompiler.this.compileContext2(bl); }
@Override public void visitCharacterLiteral(CharacterLiteral cl) { res[0] = UnitCompiler.this.compileContext2(cl); }
@Override public void visitStringLiteral(StringLiteral sl) { res[0] = UnitCompiler.this.compileContext2(sl); }
@Override public void visitNullLiteral(NullLiteral nl) { res[0] = UnitCompiler.this.compileContext2(nl); }
@Override public void visitSimpleConstant(SimpleConstant sl) { res[0] = UnitCompiler.this.compileContext2(sl); }
@Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.compileContext2(naci); }
@Override public void visitNewArray(NewArray na) { res[0] = UnitCompiler.this.compileContext2(na); }
@Override public void visitNewInitializedArray(NewInitializedArray nia) { res[0] = UnitCompiler.this.compileContext2(nia); }
@Override public void visitNewClassInstance(NewClassInstance nci) { res[0] = UnitCompiler.this.compileContext2(nci); }
@Override public void visitParameterAccess(ParameterAccess pa) { res[0] = UnitCompiler.this.compileContext2(pa); }
@Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { res[0] = UnitCompiler.this.compileContext2(qtr); }
@Override public void visitThisReference(ThisReference tr) { res[0] = UnitCompiler.this.compileContext2(tr); }
@Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.compileContext2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.compileContext2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.compileContext2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.compileContext2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.compileContext2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.compileContext2(lva); }
@Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.compileContext2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try {
rv.accept(rvv);
return res[0];
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
@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 (!this.compileGetValue(al.lhs).isArray()) {
this.compileError("Cannot determine length of non-array type", al.getLocation());
}
return 1;
}
private int
compileContext2(ArrayAccessExpression aae) throws CompileException {
IClass lhsType = this.compileGetValue(aae.lhs);
if (!lhsType.isArray()) {
this.compileError(
"Subscript not allowed on non-array type \"" + lhsType.toString() + "\"",
aae.getLocation()
);
}
IClass indexType = this.compileGetValue(aae.index);
if (
!this.tryIdentityConversion(indexType, IClass.INT)
&& !this.tryWideningPrimitiveConversion(aae, indexType, IClass.INT)
) this.compileError(
"Index expression of type \"" + indexType + "\" cannot be widened to \"int\"",
aae.getLocation()
);
return 2;
}
private int
compileContext2(FieldAccessExpression fae) throws CompileException {
this.determineValue(fae);
return this.compileContext(fae.value);
}
private int
compileContext2(SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
return this.compileContext(scfae.value);
}
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 IClass
compileGet(Rvalue rv) throws CompileException {
final IClass[] res = new IClass[1];
RvalueVisitor rvv = new RvalueVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitArrayLength(ArrayLength al) { res[0] = UnitCompiler.this.compileGet2(al); }
@Override public void visitAssignment(Assignment a) { try { res[0] = UnitCompiler.this.compileGet2(a); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitUnaryOperation(UnaryOperation uo) { try { res[0] = UnitCompiler.this.compileGet2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBinaryOperation(BinaryOperation bo) { try { res[0] = UnitCompiler.this.compileGet2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCast(Cast c) { try { res[0] = UnitCompiler.this.compileGet2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitClassLiteral(ClassLiteral cl) { try { res[0] = UnitCompiler.this.compileGet2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitConditionalExpression(ConditionalExpression ce) { try { res[0] = UnitCompiler.this.compileGet2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCrement(Crement c) { try { res[0] = UnitCompiler.this.compileGet2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitInstanceof(Instanceof io) { try { res[0] = UnitCompiler.this.compileGet2(io); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitMethodInvocation(MethodInvocation mi) { try { res[0] = UnitCompiler.this.compileGet2(mi); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { res[0] = UnitCompiler.this.compileGet2(smi); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitIntegerLiteral(IntegerLiteral il) { try { res[0] = UnitCompiler.this.compileGet2(il); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { res[0] = UnitCompiler.this.compileGet2(fpl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBooleanLiteral(BooleanLiteral bl) { try { res[0] = UnitCompiler.this.compileGet2(bl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCharacterLiteral(CharacterLiteral cl) { try { res[0] = UnitCompiler.this.compileGet2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitStringLiteral(StringLiteral sl) { try { res[0] = UnitCompiler.this.compileGet2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNullLiteral(NullLiteral nl) { try { res[0] = UnitCompiler.this.compileGet2(nl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSimpleConstant(SimpleConstant sl) { try { res[0] = UnitCompiler.this.compileGet2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { try { res[0] = UnitCompiler.this.compileGet2(naci); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewArray(NewArray na) { try { res[0] = UnitCompiler.this.compileGet2(na); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewInitializedArray(NewInitializedArray nia) { try { res[0] = UnitCompiler.this.compileGet2(nia); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitNewClassInstance(NewClassInstance nci) { try { res[0] = UnitCompiler.this.compileGet2(nci); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitParameterAccess(ParameterAccess pa) { try { res[0] = UnitCompiler.this.compileGet2(pa); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { res[0] = UnitCompiler.this.compileGet2(qtr); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitThisReference(ThisReference tr) { try { res[0] = UnitCompiler.this.compileGet2(tr); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.compileGet2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.compileGet2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.compileGet2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.compileGet2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.compileGet2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.compileGet2(lva); }
@Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.compileGet2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try {
rv.accept(rvv);
return res[0];
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
private IClass
compileGet2(BooleanRvalue brv) throws CompileException {
CodeContext.Offset isTrue = this.codeContext.new Offset();
this.compileBoolean(brv, isTrue, UnitCompiler.JUMP_IF_TRUE);
this.writeOpcode(brv, Opcode.ICONST_0);
CodeContext.Offset end = this.codeContext.new Offset();
this.writeBranch(brv, Opcode.GOTO, end);
isTrue.set();
this.writeOpcode(brv, Opcode.ICONST_1);
end.set();
return IClass.BOOLEAN;
}
private IClass
compileGet2(AmbiguousName an) throws CompileException {
return this.compileGet(this.toRvalueOrCompileException(this.reclassify(an)));
}
private IClass
compileGet2(LocalVariableAccess lva) { return this.load(lva, lva.localVariable); }
private IClass
compileGet2(FieldAccess fa) throws CompileException {
this.checkAccessible(fa.field, fa.getEnclosingBlockStatement());
this.getfield(fa, fa.field);
return fa.field.getType();
}
private IClass
compileGet2(ArrayLength al) {
this.writeOpcode(al, Opcode.ARRAYLENGTH);
return IClass.INT;
}
private IClass
compileGet2(ThisReference tr) throws CompileException {
this.referenceThis(tr);
return this.getIClass(tr);
}
private IClass
compileGet2(QualifiedThisReference qtr) throws CompileException {
this.referenceThis(
qtr, // locatable
this.getDeclaringClass(qtr), // declaringClass
this.getDeclaringTypeBodyDeclaration(qtr), // declaringTypeBodyDeclaration
this.getTargetIClass(qtr) // targetIClass
);
return this.getTargetIClass(qtr);
}
private IClass
compileGet2(ClassLiteral cl) throws CompileException {
final Location loc = cl.getLocation();
final IClassLoader icl = this.iClassLoader;
IClass iClass = this.getType(cl.type);
if (iClass.isPrimitive()) {
// Primitive class literal.
this.writeOpcode(cl, Opcode.GETSTATIC);
String wrapperClassDescriptor = (
iClass == IClass.VOID ? "Ljava/lang/Void;" :
iClass == IClass.BYTE ? "Ljava/lang/Byte;" :
iClass == IClass.CHAR ? "Ljava/lang/Character;" :
iClass == IClass.DOUBLE ? "Ljava/lang/Double;" :
iClass == IClass.FLOAT ? "Ljava/lang/Float;" :
iClass == IClass.INT ? "Ljava/lang/Integer;" :
iClass == IClass.LONG ? "Ljava/lang/Long;" :
iClass == IClass.SHORT ? "Ljava/lang/Short;" :
iClass == IClass.BOOLEAN ? "Ljava/lang/Boolean;" :
null
);
if (wrapperClassDescriptor == null) {
throw new JaninoRuntimeException("SNO: Unidentifiable primitive type \"" + iClass + "\"");
}
this.writeConstantFieldrefInfo(
wrapperClassDescriptor, // classFD
"TYPE", // fieldName
"Ljava/lang/Class;" // fieldFD
);
return icl.TYPE_java_lang_Class;
}
// Non-primitive class literal.
TypeDeclaration declaringType;
for (Scope s = cl.getEnclosingBlockStatement();; s = s.getEnclosingScope()) {
if (s instanceof TypeDeclaration) {
declaringType = (AbstractTypeDeclaration) s;
break;
}
}
// Check if synthetic method "static Class class$(String className)" is already declared.
if (declaringType.getMethodDeclaration("class$") == null) this.declareClassDollarMethod(cl);
// Determine the statics of the declaring class (this is where static fields declarations are found).
List extends BlockStatement> statics;
if (declaringType instanceof ClassDeclaration) {
statics = ((ClassDeclaration) declaringType).variableDeclaratorsAndInitializers;
} else
if (declaringType instanceof InterfaceDeclaration) {
statics = ((InterfaceDeclaration) declaringType).constantDeclarations;
} else {
throw new JaninoRuntimeException(
"SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration"
);
}
String className = Descriptor.toClassName(iClass.getDescriptor());
// Compose the "class-dollar" field name. This i done as follows:
// Type Class-name Field-name
// String java.lang.String class$java$lang$String
// String[] [Ljava.lang.String; array$Ljava$lang$String
// String[][] [[Ljava.lang.String; array$$Ljava$lang$String
// String[][][] [[[java.lang.String; array$$$Ljava$lang$String
// int[] [I array$I
// int[][] [[I array$$I
String classDollarFieldName;
{
if (className.startsWith("[")) {
classDollarFieldName = "array" + className.replace('.', '$').replace('[', '$');
if (classDollarFieldName.endsWith(";")) {
classDollarFieldName = classDollarFieldName.substring(0, classDollarFieldName.length() - 1);
}
} else
{
classDollarFieldName = "class$" + className.replace('.', '$');
}
}
// Declare the static "class dollar field" if not already done.
ADD_CLASS_DOLLAR_FIELD: {
for (BlockStatement bs : statics) {
if (!((TypeBodyDeclaration) bs).isStatic()) continue;
if (bs instanceof FieldDeclaration) {
for (VariableDeclarator vd : ((FieldDeclaration) bs).variableDeclarators) {
if (vd.name.equals(classDollarFieldName)) {
break ADD_CLASS_DOLLAR_FIELD;
}
}
}
}
Type classType = new SimpleType(loc, icl.TYPE_java_lang_Class);
FieldDeclaration fd = new FieldDeclaration(
loc, // location
null, // optionalDocComment
new Modifiers(Mod.STATIC), // modifiers
classType, // type
new VariableDeclarator[] { // variableDeclarators
new VariableDeclarator(
loc, // location
classDollarFieldName, // name
0, // brackets
(Rvalue) null // optionalInitializer
)
}
);
if (declaringType instanceof ClassDeclaration) {
((ClassDeclaration) declaringType).addFieldDeclaration(fd);
} else
if (declaringType instanceof InterfaceDeclaration) {
((InterfaceDeclaration) declaringType).addConstantDeclaration(fd);
} else {
throw new JaninoRuntimeException(
"SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration"
);
}
}
// return (class$X != null) ? class$X : (class$X = class$("X"));
Type declaringClassOrInterfaceType = new SimpleType(loc, this.resolve(declaringType));
Lvalue classDollarFieldAccess = new FieldAccessExpression(
loc, // location
declaringClassOrInterfaceType, // lhs
classDollarFieldName // fieldName
);
ConditionalExpression ce = new ConditionalExpression(
loc, // location
new BinaryOperation( // lhs
loc, // location
classDollarFieldAccess, // lhs
"!=", // op
new NullLiteral(loc, "null") // rhs
),
classDollarFieldAccess, // mhs
new Assignment( // rhs
loc, // location
classDollarFieldAccess, // lhs
"=", // operator
new MethodInvocation( // rhs
loc, // location
declaringClassOrInterfaceType, // optionalTarget
"class$", // methodName
new Rvalue[] { // arguments
new StringLiteral(
loc, // location
'"' + className + '"' // constantValue
)
}
)
)
);
ce.setEnclosingBlockStatement(cl.getEnclosingBlockStatement());
return this.compileGet(ce);
}
private IClass
compileGet2(Assignment a) throws CompileException {
if (a.operator == "=") { // SUPPRESS CHECKSTYLE StringLiteralEquality
int lhsCs = this.compileContext(a.lhs);
IClass rhsType = this.compileGetValue(a.rhs);
IClass lhsType = this.getType(a.lhs);
Object rhsCv = this.getConstantValue(a.rhs);
this.assignmentConversion(a, rhsType, lhsType, rhsCv);
this.dupx(a, lhsType, lhsCs);
this.compileSet(a.lhs);
return lhsType;
}
// Implement "|= ^= &= *= /= %= += -= <<= >>= >>>=".
int lhsCs = this.compileContext(a.lhs);
this.dup(a, lhsCs);
IClass lhsType = this.compileGet(a.lhs);
IClass 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 JaninoRuntimeException("SNO: \"" + a.operator + "\" reconversion failed");
this.dupx(a, lhsType, lhsCs);
this.compileSet(a.lhs);
return lhsType;
}
private IClass
compileGet2(ConditionalExpression ce) throws CompileException {
IClass mhsType, rhsType;
CodeContext.Inserter mhsConvertInserter, rhsConvertInserter;
CodeContext.Offset toEnd = this.codeContext.new Offset();
{
Object cv = this.getConstantValue(ce.lhs);
if (cv instanceof Boolean) {
if (((Boolean) cv).booleanValue()) {
mhsType = this.compileGetValue(ce.mhs);
mhsConvertInserter = this.codeContext.newInserter();
rhsType = this.getType(ce.rhs);
rhsConvertInserter = null;
} else {
mhsType = this.getType(ce.mhs);
mhsConvertInserter = null;
rhsType = this.compileGetValue(ce.rhs);
rhsConvertInserter = this.codeContext.currentInserter();
}
} else {
CodeContext.Offset toRhs = this.codeContext.new Offset();
this.compileBoolean(ce.lhs, toRhs, UnitCompiler.JUMP_IF_FALSE);
mhsType = this.compileGetValue(ce.mhs);
mhsConvertInserter = this.codeContext.newInserter();
this.writeBranch(ce, Opcode.GOTO, toEnd);
toRhs.set();
rhsType = this.compileGetValue(ce.rhs);
rhsConvertInserter = this.codeContext.currentInserter();
}
}
IClass expressionType;
if (mhsType == rhsType) {
// JLS7 15.25, list 1, bullet 1: "b ? T : T => T"
expressionType = mhsType;
} else
if (this.tryUnboxingConversion(ce.mhs, mhsType, rhsType, mhsConvertInserter)) {
// JLS7 15.25, list 1, bullet 2: "b ? Integer : int => int"
expressionType = rhsType;
} else
if (this.tryUnboxingConversion(ce.rhs, rhsType, mhsType, rhsConvertInserter)) {
// JLS7 15.25, list 1, bullet 2: "b ? int : Integer => int"
expressionType = mhsType;
} else
if (this.getConstantValue(ce.mhs) == null && !rhsType.isPrimitive()) {
// JLS7 15.25, list 1, bullet 3: "b ? null : ReferenceType => ReferenceType"
expressionType = rhsType;
} else
if (!mhsType.isPrimitive() && this.getConstantValue(ce.rhs) == null) {
// JLS7 15.25, list 1, bullet 3: "b ? ReferenceType : null => ReferenceType"
expressionType = mhsType;
} else
if (this.isConvertibleToPrimitiveNumeric(mhsType) && this.isConvertibleToPrimitiveNumeric(rhsType)) {
// TODO JLS7 15.25, list 1, bullet 4, bullet 1: "b ? Byte : Short => short"
// TODO JLS7 15.25, list 1, bullet 4, bullet 2: "b ? 127 : byte => byte"
// TODO JLS7 15.25, list 1, bullet 4, bullet 3: "b ? 127 : byte => byte"
// JLS7 15.25, list 1, bullet 4, bullet 4: "b ? Integer : Double => double"
expressionType = this.binaryNumericPromotion(
ce, // locatable
mhsType, // type1
mhsConvertInserter, // convertInserter1
rhsType, // type2
rhsConvertInserter // convertInserter2
);
} else
if (!mhsType.isPrimitive() && !rhsType.isPrimitive()) {
// JLS7 15.25, list 1, bullet 5: "b ? Base : Derived => Base"
if (mhsType.isAssignableFrom(rhsType)) {
expressionType = mhsType;
} else
if (rhsType.isAssignableFrom(mhsType)) {
expressionType = rhsType;
} else
{
this.compileError(
"Reference types \"" + mhsType + "\" and \"" + rhsType + "\" don't match",
ce.getLocation()
);
return this.iClassLoader.TYPE_java_lang_Object;
}
} else
{
this.compileError(
"Incompatible expression types \"" + mhsType + "\" and \"" + rhsType + "\"",
ce.getLocation()
);
return this.iClassLoader.TYPE_java_lang_Object;
}
toEnd.set();
return expressionType;
}
private IClass
compileGet2(Crement c) throws CompileException {
// Optimized crement of integer local variable.
LocalVariable lv = this.isIntLv(c);
if (lv != null) {
if (!c.pre) this.load(c, lv);
this.compileLocalVariableCrement(c, lv);
if (c.pre) this.load(c, lv);
return lv.type;
}
// Compile operand context.
int cs = this.compileContext(c.operand);
// DUP operand context.
this.dup(c, cs);
// Get operand value.
IClass type = this.compileGet(c.operand);
// DUPX operand value.
if (!c.pre) this.dupx(c, type, cs);
// Apply "unary numeric promotion".
IClass promotedType = this.unaryNumericPromotion(c, type);
// Crement.
this.writeOpcode(c, UnitCompiler.ilfd(
promotedType,
Opcode.ICONST_1,
Opcode.LCONST_1,
Opcode.FCONST_1,
Opcode.DCONST_1
));
if (c.operator == "++") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.writeOpcode(c, Opcode.IADD + UnitCompiler.ilfd(promotedType));
} else
if (c.operator == "--") { // SUPPRESS CHECKSTYLE StringLiteralEquality
this.writeOpcode(c, Opcode.ISUB + UnitCompiler.ilfd(promotedType));
} else {
this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation());
}
this.reverseUnaryNumericPromotion(c, promotedType, type);
// DUPX cremented operand value.
if (c.pre) this.dupx(c, type, cs);
// Set operand.
this.compileSet(c.operand);
return type;
}
private void
compileLocalVariableCrement(Crement c, LocalVariable lv) {
this.crement(c, lv, c.operator);
}
private void
crement(Locatable locatable, LocalVariable lv, String operator) {
if (lv.getSlotIndex() > 255) {
this.writeOpcode(locatable, Opcode.WIDE);
this.writeOpcode(locatable, Opcode.IINC);
this.writeShort(lv.getSlotIndex());
this.writeShort(operator == "++" ? 1 : -1); // SUPPRESS CHECKSTYLE StringLiteralEquality
} else {
this.writeOpcode(locatable, Opcode.IINC);
this.writeByte(lv.getSlotIndex());
this.writeByte(operator == "++" ? 1 : -1); // SUPPRESS CHECKSTYLE StringLiteralEquality
}
}
private IClass
compileGet2(ArrayAccessExpression aae) throws CompileException {
IClass lhsComponentType = this.getType(aae);
this.writeOpcode(aae, Opcode.IALOAD + UnitCompiler.ilfdabcs(lhsComponentType));
return lhsComponentType;
}
private IClass
compileGet2(FieldAccessExpression fae) throws CompileException {
this.determineValue(fae);
return this.compileGet(fae.value);
}
private IClass
compileGet2(SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
return this.compileGet(scfae.value);
}
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.getNegatedConstantValue(uo.operand);
if (ncv != UnitCompiler.NOT_CONSTANT) {
return this.unaryNumericPromotion(uo, this.pushConstant(uo, ncv));
}
}
IClass promotedType = this.unaryNumericPromotion(
uo,
this.convertToPrimitiveNumericType(uo, this.compileGetValue(uo.operand))
);
this.writeOpcode(uo, Opcode.INEG + UnitCompiler.ilfd(promotedType));
return promotedType;
}
if (uo.operator == "~") { // SUPPRESS CHECKSTYLE StringLiteralEquality
IClass operandType = this.compileGetValue(uo.operand);
IClass promotedType = this.unaryNumericPromotion(uo, operandType);
if (promotedType == IClass.INT) {
this.writeOpcode(uo, Opcode.ICONST_M1);
this.writeOpcode(uo, Opcode.IXOR);
return IClass.INT;
}
if (promotedType == IClass.LONG) {
this.writeOpcode(uo, Opcode.LDC2_W);
this.writeConstantLongInfo(-1L);
this.writeOpcode(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 {
IClass lhsType = this.compileGetValue(io.lhs);
IClass rhsType = this.getType(io.rhs);
if (
lhsType.isInterface() || rhsType.isInterface()
// We cannot precompute the result from type information as the value might be null, but we should detect
// when the instanceof is statically impossible.
|| lhsType.isAssignableFrom(rhsType)
|| rhsType.isAssignableFrom(lhsType)
) {
this.writeOpcode(io, Opcode.INSTANCEOF);
this.writeConstantClassInfo(rhsType.getDescriptor());
} else {
this.compileError("\"" + lhsType + "\" can never be an instance of \"" + rhsType + "\"", io.getLocation());
}
return IClass.BOOLEAN;
}
private IClass
compileGet2(BinaryOperation bo) throws CompileException {
if (
bo.op == "||" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "&&" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "==" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "!=" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "<" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == ">" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == "<=" // SUPPRESS CHECKSTYLE StringLiteralEquality
|| bo.op == ">=" // SUPPRESS CHECKSTYLE StringLiteralEquality
) {
// Eventually calls "compileBoolean()".
return this.compileGet2((BooleanRvalue) bo);
}
// Implements "| ^ & * / % + - << >> >>>".
return this.compileArithmeticOperation(
bo, // locatable
null, // type
bo.unrollLeftAssociation(), // operands
bo.op // operator
);
}
private IClass
compileGet2(Cast c) throws CompileException {
// JLS7 5.5 Casting Conversion.
IClass tt = this.getType(c.targetType);
IClass vt = this.compileGetValue(c.value);
if (
this.tryIdentityConversion(vt, tt)
|| this.tryWideningPrimitiveConversion(c, vt, tt)
|| this.tryNarrowingPrimitiveConversion(c, vt, tt)
|| this.tryWideningReferenceConversion(vt, tt)
|| this.tryNarrowingReferenceConversion(c, vt, tt)
|| this.tryBoxingConversion(c, vt, tt)
|| this.tryUnboxingConversion(c, vt, tt, null)
) 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 IClass
compileGet2(ParenthesizedExpression pe) throws CompileException {
return this.compileGet(pe.value);
}
private IClass
compileGet2(MethodInvocation mi) throws CompileException {
IClass.IMethod iMethod = this.findIMethod(mi);
if (mi.optionalTarget == null) {
// JLS7 6.5.7.1, 15.12.4.1.1.1
TypeBodyDeclaration scopeTbd;
ClassDeclaration scopeClassDeclaration;
{
Scope s;
for (
s = mi.getEnclosingBlockStatement();
!(s instanceof TypeBodyDeclaration);
s = s.getEnclosingScope()
);
scopeTbd = (TypeBodyDeclaration) s;
if (!(s instanceof ClassDeclaration)) s = s.getEnclosingScope();
scopeClassDeclaration = (ClassDeclaration) 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 (scopeTbd.isStatic()) {
this.compileError(
"Instance method \"" + iMethod.toString() + "\" cannot be invoked in static context",
mi.getLocation()
);
}
this.referenceThis(
mi, // locatable
scopeClassDeclaration, // declaringClass
scopeTbd, // declaringTypeBodyDeclaration
iMethod.getDeclaringIClass() // targetIClass
);
}
} else {
// 6.5.7.2
boolean staticContext = this.isType(mi.optionalTarget);
if (staticContext) {
this.getType(this.toTypeOrCompileException(mi.optionalTarget));
} else
{
this.compileGetValue(this.toRvalueOrCompileException(mi.optionalTarget));
}
if (iMethod.isStatic()) {
if (!staticContext) {
// JLS7 15.12.4.1.2.1
this.pop(mi.optionalTarget, this.getType(mi.optionalTarget));
}
} else {
if (staticContext) {
this.compileError(
"Instance method \"" + mi.methodName + "\" cannot be invoked in static context",
mi.getLocation()
);
}
}
}
// 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]) // optionalConstantValue
);
}
// Invoke!
this.checkAccessible(iMethod, mi.getEnclosingBlockStatement());
if (iMethod.getDeclaringIClass().isInterface()) {
this.invoke(mi, iMethod);
} else {
if (!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.writeOpcode(mi, Opcode.INVOKESTATIC);
this.writeConstantMethodrefInfo(
iMethod.getDeclaringIClass().getDescriptor(), // classFD
iMethod.getName() + '$', // methodName
MethodDescriptor.prependParameter( // methodMD
iMethod.getDescriptor(),
iMethod.getDeclaringIClass().getDescriptor()
)
);
} else
{
this.invoke(mi, iMethod);
}
}
return iMethod.getReturnType();
}
private IClass
compileGet2(SuperclassMethodInvocation scmi) throws CompileException {
final IClass.IMethod iMethod = this.findIMethod(scmi);
Scope s;
for (
s = scmi.getEnclosingBlockStatement();
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 (Mod.isStatic(fd.modifiers.flags)) {
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]) // optionalConstantValue
);
}
// Invoke!
this.writeOpcode(scmi, Opcode.INVOKESPECIAL);
this.writeConstantMethodrefInfo(
iMethod.getDeclaringIClass().getDescriptor(), // classFD
scmi.methodName, // methodName
iMethod.getDescriptor() // methodMD
);
return iMethod.getReturnType();
}
private IClass
compileGet2(NewClassInstance nci) throws CompileException {
if (nci.iClass == null) nci.iClass = this.getType(nci.type);
this.writeOpcode(nci, Opcode.NEW);
this.writeConstantClassInfo(nci.iClass.getDescriptor());
this.writeOpcode(nci, Opcode.DUP);
if (nci.iClass.isInterface()) this.compileError("Cannot instantiate \"" + nci.iClass + "\"", nci.getLocation());
this.checkAccessible(nci.iClass, nci.getEnclosingBlockStatement());
if (nci.iClass.isAbstract()) {
this.compileError("Cannot instantiate abstract \"" + nci.iClass + "\"", nci.getLocation());
}
// Determine the enclosing instance for the new object.
Rvalue optionalEnclosingInstance;
if (nci.optionalQualification != null) {
if (nci.iClass.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).
optionalEnclosingInstance = nci.optionalQualification;
} else {
Scope s = nci.getEnclosingBlockStatement();
for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope());
TypeBodyDeclaration enclosingTypeBodyDeclaration = (TypeBodyDeclaration) s;
TypeDeclaration enclosingTypeDeclaration = (TypeDeclaration) s.getEnclosingScope();
if (
!(enclosingTypeDeclaration instanceof ClassDeclaration)
|| 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 (nci.iClass.getOuterIClass() != null) {
this.compileError(
"Instantiation of \"" + nci.type + "\" requires an enclosing instance",
nci.getLocation()
);
}
optionalEnclosingInstance = null;
} else
{
// Determine the type of the enclosing instance for the new object.
IClass optionalOuterIClass = nci.iClass.getDeclaringIClass();
if (optionalOuterIClass == null) {
// No enclosing instance needed for a top-level class object.
optionalEnclosingInstance = 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).
optionalEnclosingInstance = new QualifiedThisReference(
nci.getLocation(), // location
new SimpleType( // qualification
nci.getLocation(),
optionalOuterIClass
)
);
optionalEnclosingInstance.setEnclosingBlockStatement(nci.getEnclosingBlockStatement());
}
}
}
this.invokeConstructor(
nci, // l
nci.getEnclosingBlockStatement(), // scope
optionalEnclosingInstance, // optionalEnclosingInstance
nci.iClass, // targetClass
nci.arguments // arguments
);
return nci.iClass;
}
private IClass
compileGet2(NewAnonymousClassInstance naci) throws CompileException {
// Find constructors.
AnonymousClassDeclaration acd = naci.anonymousClassDeclaration;
IClass sc = this.resolve(acd).getSuperclass();
IClass.IConstructor[] iConstructors = sc.getDeclaredIConstructors();
if (iConstructors.length == 0) throw new JaninoRuntimeException("SNO: Base class has no constructors");
// Determine most specific constructor.
IClass.IConstructor iConstructor = (IClass.IConstructor) this.findMostSpecificIInvocable(
naci, // locatable
iConstructors, // iInvocables
naci.arguments, // arguments
acd // contextScope
);
IClass[] pts = iConstructor.getParameterTypes();
// Determine formal parameters of anonymous constructor.
FormalParameters parameters;
Location loc = naci.getLocation();
{
List l = new ArrayList();
// Pass the enclosing instance of the base class as parameter #1.
if (naci.optionalQualification != null) l.add(new FormalParameter(
loc, // location
true, // finaL
new SimpleType(loc, this.getType(naci.optionalQualification)), // type
"this$base" // name
));
for (int i = 0; i < pts.length; ++i) l.add(new FormalParameter(
loc, // location
true, // finaL
new SimpleType(loc, pts[i]), // type
"p" + i // name
));
parameters = new FormalParameters(
loc,
(FormalParameter[]) l.toArray(new FormalParameter[l.size()]),
false
);
}
// Determine thrown exceptions of anonymous constructor.
IClass[] tes = iConstructor.getThrownExceptions();
Type[] tets = new Type[tes.length];
for (int i = 0; i < tes.length; ++i) tets[i] = new SimpleType(loc, tes[i]);
// The anonymous constructor merely invokes the constructor of its superclass.
int j = 0;
Rvalue optionalQualificationAccess;
if (naci.optionalQualification == null) {
optionalQualificationAccess = null;
} else
{
optionalQualificationAccess = new ParameterAccess(loc, parameters.parameters[j++]);
}
Rvalue[] parameterAccesses = new Rvalue[pts.length];
for (int i = 0; i < pts.length; ++i) {
parameterAccesses[i] = new ParameterAccess(loc, parameters.parameters[j++]);
}
// Generate the anonymous constructor for the anonymous class (JLS7 15.9.5.1).
ConstructorDeclarator anonymousConstructor = new ConstructorDeclarator(
loc, // location
null, // optionalDocComment
new Modifiers(Mod.PACKAGE), // modifiers
parameters, // parameters
tets, // thrownExceptions
new SuperConstructorInvocation( // optionalConstructorInvocation
loc, // location
optionalQualificationAccess, // optionalQualification
parameterAccesses // arguments
),
Collections.EMPTY_LIST // optionalStatements
);
// Compile the anonymous class.
acd.addConstructor(anonymousConstructor);
try {
this.compile(acd);
// Instantiate the anonymous class.
this.writeOpcode(naci, Opcode.NEW);
this.writeConstantClassInfo(this.resolve(naci.anonymousClassDeclaration).getDescriptor());
// TODO: adjust argument (for varargs case ?)
// Invoke the anonymous constructor.
this.writeOpcode(naci, Opcode.DUP);
Rvalue[] arguments2;
if (naci.optionalQualification == null) {
arguments2 = naci.arguments;
} else {
arguments2 = new Rvalue[naci.arguments.length + 1];
arguments2[0] = naci.optionalQualification;
System.arraycopy(naci.arguments, 0, arguments2, 1, naci.arguments.length);
}
// Adjust if needed.
// TODO: Not doing this now because we don't need vararg-annonymous 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.getEnclosingBlockStatement();
!(s instanceof TypeBodyDeclaration);
s = s.getEnclosingScope()
);
ThisReference oei;
if (((TypeBodyDeclaration) s).isStatic()) {
oei = null;
} else
{
oei = new ThisReference(loc);
oei.setEnclosingBlockStatement(naci.getEnclosingBlockStatement());
}
this.invokeConstructor(
naci, // locatable
naci.getEnclosingBlockStatement(), // scope
oei, // optionalEnclosingInstance
this.resolve(naci.anonymousClassDeclaration), // targetClass
arguments2 // arguments
);
} 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 this.resolve(naci.anonymousClassDeclaration);
}
private IClass
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) {
IClass 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 IClass
compileGet2(NewInitializedArray nia) throws CompileException {
IClass at = nia.arrayType == null ? nia.arrayIClass : this.getType(nia.arrayType);
this.compileGetValue(nia.arrayInitializer, at);
return at;
}
private void
compileGetValue(ArrayInitializer ai, IClass arrayType) throws CompileException {
if (!arrayType.isArray()) {
this.compileError("Array initializer not allowed for non-array type \"" + arrayType.toString() + "\"");
}
IClass ct = arrayType.getComponentType();
this.pushConstant(ai, new Integer(ai.values.length));
this.newArray(
ai, // locatable
1, // dimExprCount
0, // dims
ct // componentType
);
for (int i = 0; i < ai.values.length; ++i) {
this.writeOpcode(ai, Opcode.DUP);
this.pushConstant(ai, new Integer(i));
ArrayInitializerOrRvalue aiorv = ai.values[i];
if (aiorv instanceof Rvalue) {
Rvalue rv = (Rvalue) aiorv;
this.assignmentConversion(
ai, // locatable
this.compileGetValue(rv), // sourceType
ct, // targetType
this.getConstantValue(rv) // optionalConstantValue
);
} else
if (aiorv instanceof ArrayInitializer) {
this.compileGetValue((ArrayInitializer) aiorv, ct);
} else
{
throw new JaninoRuntimeException(
"Unexpected array initializer or rvalue class " + aiorv.getClass().getName()
);
}
this.writeOpcode(ai, Opcode.IASTORE + UnitCompiler.ilfdabcs(ct));
}
}
private IClass
compileGet2(Literal l) throws CompileException {
return this.pushConstant(l, this.getConstantValue(l));
}
private IClass
compileGet2(SimpleConstant sl) throws CompileException {
return this.pushConstant(sl, sl.value);
}
/**
* Convenience function that calls {@link #compileContext(Rvalue)} and {@link #compileGet(Rvalue)}.
*
* @return The type of the Rvalue
*/
private IClass
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.pushConstant(rv, cv);
return this.getType(rv);
}
this.compileContext(rv);
return this.compileGet(rv);
}
// -------------------- 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.
*
* @return {@link #NOT_CONSTANT} iff the rvalue is not a constant value
*/
public final Object
getConstantValue(Rvalue rv) throws CompileException {
if (rv.constantValue != Rvalue.CONSTANT_VALUE_UNKNOWN) return rv.constantValue;
final Object[] res = new Object[1];
RvalueVisitor rvv = new RvalueVisitor() {
// CHECKSTYLE LineLengthCheck:OFF
@Override public void visitArrayLength(ArrayLength al) { res[0] = UnitCompiler.this.getConstantValue2(al); }
@Override public void visitAssignment(Assignment a) { res[0] = UnitCompiler.this.getConstantValue2(a); }
@Override public void visitUnaryOperation(UnaryOperation uo) { try { res[0] = UnitCompiler.this.getConstantValue2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBinaryOperation(BinaryOperation bo) { try { res[0] = UnitCompiler.this.getConstantValue2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCast(Cast c) { try { res[0] = UnitCompiler.this.getConstantValue2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitClassLiteral(ClassLiteral cl) { res[0] = UnitCompiler.this.getConstantValue2(cl); }
@Override public void visitConditionalExpression(ConditionalExpression ce) { try { res[0] = UnitCompiler.this.getConstantValue2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitCrement(Crement c) { res[0] = UnitCompiler.this.getConstantValue2(c); }
@Override public void visitInstanceof(Instanceof io) { res[0] = UnitCompiler.this.getConstantValue2(io); }
@Override public void visitMethodInvocation(MethodInvocation mi) { res[0] = UnitCompiler.this.getConstantValue2(mi); }
@Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.getConstantValue2(smi); }
@Override public void visitIntegerLiteral(IntegerLiteral il) { try { res[0] = UnitCompiler.this.getConstantValue2(il); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { res[0] = UnitCompiler.this.getConstantValue2(fpl); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitBooleanLiteral(BooleanLiteral bl) { res[0] = UnitCompiler.this.getConstantValue2(bl); }
@Override public void visitCharacterLiteral(CharacterLiteral cl) { res[0] = UnitCompiler.this.getConstantValue2(cl); }
@Override public void visitStringLiteral(StringLiteral sl) { res[0] = UnitCompiler.this.getConstantValue2(sl); }
@Override public void visitNullLiteral(NullLiteral nl) { res[0] = UnitCompiler.this.getConstantValue2(nl); }
@Override public void visitSimpleConstant(SimpleConstant sl) { res[0] = UnitCompiler.this.getConstantValue2(sl); }
@Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.getConstantValue2(naci); }
@Override public void visitNewArray(NewArray na) { res[0] = UnitCompiler.this.getConstantValue2(na); }
@Override public void visitNewInitializedArray(NewInitializedArray nia) { res[0] = UnitCompiler.this.getConstantValue2(nia); }
@Override public void visitNewClassInstance(NewClassInstance nci) { res[0] = UnitCompiler.this.getConstantValue2(nci); }
@Override public void visitParameterAccess(ParameterAccess pa) { res[0] = UnitCompiler.this.getConstantValue2(pa); }
@Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { res[0] = UnitCompiler.this.getConstantValue2(qtr); }
@Override public void visitThisReference(ThisReference tr) { res[0] = UnitCompiler.this.getConstantValue2(tr); }
@Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.getConstantValue2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { res[0] = UnitCompiler.this.getConstantValue2(aae); }
@Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.getConstantValue2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
@Override public void visitFieldAccessExpression(FieldAccessExpression fae) { res[0] = UnitCompiler.this.getConstantValue2(fae); }
@Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { res[0] = UnitCompiler.this.getConstantValue2(scfae); }
@Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.getConstantValue2(lva); }
@Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.getConstantValue2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } }
// CHECKSTYLE LineLengthCheck:ON
};
try {
rv.accept(rvv);
rv.constantValue = res[0];
return rv.constantValue;
} catch (UncheckedCompileException uce) {
throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause
}
}
@SuppressWarnings("static-method") private Object
getConstantValue2(Rvalue rv) { return UnitCompiler.NOT_CONSTANT; }
private Object
getConstantValue2(AmbiguousName an) throws CompileException {
return this.getConstantValue(this.toRvalueOrCompileException(this.reclassify(an)));
}
@SuppressWarnings("static-method") private Object
getConstantValue2(FieldAccess fa) throws CompileException {
return fa.field.getConstantValue();
}
private Object
getConstantValue2(UnaryOperation uo) throws CompileException {
if (uo.operator.equals("+")) return this.getConstantValue(uo.operand);
if (uo.operator.equals("-")) return this.getNegatedConstantValue(uo.operand);
if (uo.operator.equals("!")) {
Object cv = this.getConstantValue(uo.operand);
return (
cv == Boolean.TRUE ? Boolean.FALSE :
cv == Boolean.FALSE ? Boolean.TRUE :
UnitCompiler.NOT_CONSTANT
);
}
return UnitCompiler.NOT_CONSTANT;
}
private Object
getConstantValue2(ConditionalExpression ce) throws CompileException {
Object lhsValue = this.getConstantValue(ce.lhs);
if (lhsValue instanceof Boolean) {
return (
((Boolean) lhsValue).booleanValue()
? this.getConstantValue(ce.mhs)
: this.getConstantValue(ce.rhs)
);
}
return UnitCompiler.NOT_CONSTANT;
}
private Object
getConstantValue2(BinaryOperation bo) throws CompileException {
// "|", "^", "&", "*", "/", "%", "+", "-", "==", "!=".
if (
// CHECKSTYLE StringLiteralEquality:OFF
bo.op == "|"
|| bo.op == "^"
|| bo.op == "&"
|| bo.op == "*"
|| bo.op == "/"
|| bo.op == "%"
|| bo.op == "+"
|| bo.op == "-"
|| bo.op == "=="
|| bo.op == "!="
// CHECKSTYLE StringLiteralEquality:ON
) {
// Unroll the constant operands.
List