/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2010, 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 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 org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.Location;
import org.codehaus.janino.IClass.IField;
import org.codehaus.janino.IClass.IInvocable;
import org.codehaus.janino.IClass.IMethod;
import org.codehaus.janino.Java.AlternateConstructorInvocation;
import org.codehaus.janino.Java.AnonymousClassDeclaration;
import org.codehaus.janino.Java.ArrayInitializerOrRvalue;
import org.codehaus.janino.Java.Block;
import org.codehaus.janino.Java.BlockStatement;
import org.codehaus.janino.Java.BreakStatement;
import org.codehaus.janino.Java.CatchClause;
import org.codehaus.janino.Java.ConstructorDeclarator;
import org.codehaus.janino.Java.ConstructorInvocation;
import org.codehaus.janino.Java.ContinueStatement;
import org.codehaus.janino.Java.DoStatement;
import org.codehaus.janino.Java.EmptyStatement;
import org.codehaus.janino.Java.ExpressionStatement;
import org.codehaus.janino.Java.FieldAccess;
import org.codehaus.janino.Java.FieldDeclaration;
import org.codehaus.janino.Java.ForStatement;
import org.codehaus.janino.Java.FunctionDeclarator;
import org.codehaus.janino.Java.IfStatement;
import org.codehaus.janino.Java.Initializer;
import org.codehaus.janino.Java.Invocation;
import org.codehaus.janino.Java.LabeledStatement;
import org.codehaus.janino.Java.LocalClassDeclaration;
import org.codehaus.janino.Java.LocalClassDeclarationStatement;
import org.codehaus.janino.Java.LocalVariable;
import org.codehaus.janino.Java.LocalVariableDeclarationStatement;
import org.codehaus.janino.Java.LocalVariableSlot;
import org.codehaus.janino.Java.Locatable;
import org.codehaus.janino.Java.Located;
import org.codehaus.janino.Java.ReturnStatement;
import org.codehaus.janino.Java.Rvalue;
import org.codehaus.janino.Java.SimpleType;
import org.codehaus.janino.Java.Statement;
import org.codehaus.janino.Java.SuperConstructorInvocation;
import org.codehaus.janino.Java.SuperclassFieldAccessExpression;
import org.codehaus.janino.Java.SwitchStatement;
import org.codehaus.janino.Java.SynchronizedStatement;
import org.codehaus.janino.Java.ThrowStatement;
import org.codehaus.janino.Java.TryStatement;
import org.codehaus.janino.Java.TypeBodyDeclaration;
import org.codehaus.janino.Java.VariableDeclarator;
import org.codehaus.janino.Java.WhileStatement;
import org.codehaus.janino.Java.CompilationUnit.ImportDeclaration;
import org.codehaus.janino.Java.CompilationUnit.SingleStaticImportDeclaration;
import org.codehaus.janino.Java.CompilationUnit.SingleTypeImportDeclaration;
import org.codehaus.janino.Java.CompilationUnit.StaticImportOnDemandDeclaration;
import org.codehaus.janino.Java.CompilationUnit.TypeImportOnDemandDeclaration;
import org.codehaus.janino.Visitor.BlockStatementVisitor;
import org.codehaus.janino.Visitor.ImportVisitor;
import org.codehaus.janino.util.ClassFile;
/**
* This class actually implements the Java™ compiler. It is
* associated with exactly one compilation unit which it compiles.
*/
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 StringBuffer(a).append(b).append(c).append(d).toString()
*
* strategy is chosen.
*
* A very good article from Tom Gibara
*
* http://www.tomgibara.com/janino-evaluation/string-concatenation-benchmark
*
* analyzes the impact of this decision and recommends a value of three.
*/
private static final int STRING_CONCAT_LIMIT = 3;
public UnitCompiler(
Java.CompilationUnit compilationUnit,
IClassLoader iClassLoader
) throws CompileException {
this.compilationUnit = compilationUnit;
this.iClassLoader = iClassLoader;
try {
boolean allowStringBuilder = true;
String targetVersion = System.getProperty("Janino.TargetVersion");
if (targetVersion != null && Double.parseDouble(targetVersion) <= 1.4d) {
allowStringBuilder = false;
}
if ((iClassLoader.loadIClass(Descriptor.STRING_BUILDER) != null) && allowStringBuilder) {
this.isStringBuilderAvailable = true;
} else
if (iClassLoader.loadIClass(Descriptor.STRING_BUFFER) != null) {
this.isStringBuilderAvailable = false;
} else
{
throw new JaninoRuntimeException("SNO: Could neither load \"StringBuffer\" nor \"StringBuilder\"");
}
} catch (ClassNotFoundException ex) {
throw new JaninoRuntimeException(
"SNO: Error loading \"StringBuffer\" or \"StringBuilder\": "
+ ex.getMessage()
);
}
// Compile non-static import declarations. (Must be done here in the constructor and not
// down in "compileUnit()" because otherwise "resolve()" cannot resolve type names.)
this.typeImportsOnDemand = new ArrayList();
this.typeImportsOnDemand.add(new String[] { "java", "lang" });
for (Iterator it = this.compilationUnit.importDeclarations.iterator(); it.hasNext();) {
ImportDeclaration id = (ImportDeclaration) it.next();
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
try {
id.accept(new ImportVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitSingleTypeImportDeclaration(SingleTypeImportDeclaration stid) { try { UnitCompiler.this.import2(stid); } catch (CompileException e) { throw new UCE(e); } }
public void visitTypeImportOnDemandDeclaration(TypeImportOnDemandDeclaration tiodd) { UnitCompiler.this.import2(tiodd); }
public void visitSingleStaticImportDeclaration(SingleStaticImportDeclaration ssid) {}
public void visitStaticImportOnDemandDeclaration(StaticImportOnDemandDeclaration siodd) {}
// CHECKSTYLE(LineLengthCheck):ON
});
} catch (UCE uce) { throw uce.ce; }
}
}
private void import2(SingleTypeImportDeclaration stid) throws CompileException {
String[] ids = stid.identifiers;
String name = last(ids);
String[] prev = (String[]) UnitCompiler.this.singleTypeImports.put(name, ids);
// Check for re-import of same name.
if (prev != null && !Arrays.equals(prev, ids)) {
this.compileError(
"Class \"" + name + "\" was previously imported as " +
"\"" + Java.join(prev, ".") + "\", now as \"" + Java.join(ids, ".") + "\"",
stid.getLocation()
);
}
IClass iClass = this.loadFullyQualifiedClass(ids);
if (iClass == null) {
this.compileError("Imported class \"" + Java.join(ids, ".") + "\" could not be loaded", stid.getLocation());
}
}
private void import2(TypeImportOnDemandDeclaration tiodd) {
// Duplicate type-imports-on-demand are not an error, so no checks here.
UnitCompiler.this.typeImportsOnDemand.add(tiodd.identifiers);
}
private void import2(SingleStaticImportDeclaration ssid) throws CompileException {
String name = 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.loadFullyQualifiedClass(ssid.identifiers);
if (iClass != null) {
importedObjects.add(iClass);
return;
}
}
String[] typeName = allButLast(ssid.identifiers);
IClass iClass = this.loadFullyQualifiedClass(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.loadFullyQualifiedClass(siodd.identifiers);
if (iClass == null) {
this.compileError("Could not load \"" + Java.join(siodd.identifiers, ".") + "\"", siodd.getLocation());
return;
}
UnitCompiler.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.
for (Iterator it = this.compilationUnit.importDeclarations.iterator(); it.hasNext();) {
ImportDeclaration id = (ImportDeclaration) it.next();
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
try {
id.accept(new ImportVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitSingleTypeImportDeclaration(SingleTypeImportDeclaration stid) {}
public void visitTypeImportOnDemandDeclaration(TypeImportOnDemandDeclaration tiodd) {}
public void visitSingleStaticImportDeclaration(SingleStaticImportDeclaration ssid) { try { UnitCompiler.this.import2(ssid); } catch (CompileException e) { throw new UCE(e); } }
public void visitStaticImportOnDemandDeclaration(StaticImportOnDemandDeclaration siodd) { try { UnitCompiler.this.import2(siodd); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
});
} catch (UCE uce) { throw uce.ce; }
}
this.generatedClassFiles = new ArrayList();
for (Iterator it = this.compilationUnit.packageMemberTypeDeclarations.iterator(); it.hasNext();) {
UnitCompiler.this.compile((Java.PackageMemberTypeDeclaration) it.next());
}
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(Java.TypeDeclaration td) throws CompileException {
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.TypeDeclarationVisitor tdv = new Visitor.TypeDeclarationVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitAnonymousClassDeclaration(Java.AnonymousClassDeclaration acd) { try { UnitCompiler.this.compile2(acd); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalClassDeclaration(Java.LocalClassDeclaration lcd) { try { UnitCompiler.this.compile2(lcd); } catch (CompileException e) { throw new UCE(e); } }
public void visitPackageMemberClassDeclaration(Java.PackageMemberClassDeclaration pmcd) { try { UnitCompiler.this.compile2((Java.PackageMemberTypeDeclaration) pmcd); } catch (CompileException e) { throw new UCE(e); } }
public void visitMemberInterfaceDeclaration(Java.MemberInterfaceDeclaration mid) { try { UnitCompiler.this.compile2(mid); } catch (CompileException e) { throw new UCE(e); } }
public void visitPackageMemberInterfaceDeclaration(Java.PackageMemberInterfaceDeclaration pmid) { try { UnitCompiler.this.compile2((Java.PackageMemberTypeDeclaration) pmid); } catch (CompileException e) { throw new UCE(e); } }
public void visitMemberClassDeclaration(Java.MemberClassDeclaration mcd) { try { UnitCompiler.this.compile2(mcd); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
td.accept(tdv);
} catch (UCE uce) {
throw uce.ce;
}
}
public void compile2(Java.PackageMemberTypeDeclaration pmtd) throws CompileException {
Java.CompilationUnit declaringCompilationUnit = pmtd.getDeclaringCompilationUnit();
// Check for conflict with single-type-import (7.6).
{
String[] ss = this.getSingleTypeImport(pmtd.getName());
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).
{
Java.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 Java.NamedClassDeclaration) {
this.compile2((Java.NamedClassDeclaration) pmtd);
} else
if (pmtd instanceof Java.InterfaceDeclaration) {
this.compile2((Java.InterfaceDeclaration) pmtd);
} else
{
throw new JaninoRuntimeException("PMTD of unexpected type " + pmtd.getClass().getName());
}
}
public void compile2(Java.ClassDeclaration cd) throws CompileException {
IClass iClass = this.resolve(cd);
// Check that all methods are implemented.
if ((cd.getModifiers() & Mod.ABSTRACT) == 0) {
IMethod[] ms = iClass.getIMethods();
for (int i = 0; i < ms.length; ++i) {
IMethod base = ms[i];
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.getModifiers() | Mod.SUPER), // accessFlags
iClass.getDescriptor(), // thisClassFD
iClass.getSuperclass().getDescriptor(), // superClassFD
IClass.getDescriptors(iClass.getInterfaces()) // interfaceFDs
);
// Add InnerClasses attribute entry for this class declaration.
if (cd.getEnclosingScope() instanceof Java.CompilationUnit) {
;
} else
if (cd.getEnclosingScope() instanceof Java.Block) {
short innerClassInfoIndex = cf.addConstantClassInfo(iClass.getDescriptor());
short innerNameIndex = (
this instanceof Java.NamedTypeDeclaration ?
cf.addConstantUtf8Info(((Java.NamedTypeDeclaration) this).getName()) :
(short) 0
);
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
(short) 0, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
cd.getModifiers() // innerClassAccessFlags
));
} else
if (cd.getEnclosingScope() instanceof Java.TypeDeclaration) {
short innerClassInfoIndex = cf.addConstantClassInfo(iClass.getDescriptor());
short outerClassInfoIndex = cf.addConstantClassInfo(
this.resolve(((Java.TypeDeclaration) cd.getEnclosingScope())).getDescriptor()
);
short innerNameIndex = cf.addConstantUtf8Info(((Java.MemberTypeDeclaration) cd).getName());
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
outerClassInfoIndex, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
cd.getModifiers() // 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 Java.NamedTypeDeclaration) {
sourceFileName = ((Java.NamedTypeDeclaration) cd).getName() + ".java";
} else {
sourceFileName = "ANONYMOUS.java";
}
}
cf.addSourceFileAttribute(sourceFileName);
}
// Add "Deprecated" attribute (JVMS 4.7.10)
if (cd instanceof Java.DocCommentable) {
if (((Java.DocCommentable) cd).hasDeprecatedDocTag()) cf.addDeprecatedAttribute();
}
// Optional: Generate and compile class initialization method.
{
List/*<BlockStatement>*/ statements = new ArrayList();
for (Iterator it = cd.variableDeclaratorsAndInitializers.iterator(); it.hasNext();) {
Java.TypeBodyDeclaration tbd = (Java.TypeBodyDeclaration) it.next();
if (tbd.isStatic()) statements.add((Java.BlockStatement) tbd);
}
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.
int declaredMethodCount = cd.getMethodDeclarations().size();
{
int syntheticFieldCount = cd.syntheticFields.size();
Java.ConstructorDeclarator[] cds = cd.getConstructors();
for (int i = 0; i < cds.length; ++i) {
this.compile(cds[i], cf);
if (syntheticFieldCount != cd.syntheticFields.size()) {
throw new JaninoRuntimeException(
"SNO: Compilation of constructor \""
+ cds[i]
+ "\" ("
+ cds[i].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 (int i = 0; i < ms.length; ++i) {
IMethod base = ms[i];
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 (Iterator it = cd.variableDeclaratorsAndInitializers.iterator(); it.hasNext();) {
Java.TypeBodyDeclaration tbd = (Java.TypeBodyDeclaration) it.next();
if (!(tbd instanceof Java.FieldDeclaration)) continue;
this.addFields((Java.FieldDeclaration) tbd, cf);
}
// Synthetic fields.
for (Iterator it = cd.syntheticFields.values().iterator(); it.hasNext();) {
IClass.IField f = (IClass.IField) it.next();
cf.addFieldInfo(
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);
}
/**
* Create {@link ClassFile.FieldInfo}s for all fields declared by the
* given {@link Java.FieldDeclaration}.
*/
private void addFields(Java.FieldDeclaration fd, ClassFile cf) throws CompileException {
for (int j = 0; j < fd.variableDeclarators.length; ++j) {
Java.VariableDeclarator vd = fd.variableDeclarators[j];
Java.Type type = fd.type;
for (int k = 0; k < vd.brackets; ++k) type = new Java.ArrayType(type);
Object ocv = null;
if (
(fd.modifiers & Mod.FINAL) != 0 &&
vd.optionalInitializer != null
) {
if (vd.optionalInitializer instanceof Java.Rvalue) {
ocv = this.getConstantValue((Java.Rvalue) vd.optionalInitializer);
}
if (ocv == Java.Rvalue.CONSTANT_VALUE_NULL) ocv = null;
}
ClassFile.FieldInfo fi;
if (Mod.isPrivateAccess(fd.modifiers)) {
// 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
fi = cf.addFieldInfo(
Mod.changeAccess(fd.modifiers, Mod.PACKAGE), // modifiers
vd.name, // fieldName
this.getType(type).getDescriptor(), // fieldTypeFD
ocv // optionalConstantValue
);
} else
{
fi = cf.addFieldInfo(
fd.modifiers, // modifiers
vd.name, // fieldName
this.getType(type).getDescriptor(), // fieldTypeFD
ocv // optionalConstantValue
);
}
// Add "Deprecated" attribute (JVMS 4.7.10)
if (fd.hasDeprecatedDocTag()) {
fi.addAttribute(new ClassFile.DeprecatedAttribute(cf.addConstantUtf8Info("Deprecated")));
}
}
}
public void compile2(Java.AnonymousClassDeclaration acd) throws CompileException {
this.compile2((Java.InnerClassDeclaration) acd);
}
public void compile2(Java.LocalClassDeclaration lcd) throws CompileException {
this.compile2((Java.InnerClassDeclaration) lcd);
}
public void compile2(Java.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((Java.TypeDeclaration) ocs.get(1))
));
}
}
// For classes that enclose surrounding scopes, trawl their initializers looking for synthetic fields.
if (icd instanceof AnonymousClassDeclaration || icd instanceof LocalClassDeclaration) {
Java.ClassDeclaration cd = (Java.ClassDeclaration) icd;
for (int i = 0; i < cd.variableDeclaratorsAndInitializers.size(); ++i) {
TypeBodyDeclaration tbd = (TypeBodyDeclaration) cd.variableDeclaratorsAndInitializers.get(i);
if (tbd instanceof FieldDeclaration) {
FieldDeclaration fd = (FieldDeclaration) tbd;
for (int j = 0; j < fd.variableDeclarators.length; ++j) {
VariableDeclarator vd = fd.variableDeclarators[j];
if (vd.optionalInitializer != null) {
this.fakeCompile(vd.optionalInitializer);
}
}
}
}
}
this.compile2((Java.ClassDeclaration) icd);
}
public void compile2(final Java.MemberClassDeclaration mcd) throws CompileException {
this.compile2((Java.InnerClassDeclaration) mcd);
}
public void compile2(Java.InterfaceDeclaration id) throws CompileException {
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) ( // accessFlags
id.getModifiers() |
Mod.SUPER |
Mod.INTERFACE |
Mod.ABSTRACT
),
iClass.getDescriptor(), // thisClassFD
Descriptor.OBJECT, // superClassFD
interfaceDescriptors // interfaceFDs
);
// 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(); // BlockStatements
statements.addAll(id.constantDeclarations);
maybeCreateInitMethod(id, cf, statements);
}
compileDeclaredMethods(id, cf);
// Class variables.
for (int i = 0; i < id.constantDeclarations.size(); ++i) {
Java.BlockStatement bs = (Java.BlockStatement) id.constantDeclarations.get(i);
if (!(bs instanceof Java.FieldDeclaration)) continue;
this.addFields((Java.FieldDeclaration) bs, cf);
}
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(
Java.AbstractTypeDeclaration decl,
ClassFile cf,
List/*<BlockStatement>*/ statements
) throws CompileException {
// Create interface initialization method iff there is any initialization code.
if (this.generatesCode2ListStatements(statements)) {
Java.MethodDeclarator md = new Java.MethodDeclarator(
decl.getLocation(), // location
null, // optionalDocComment
(short) (Mod.STATIC | Mod.PUBLIC), // modifiers
new Java.BasicType( // type
decl.getLocation(),
Java.BasicType.VOID
),
"<clinit>", // name
new Java.FunctionDeclarator.FormalParameter[0], // formalParameters
new Java.ReferenceType[0], // thrownExcaptions
statements // optionalStatements
);
md.setDeclaringType(decl);
this.compile(md, cf);
}
}
/**
* Compile all of the types for this declaration
* <p>
* NB: as a side effect this will fill in the synthetic field map
*
* @throws CompileException
*/
private void compileDeclaredMemberTypes(
Java.TypeDeclaration decl,
ClassFile cf
) throws CompileException {
for (Iterator it = decl.getMemberTypeDeclarations().iterator(); it.hasNext();) {
Java.TypeDeclaration td = ((Java.MemberTypeDeclaration) it.next());
this.compile(td);
// Add InnerClasses attribute entry for member type declaration.
short innerClassInfoIndex = cf.addConstantClassInfo(this.resolve(td).getDescriptor());
short outerClassInfoIndex = cf.addConstantClassInfo(this.resolve(decl).getDescriptor());
short innerNameIndex = cf.addConstantUtf8Info(((Java.MemberTypeDeclaration) td).getName());
cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry(
innerClassInfoIndex, // innerClassInfoIndex
outerClassInfoIndex, // outerClassInfoIndex
innerNameIndex, // innerNameIndex
td.getModifiers() // innerClassAccessFlags
));
}
}
/**
* Compile all of the methods for this declaration
* <p>
* NB: as a side effect this will fill in the synthetic field map
* @throws CompileException
*/
private void compileDeclaredMethods(
Java.AbstractTypeDeclaration typeDeclaration,
ClassFile cf
) throws CompileException {
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(
Java.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) {
this.compile(((Java.MethodDeclarator) typeDeclaration.getMethodDeclarations().get(i)), cf);
}
}
/**
* Compile a bridge method which will add a method of the signature of base that
* delegates to override.
* @throws CompileException
*/
private void compileBridgeMethod(ClassFile cf, IMethod base, IMethod override) throws CompileException {
ClassFile.MethodInfo mi = cf.addMethodInfo(
(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());
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(
(short) Descriptor.size(paramTypes[i].getDescriptor()),
"param"+i,
paramTypes[i]);
}
this.writeOpcode(Located.NOWHERE, Opcode.ALOAD_0);
for (int i = 0; i < locals.length; ++i) {
this.load(Located.NOWHERE, locals[i].getType(), locals[i].getSlotIndex());
}
this.writeOpcode(Located.NOWHERE, Opcode.INVOKEVIRTUAL);
this.writeConstantMethodrefInfo(
override.getDeclaringIClass().getDescriptor(), // classFD
override.getName(), // methodName
override.getDescriptor() // methodMD
);
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")) {
protected void storeBody(DataOutputStream dos) throws IOException {
codeContext.storeCodeAttributeBody(dos, (short)0, (short)0);
}
});
}
/**
* @return <tt>false</tt> if this statement cannot complete normally (JLS2
* 14.20)
*/
private boolean compile(Java.BlockStatement bs) throws CompileException {
final boolean[] res = new boolean[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.BlockStatementVisitor bsv = new Visitor.BlockStatementVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitInitializer(Java.Initializer i) { try { res[0] = UnitCompiler.this.compile2(i); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldDeclaration(Java.FieldDeclaration fd) { try { res[0] = UnitCompiler.this.compile2(fd); } catch (CompileException e) { throw new UCE(e); } }
public void visitLabeledStatement(Java.LabeledStatement ls) { try { res[0] = UnitCompiler.this.compile2(ls); } catch (CompileException e) { throw new UCE(e); } }
public void visitBlock(Java.Block b) { try { res[0] = UnitCompiler.this.compile2(b); } catch (CompileException e) { throw new UCE(e); } }
public void visitExpressionStatement(Java.ExpressionStatement es) { try { res[0] = UnitCompiler.this.compile2(es); } catch (CompileException e) { throw new UCE(e); } }
public void visitIfStatement(Java.IfStatement is) { try { res[0] = UnitCompiler.this.compile2(is); } catch (CompileException e) { throw new UCE(e); } }
public void visitForStatement(Java.ForStatement fs) { try { res[0] = UnitCompiler.this.compile2(fs); } catch (CompileException e) { throw new UCE(e); } }
public void visitWhileStatement(Java.WhileStatement ws) { try { res[0] = UnitCompiler.this.compile2(ws); } catch (CompileException e) { throw new UCE(e); } }
public void visitTryStatement(Java.TryStatement ts) { try { res[0] = UnitCompiler.this.compile2(ts); } catch (CompileException e) { throw new UCE(e); } }
public void visitSwitchStatement(Java.SwitchStatement ss) { try { res[0] = UnitCompiler.this.compile2(ss); } catch (CompileException e) { throw new UCE(e); } }
public void visitSynchronizedStatement(Java.SynchronizedStatement ss) { try { res[0] = UnitCompiler.this.compile2(ss); } catch (CompileException e) { throw new UCE(e); } }
public void visitDoStatement(Java.DoStatement ds) { try { res[0] = UnitCompiler.this.compile2(ds); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableDeclarationStatement(Java.LocalVariableDeclarationStatement lvds) { try { res[0] = UnitCompiler.this.compile2(lvds); } catch (CompileException e) { throw new UCE(e); } }
public void visitReturnStatement(Java.ReturnStatement rs) { try { res[0] = UnitCompiler.this.compile2(rs); } catch (CompileException e) { throw new UCE(e); } }
public void visitThrowStatement(Java.ThrowStatement ts) { try { res[0] = UnitCompiler.this.compile2(ts); } catch (CompileException e) { throw new UCE(e); } }
public void visitBreakStatement(Java.BreakStatement bs) { try { res[0] = UnitCompiler.this.compile2(bs); } catch (CompileException e) { throw new UCE(e); } }
public void visitContinueStatement(Java.ContinueStatement cs) { try { res[0] = UnitCompiler.this.compile2(cs); } catch (CompileException e) { throw new UCE(e); } }
public void visitEmptyStatement(Java.EmptyStatement es) { res[0] = UnitCompiler.this.compile2(es); }
public void visitLocalClassDeclarationStatement(Java.LocalClassDeclarationStatement lcds) { try { res[0] = UnitCompiler.this.compile2(lcds); } catch (CompileException e) { throw new UCE(e); } }
public void visitAlternateConstructorInvocation(Java.AlternateConstructorInvocation aci) { try { res[0] = UnitCompiler.this.compile2(aci); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperConstructorInvocation(Java.SuperConstructorInvocation sci) { try { res[0] = UnitCompiler.this.compile2(sci); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
bs.accept(bsv);
return res[0];
} catch (UCE uce) {
throw uce.ce;
}
}
private boolean compile2(Java.Initializer i) throws CompileException {
return this.compile(i.block);
}
private boolean compile2(Java.Block b) throws CompileException {
this.codeContext.saveLocalVariables();
try {
return compileStatements(b.statements);
} finally {
this.codeContext.restoreLocalVariables();
}
}
private boolean compileStatements(List statements) throws CompileException {
boolean previousStatementCanCompleteNormally = true;
for (int i = 0; i < statements.size(); ++i) {
Java.BlockStatement bs = (Java.BlockStatement) statements.get(i);
if (!previousStatementCanCompleteNormally && this.generatesCode(bs)) {
this.compileError("Statement is unreachable", bs.getLocation());
break;
}
previousStatementCanCompleteNormally = this.compile(bs);
}
return previousStatementCanCompleteNormally;
}
private boolean compile2(Java.DoStatement ds) throws CompileException {
Object cvc = this.getConstantValue(ds.condition);
if (cvc != null) {
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());
}
}
ds.whereToContinue = this.codeContext.new Offset();
ds.bodyHasContinue = false;
CodeContext.Offset bodyOffset = this.codeContext.newOffset();
// Compile body.
if (!this.compile(ds.body) && !ds.bodyHasContinue) {
this.warning("DSNTC", "\"do\" statement never tests its condition", ds.getLocation());
if (ds.whereToBreak == null) return false;
ds.whereToBreak.set();
return true;
}
// Compile condition.
ds.whereToContinue.set();
this.compileBoolean(ds.condition, bodyOffset, Java.Rvalue.JUMP_IF_TRUE);
if (ds.whereToBreak != null) ds.whereToBreak.set();
return true;
}
private boolean compile2(Java.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 != null) {
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 = this.codeContext.new Offset();
fs.bodyHasContinue = false;
CodeContext.Offset bodyOffset = this.codeContext.newOffset();
boolean bodyCCN = this.compile(fs.body);
// Compile update.
fs.whereToContinue.set();
if (fs.optionalUpdate != null) {
if (!bodyCCN && !fs.bodyHasContinue) {
this.warning("FUUR", "For update is unreachable", fs.getLocation());
} else
{
for (int i = 0; i < fs.optionalUpdate.length; ++i) {
this.compile(fs.optionalUpdate[i]);
}
}
}
// Compile condition.
toCondition.set();
this.compileBoolean(fs.optionalCondition, bodyOffset, Java.Rvalue.JUMP_IF_TRUE);
} finally {
this.codeContext.restoreLocalVariables();
}
if (fs.whereToBreak != null) fs.whereToBreak.set();
return true;
}
private boolean compile2(Java.WhileStatement ws) throws CompileException {
Object cvc = this.getConstantValue(ws.condition);
if (cvc != null) {
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());
}
}
ws.whereToContinue = this.codeContext.new Offset();
ws.bodyHasContinue = false;
this.writeBranch(ws, Opcode.GOTO, ws.whereToContinue);
// Compile body.
CodeContext.Offset bodyOffset = this.codeContext.newOffset();
this.compile(ws.body); // Return value (CCN) is ignored.
// Compile condition.
ws.whereToContinue.set();
this.compileBoolean(ws.condition, bodyOffset, Java.Rvalue.JUMP_IF_TRUE);
if (ws.whereToBreak != null) ws.whereToBreak.set();
return true;
}
private boolean compileUnconditionalLoop(
Java.ContinuableStatement cs,
Java.BlockStatement body,
Java.Rvalue[] optionalUpdate
) throws CompileException {
if (optionalUpdate != null) return this.compileUnconditionalLoopWithUpdate(cs, body, optionalUpdate);
cs.whereToContinue = this.codeContext.newOffset();
cs.bodyHasContinue = false;
// Compile body.
if (this.compile(body)) this.writeBranch(cs, Opcode.GOTO, cs.whereToContinue);
if (cs.whereToBreak == null) return false;
cs.whereToBreak.set();
return true;
}
private boolean compileUnconditionalLoopWithUpdate(
Java.ContinuableStatement cs,
Java.BlockStatement body,
Java.Rvalue[] update
) throws CompileException {
cs.whereToContinue = this.codeContext.new Offset();
cs.bodyHasContinue = false;
// Compile body.
CodeContext.Offset bodyOffset = this.codeContext.newOffset();
boolean bodyCCN = this.compile(body);
// Compile the "update".
cs.whereToContinue.set();
if (!bodyCCN && !cs.bodyHasContinue) {
this.warning("LUUR", "Loop update is unreachable", update[0].getLocation());
} else
{
for (int i = 0; i < update.length; ++i) this.compile(update[i]);
this.writeBranch(cs, Opcode.GOTO, bodyOffset);
}
if (cs.whereToBreak == null) return false;
cs.whereToBreak.set();
return true;
}
private boolean compile2(Java.LabeledStatement ls) throws CompileException {
boolean canCompleteNormally = this.compile(ls.body);
if (ls.whereToBreak != null) {
ls.whereToBreak.set();
canCompleteNormally = true;
}
return canCompleteNormally;
}
private boolean compile2(Java.SwitchStatement ss) throws CompileException {
// Compute condition.
IClass switchExpressionType = this.compileGetValue(ss.condition);
this.assignmentConversion(
(Locatable) ss, // l
switchExpressionType, // sourceType
IClass.INT, // targetType
null // optionalConstantValue
);
// Prepare the map of case labels to code offsets.
TreeMap caseLabelMap = new TreeMap(); // Integer => Offset
CodeContext.Offset defaultLabelOffset = null;
CodeContext.Offset[] sbsgOffsets = new CodeContext.Offset[ss.sbsgs.size()];
for (int i = 0; i < ss.sbsgs.size(); ++i) {
Java.SwitchStatement.SwitchBlockStatementGroup sbsg = (
(Java.SwitchStatement.SwitchBlockStatementGroup) ss.sbsgs.get(i)
);
sbsgOffsets[i] = this.codeContext.new Offset();
for (int j = 0; j < sbsg.caseLabels.size(); ++j) {
// Verify that case label value is a constant.
Java.Rvalue rv = (Java.Rvalue) sbsg.caseLabels.get(j);
Object cv = this.getConstantValue(rv);
if (cv == null) {
this.compileError("Value of \"case\" label does not pose a constant value", rv.getLocation());
cv = new Integer(99);
}
// Verify that case label is assignable to the type of the switch expression.
IClass rvType = this.getType(rv);
this.assignmentConversion(
(Locatable) ss, // l
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",
rv.getLocation()
);
civ = new Integer(99);
}
// Store in case label map.
if (caseLabelMap.containsKey(civ)) {
this.compileError("Duplicate \"case\" switch label value", rv.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()).intValue() + caseLabelMap.size() >= // Beware of INT overflow!
((Integer) caseLabelMap.lastKey()).intValue() - caseLabelMap.size()
) {
int low = ((Integer) caseLabelMap.firstKey()).intValue();
int high = ((Integer) caseLabelMap.lastKey()).intValue();
this.writeOpcode(ss, Opcode.TABLESWITCH);
new Java.Padder(this.codeContext).set();
this.writeOffset(switchOffset, defaultLabelOffset);
this.writeInt(low);
this.writeInt(high);
Iterator si = caseLabelMap.entrySet().iterator();
int cur = low;
while (si.hasNext()) {
Map.Entry me = (Map.Entry) si.next();
int val = ((Integer) me.getKey()).intValue();
while (cur < val) {
this.writeOffset(switchOffset, defaultLabelOffset);
++cur;
}
this.writeOffset(switchOffset, (CodeContext.Offset) me.getValue());
++cur;
}
} else {
this.writeOpcode(ss, Opcode.LOOKUPSWITCH);
new Java.Padder(this.codeContext).set();
this.writeOffset(switchOffset, defaultLabelOffset);
this.writeInt(caseLabelMap.size());
Iterator si = caseLabelMap.entrySet().iterator();
while (si.hasNext()) {
Map.Entry me = (Map.Entry) si.next();
this.writeInt(((Integer) me.getKey()).intValue());
this.writeOffset(switchOffset, (CodeContext.Offset) me.getValue());
}
}
// Compile statement groups.
boolean canCompleteNormally = true;
for (int i = 0; i < ss.sbsgs.size(); ++i) {
Java.SwitchStatement.SwitchBlockStatementGroup sbsg = (
(Java.SwitchStatement.SwitchBlockStatementGroup) ss.sbsgs.get(i)
);
sbsgOffsets[i].set();
canCompleteNormally = true;
for (int j = 0; j < sbsg.blockStatements.size(); ++j) {
Java.BlockStatement bs = (Java.BlockStatement) sbsg.blockStatements.get(j);
if (!canCompleteNormally) {
this.compileError("Statement is unreachable", bs.getLocation());
break;
}
canCompleteNormally = this.compile(bs);
}
}
if (ss.whereToBreak != null) {
ss.whereToBreak.set();
canCompleteNormally = true;
}
return canCompleteNormally;
}
private boolean compile2(Java.BreakStatement bs) throws CompileException {
// Find the broken statement.
Java.BreakableStatement brokenStatement = null;
if (bs.optionalLabel == null) {
for (
Java.Scope s = bs.getEnclosingScope();
s instanceof Java.Statement || s instanceof Java.CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof Java.BreakableStatement) {
brokenStatement = (Java.BreakableStatement) s;
break;
}
}
if (brokenStatement == null) {
this.compileError("\"break\" statement is not enclosed by a breakable statement", bs.getLocation());
return false;
}
} else {
for (
Java.Scope s = bs.getEnclosingScope();
s instanceof Java.Statement || s instanceof Java.CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof Java.LabeledStatement) {
Java.LabeledStatement ls = (Java.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(Java.ContinueStatement cs) throws CompileException {
// Find the continued statement.
Java.ContinuableStatement continuedStatement = null;
if (cs.optionalLabel == null) {
for (
Java.Scope s = cs.getEnclosingScope();
s instanceof Java.Statement || s instanceof Java.CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof Java.ContinuableStatement) {
continuedStatement = (Java.ContinuableStatement) s;
break;
}
}
if (continuedStatement == null) {
this.compileError(
"\"continue\" statement is not enclosed by a continuable statement",
cs.getLocation()
);
return false;
}
} else {
for (
Java.Scope s = cs.getEnclosingScope();
s instanceof Java.Statement || s instanceof Java.CatchClause;
s = s.getEnclosingScope()
) {
if (s instanceof Java.LabeledStatement) {
Java.LabeledStatement ls = (Java.LabeledStatement) s;
if (ls.label.equals(cs.optionalLabel)) {
Java.Statement st = ls.body;
while (st instanceof Java.LabeledStatement) st = ((Java.LabeledStatement) st).body;
if (!(st instanceof Java.ContinuableStatement)) {
this.compileError("Labeled statement is not continuable", st.getLocation());
return false;
}
continuedStatement = (Java.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;
}
}
continuedStatement.bodyHasContinue = true;
this.leaveStatements(
cs.getEnclosingScope(), // from
continuedStatement.getEnclosingScope(), // to
null // optionalStackValueType
);
this.writeBranch(cs, Opcode.GOTO, continuedStatement.whereToContinue);
return false;
}
private boolean compile2(Java.EmptyStatement es) {
return true;
}
private boolean compile2(Java.ExpressionStatement ee) throws CompileException {
this.compile(ee.rvalue);
return true;
}
private boolean compile2(Java.FieldDeclaration fd) throws CompileException {
for (int i = 0; i < fd.variableDeclarators.length; ++i) {
Java.VariableDeclarator vd = fd.variableDeclarators[i];
Java.ArrayInitializerOrRvalue initializer = this.getNonConstantFinalInitializer(fd, vd);
if (initializer == null) continue;
if ((fd.modifiers & Mod.STATIC) == 0) {
this.writeOpcode(fd, Opcode.ALOAD_0);
}
IClass fieldType = this.getType(fd.type);
if (initializer instanceof Java.Rvalue) {
Java.Rvalue rvalue = (Java.Rvalue) initializer;
IClass initializerType = this.compileGetValue(rvalue);
fieldType = fieldType.getArrayIClass(vd.brackets, this.iClassLoader.OBJECT);
this.assignmentConversion(
(Locatable) fd, // l
initializerType, // sourceType
fieldType, // destinationType
this.getConstantValue(rvalue) // optionalConstantValue
);
} else
if (initializer instanceof Java.ArrayInitializer) {
this.compileGetValue((Java.ArrayInitializer) initializer, fieldType);
} else
{
throw new JaninoRuntimeException(
"Unexpected array initializer or rvalue class "
+ initializer.getClass().getName()
);
}
// No need to check accessibility here.
;
if ((fd.modifiers & Mod.STATIC) != 0) {
this.writeOpcode(fd, Opcode.PUTSTATIC);
} else {
this.writeOpcode(fd, Opcode.PUTFIELD);
}
this.writeConstantFieldrefInfo(
this.resolve(fd.getDeclaringType()).getDescriptor(),
vd.name, // classFD
fieldType.getDescriptor() // fieldFD
);
}
return true;
}
private boolean compile2(Java.IfStatement is) throws CompileException {
Object cv = this.getConstantValue(is.condition);
Java.BlockStatement es = (
is.optionalElseStatement != null
? is.optionalElseStatement
: new Java.EmptyStatement(is.thenStatement.getLocation())
);
if (cv instanceof Boolean) {
// Constant condition.
this.fakeCompile(is.condition);
Java.BlockStatement seeingStatement, blindStatement;
if (((Boolean) cv).booleanValue()) {
seeingStatement = is.thenStatement;
blindStatement = es;
} else {
seeingStatement = es;
blindStatement = is.thenStatement;
}
// Compile the seeing statement.
CodeContext.Inserter ins = this.codeContext.newInserter();
boolean ssccn = this.compile(seeingStatement);
if (ssccn) return true;
// Hm... the "seeing statement" cannot complete normally. 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.
// Constant-condition-IF statement only enclosed by blocks?
Java.Scope s = is.getEnclosingScope();
while (s instanceof Java.Block) s = s.getEnclosingScope();
if (s instanceof Java.FunctionDeclarator) {
// Yes, compile rest of method to /dev/null.
throw UnitCompiler.STOP_COMPILING_CODE;
} else
{
// 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, new Integer(0));
this.writeBranch((Locatable) is, Opcode.IFNE, off);
} finally {
this.codeContext.popInserter();
}
}
return this.compile(blindStatement);
}
// 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, Java.Rvalue.JUMP_IF_FALSE);
boolean tsccn = this.compile(is.thenStatement);
if (tsccn) this.writeBranch((Locatable) 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, Java.Rvalue.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, Java.Rvalue.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((Locatable) is, conditionType);
return true;
}
}
}
private static final RuntimeException STOP_COMPILING_CODE = new RuntimeException(
"SNO: This exception should have been caught and processed"
);
private boolean compile2(Java.LocalClassDeclarationStatement lcds) throws CompileException {
// Check for redefinition.
Java.LocalClassDeclaration otherLCD = this.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;
}
/**
* Find a local class declared in any block enclosing the given block statement.
*/
private Java.LocalClassDeclaration findLocalClassDeclaration(Java.Scope s, String name) {
for (;;) {
Java.Scope es = s.getEnclosingScope();
if (es instanceof Java.CompilationUnit) break;
if (
s instanceof Java.BlockStatement
&& (es instanceof Java.Block || es instanceof Java.FunctionDeclarator)
) {
Java.BlockStatement bs = (Java.BlockStatement) s;
List statements = (
es instanceof Java.BlockStatement
? ((Java.Block) es).statements
: ((Java.FunctionDeclarator) es).optionalStatements
);
for (Iterator it = statements.iterator(); it.hasNext();) {
Java.BlockStatement bs2 = (Java.BlockStatement) it.next();
if (bs2 instanceof Java.LocalClassDeclarationStatement) {
Java.LocalClassDeclarationStatement lcds = ((Java.LocalClassDeclarationStatement) bs2);
if (lcds.lcd.name.equals(name)) return lcds.lcd;
}
if (bs2 == bs) break;
}
}
s = es;
}
return null;
}
private boolean compile2(Java.LocalVariableDeclarationStatement lvds) throws CompileException {
if ((lvds.modifiers & ~Mod.FINAL) != 0) {
this.compileError(
"The only allowed modifier in local variable declarations is \"final\"",
lvds.getLocation()
);
}
for (int j = 0; j < lvds.variableDeclarators.length; ++j) {
Java.VariableDeclarator vd = lvds.variableDeclarators[j];
Java.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 Java.Rvalue) {
Java.Rvalue rhs = (Java.Rvalue) vd.optionalInitializer;
this.assignmentConversion(
(Locatable) lvds, // l
this.compileGetValue(rhs), // sourceType
lv.type, // targetType
this.getConstantValue(rhs) // optionalConstantValue
);
} else
if (vd.optionalInitializer instanceof Java.ArrayInitializer) {
this.compileGetValue((Java.ArrayInitializer) vd.optionalInitializer, lv.type);
} else
{
throw new JaninoRuntimeException(
"Unexpected rvalue or array initialized class "
+ vd.optionalInitializer.getClass().getName()
);
}
this.store(
(Locatable) lvds, // l
lv.type, // valueType
lv // localVariable
);
}
}
return true;
}
public Java.LocalVariable getLocalVariable(
Java.LocalVariableDeclarationStatement lvds,
Java.VariableDeclarator vd
) throws CompileException {
if (vd.localVariable == null) {
// Determine variable type.
Java.Type variableType = lvds.type;
for (int k = 0; k < vd.brackets; ++k) variableType = new Java.ArrayType(variableType);
vd.localVariable = new Java.LocalVariable((lvds.modifiers & Mod.FINAL) != 0, this.getType(variableType));
}
return vd.localVariable;
}
private boolean compile2(Java.ReturnStatement rs) throws CompileException {
// Determine enclosing block, function and compilation Unit.
Java.FunctionDeclarator enclosingFunction = null;
{
Java.Scope s = rs.getEnclosingScope();
while (s instanceof Java.Statement || s instanceof Java.CatchClause) s = s.getEnclosingScope();
enclosingFunction = (Java.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(
(Locatable) rs, // l
type, // sourceType
returnType, // targetType
this.getConstantValue(rs.optionalReturnValue) // optionalConstantValue
);
this.leaveStatements(
rs.getEnclosingScope(), // from
enclosingFunction, // to
returnType // optionalStackValueType
);
this.writeOpcode(rs, Opcode.IRETURN + this.ilfda(returnType));
return false;
}
private boolean compile2(Java.SynchronizedStatement ss) throws CompileException {
// Evaluate monitor object expression.
if (!this.iClassLoader.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((Locatable) ss, this.iClassLoader.OBJECT, ss.monitorLvIndex);
// Create lock on the monitor object.
this.writeOpcode(ss, Opcode.MONITORENTER);
// Compile the statement body.
CodeContext.Offset monitorExitOffset = this.codeContext.new Offset();
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.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(Java.ThrowStatement ts) throws CompileException {
IClass expressionType = this.compileGetValue(ts.expression);
this.checkThrownException(
(Locatable) ts, // l
expressionType, // type
ts.getEnclosingScope() // scope
);
this.writeOpcode(ts, Opcode.ATHROW);
return false;
}
private boolean compile2(Java.TryStatement ts) throws CompileException {
if (ts.optionalFinally != null) ts.finallyOffset = this.codeContext.new Offset();
CodeContext.Offset beginningOfBody = this.codeContext.newOffset();
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.
short pcLVIndex = (
ts.optionalFinally != null
? this.codeContext.allocateLocalVariable((short) 1)
: (short) 0
);
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();
Java.CatchClause cc = (Java.CatchClause) ts.catchClauses.get(i);
IClass caughtExceptionType = this.getType(cc.caughtException.type);
// Allocate the "exception variable".
Java.LocalVariableSlot exceptionVarSlot = this.codeContext.allocateLocalVariable(
(short) 1,
cc.caughtException.name,
caughtExceptionType
);
short evi = exceptionVarSlot.getSlotIndex();
// Kludge: Treat the exception variable like a local
// variable of the catch clause body.
UnitCompiler.this.getLocalVariable(cc.caughtException).setSlot(exceptionVarSlot);
this.codeContext.addExceptionTableEntry(
beginningOfBody, // startPC
afterBody, // endPC
this.codeContext.newOffset(), // handlerPC
caughtExceptionType.getDescriptor() // catchTypeFD
);
this.store(
(Locatable) cc, // l
caughtExceptionType, // lvType
evi // lvIndex
);
if (this.compile(cc.body)) {
canCompleteNormally = true;
if (
i < ts.catchClauses.size() - 1 ||
ts.optionalFinally != null
) this.writeBranch(cc, 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(
(Locatable) ts.optionalFinally, // l
this.iClassLoader.OBJECT, // valueType
evi // localVariableIndex
);
this.writeBranch(ts.optionalFinally, Opcode.JSR, ts.finallyOffset);
this.load(
(Locatable) ts.optionalFinally, // l
this.iClassLoader.OBJECT, // valueType
evi // localVariableIndex
);
this.writeOpcode(ts.optionalFinally, Opcode.ATHROW);
// Compile the "finally" body.
ts.finallyOffset.set();
this.store(
(Locatable) ts.optionalFinally, // l
this.iClassLoader.OBJECT, // valueType
pcLVIndex // localVariableIndex
);
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(Java.FunctionDeclarator fd, final ClassFile classFile) throws CompileException {
ClassFile.MethodInfo mi;
if (Mod.isPrivateAccess(fd.modifiers)) {
if (fd instanceof Java.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
mi = classFile.addMethodInfo(
(short) (Mod.changeAccess(fd.modifiers, Mod.PACKAGE) | Mod.STATIC), // accessFlags
fd.name + '$', // name
MethodDescriptor.prependParameter( // methodMD
this.toIMethod((Java.MethodDeclarator) fd).getDescriptor(),
this.resolve(fd.getDeclaringType()).getDescriptor()
)
);
} 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
mi = classFile.addMethodInfo(
Mod.changeAccess(fd.modifiers, Mod.PACKAGE), // accessFlags
fd.name, // name
this.toIInvocable(fd).getDescriptor() // methodMD
);
}
} else
{
mi = classFile.addMethodInfo(
fd.modifiers, // accessFlags
fd.name, // name
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 ((fd.modifiers & (Mod.ABSTRACT | Mod.NATIVE)) != 0) return;
// Create CodeContext.
final CodeContext codeContext = new CodeContext(mi.getClassFile());
CodeContext savedCodeContext = this.replaceCodeContext(codeContext);
try {
this.codeContext.saveLocalVariables();
// Define special parameter "this".
if ((fd.modifiers & Mod.STATIC) == 0) {
this.codeContext.allocateLocalVariable((short) 1, "this", this.resolve(fd.getDeclaringType()));
}
if (fd instanceof Java.ConstructorDeclarator) {
Java.ConstructorDeclarator constructorDeclarator = (Java.ConstructorDeclarator) fd;
// Reserve space for synthetic parameters ("this$...", "val$...").
for (
Iterator it = constructorDeclarator.getDeclaringClass().syntheticFields.values().iterator();
it.hasNext();
) {
IClass.IField sf = (IClass.IField) it.next();
Java.LocalVariable lv = new Java.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 Java.ConstructorDeclarator) {
Java.ConstructorDeclarator cd = (Java.ConstructorDeclarator) fd;
if (cd.optionalConstructorInvocation != null) {
this.compile(cd.optionalConstructorInvocation);
if (cd.optionalConstructorInvocation instanceof Java.SuperConstructorInvocation) {
this.assignSyntheticParametersToSyntheticFields(cd);
this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd);
}
} else {
// Determine qualification for superconstructor invocation.
Java.QualifiedThisReference qualification = null;
IClass outerClassOfSuperclass = this.resolve(
cd.getDeclaringClass()
).getSuperclass().getOuterIClass();
if (outerClassOfSuperclass != null) {
// qualification = new Java.QualifiedThisReference(
// cd.getLocation(), // location
// cd.getDeclaringClass(), // declaringClass
// cd, // declaringTypeBodyDeclaration
// outerClassOfSuperclass // targetIClass
// );
qualification = new Java.QualifiedThisReference(
cd.getLocation(), // location
new Java.SimpleType( // qualification
cd.getLocation(),
outerClassOfSuperclass
)
);
}
// Invoke the superconstructor.
Java.SuperConstructorInvocation sci = new Java.SuperConstructorInvocation(
cd.getLocation(), // location
qualification, // optionalQualification
new Java.Rvalue[0] // arguments
);
sci.setEnclosingScope(fd);
this.compile(sci);
this.assignSyntheticParametersToSyntheticFields(cd);
this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd);
}
}
// Compile the function body.
try {
if (fd.optionalStatements == null) {
this.compileError("Method must have a body", fd.getLocation());
return;
}
if (compileStatements(fd.optionalStatements)) {
if (this.getReturnType(fd) != IClass.VOID) {
this.compileError("Method must return a value", fd.getLocation());
}
this.writeOpcode(fd, Opcode.RETURN);
}
} catch (RuntimeException ex) {
if (ex != UnitCompiler.STOP_COMPILING_CODE) throw ex;
// In very special circumstances (e.g. "if (true) return;"), code generation is
// terminated abruptly by throwing STOP_COMPILING_CODE.
;
}
} 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) {
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")) {
protected void storeBody(DataOutputStream dos) throws IOException {
codeContext.storeCodeAttributeBody(dos, lntani, lvtani);
}
});
}
/** Make the variable name and class name Constant Pool names used by local variables. */
private void makeLocalVariableNames(final CodeContext cc, final ClassFile.MethodInfo mi) {
ClassFile cf = mi.getClassFile();
Iterator iter = cc.getAllLocalVars().iterator();
cf.addConstantUtf8Info("LocalVariableTable");
while (iter.hasNext()) {
Java.LocalVariableSlot slot = (Java.LocalVariableSlot) iter.next();
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.length; ++i) {
Java.FunctionDeclarator.FormalParameter fp = fd.formalParameters[i];
Java.LocalVariable lv = this.getLocalVariable(fp);
lv.setSlot(this.codeContext.allocateLocalVariable(
Descriptor.size(lv.type.getDescriptor()),
fp.name,
this.getType(fp.type)
));
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) {
this.buildLocalVariableMap(cd.optionalConstructorInvocation, localVars);
}
}
if (fd.optionalStatements != null) {
for (Iterator it = fd.optionalStatements.iterator(); it.hasNext();) {
BlockStatement bs = (BlockStatement) it.next();
localVars = this.buildLocalVariableMap(bs, localVars);
}
}
}
private Map buildLocalVariableMap(BlockStatement bs, final Map localVars) throws CompileException {
final Map[] resVars = new Map[] { localVars };
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
BlockStatementVisitor bsv = new BlockStatementVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
// basic statements that use the default handlers
public void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { UnitCompiler.this.buildLocalVariableMap(aci, localVars); }
public void visitBreakStatement(BreakStatement bs) { UnitCompiler.this.buildLocalVariableMap(bs, localVars); }
public void visitContinueStatement(ContinueStatement cs) { UnitCompiler.this.buildLocalVariableMap(cs, localVars); }
public void visitEmptyStatement(EmptyStatement es) { UnitCompiler.this.buildLocalVariableMap(es, localVars); }
public void visitExpressionStatement(ExpressionStatement es) { UnitCompiler.this.buildLocalVariableMap(es, localVars); }
public void visitFieldDeclaration(FieldDeclaration fd) { UnitCompiler.this.buildLocalVariableMap(fd, localVars); }
public void visitReturnStatement(ReturnStatement rs) { UnitCompiler.this.buildLocalVariableMap(rs, localVars); }
public void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { UnitCompiler.this.buildLocalVariableMap(sci, localVars); }
public void visitThrowStatement(ThrowStatement ts) { UnitCompiler.this.buildLocalVariableMap(ts, localVars); }
public void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { UnitCompiler.this.buildLocalVariableMap(lcds, localVars); }
// more complicated statements with specialized handlers, but don't add new variables in this scope
public void visitBlock(Block b) { try { UnitCompiler.this.buildLocalVariableMap(b , localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitDoStatement(DoStatement ds) { try { UnitCompiler.this.buildLocalVariableMap(ds, localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitForStatement(ForStatement fs) { try { UnitCompiler.this.buildLocalVariableMap(fs, localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitIfStatement(IfStatement is) { try { UnitCompiler.this.buildLocalVariableMap(is, localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitInitializer(Initializer i) { try { UnitCompiler.this.buildLocalVariableMap(i , localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitSwitchStatement(SwitchStatement ss) { try { UnitCompiler.this.buildLocalVariableMap(ss, localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitSynchronizedStatement(SynchronizedStatement ss) { try { UnitCompiler.this.buildLocalVariableMap(ss, localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitTryStatement(TryStatement ts) { try { UnitCompiler.this.buildLocalVariableMap(ts, localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitWhileStatement(WhileStatement ws) { try { UnitCompiler.this.buildLocalVariableMap(ws, localVars); } catch (CompileException e) { throw new UCE(e); } }
// more complicated statements with specialized handlers, that can add variables in this scope
public void visitLabeledStatement(LabeledStatement ls) { try { resVars[0] = UnitCompiler.this.buildLocalVariableMap(ls , localVars); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { try { resVars[0] = UnitCompiler.this.buildLocalVariableMap(lvds, localVars); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try { bs.accept(bsv); } catch (UCE uce) { throw uce.ce; }
return resVars[0];
}
// default handlers
private Map buildLocalVariableMap(Statement s, final Map localVars) { return s.localVariables = localVars; }
private 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 (Iterator it = block.statements.iterator(); it.hasNext();) {
BlockStatement bs = (BlockStatement) it.next();
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(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 (Iterator cases = ss.sbsgs.iterator(); cases.hasNext();) {
SwitchStatement.SwitchBlockStatementGroup sbsg = (SwitchStatement.SwitchBlockStatementGroup) cases.next();
for (Iterator stmts = sbsg.blockStatements.iterator(); stmts.hasNext();) {
BlockStatement bs = (BlockStatement) stmts.next();
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 (Iterator it = ts.catchClauses.iterator(); it.hasNext();) {
Java.CatchClause cc = (Java.CatchClause) it.next();
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 (int i = 0; i < lvds.variableDeclarators.length; ++i) {
Java.VariableDeclarator vd = lvds.variableDeclarators[i];
Java.LocalVariable lv = UnitCompiler.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;
}
protected void buildLocalVariableMap(CatchClause cc, Map localVars) throws CompileException {
Map vars = new HashMap();
vars.putAll(localVars);
LocalVariable lv = this.getLocalVariable(cc.caughtException);
vars.put(cc.caughtException.name, lv);
this.buildLocalVariableMap(cc.body, vars);
}
public Java.LocalVariable getLocalVariable(Java.FunctionDeclarator.FormalParameter fp) throws CompileException {
if (fp.localVariable == null) {
fp.localVariable = new Java.LocalVariable(fp.finaL, this.getType(fp.type));
}
return fp.localVariable;
}
// ------------------ Rvalue.compile() ----------------
/**
* Call to check whether the given {@link ArrayInitializerOrRvalue} compiles or not.
*/
private void fakeCompile(Java.ArrayInitializerOrRvalue aior) throws CompileException {
if (aior instanceof Java.Rvalue) {
Java.Rvalue rv = (Java.Rvalue) aior;
this.fakeCompile(rv);
}
if (aior instanceof Java.ArrayInitializer) {
Java.ArrayInitializer ai = (Java.ArrayInitializer) aior;
for (int i = 0; i < ai.values.length; ++i) {
fakeCompile(ai.values[i]);
}
}
}
/**
* Call to check whether the given {@link Java.Rvalue} compiles or not.
*/
private void fakeCompile(Java.Rvalue rv) throws CompileException {
CodeContext savedCodeContext = this.replaceCodeContext(this.createDummyCodeContext());
try {
this.compileContext(rv);
this.compileGet(rv);
} finally {
this.replaceCodeContext(savedCodeContext);
}
}
/**
* Some {@link Java.Rvalue}s compile more efficiently when their value
* is not needed, e.g. "i++".
*/
private void compile(Java.Rvalue rv) throws CompileException {
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.RvalueVisitor rvv = new Visitor.RvalueVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitArrayLength(Java.ArrayLength al) { try { UnitCompiler.this.compile2(al); } catch (CompileException e) { throw new UCE(e); } }
public void visitAssignment(Java.Assignment a) { try { UnitCompiler.this.compile2(a); } catch (CompileException e) { throw new UCE(e); } }
public void visitUnaryOperation(Java.UnaryOperation uo) { try { UnitCompiler.this.compile2(uo); } catch (CompileException e) { throw new UCE(e); } }
public void visitBinaryOperation(Java.BinaryOperation bo) { try { UnitCompiler.this.compile2(bo); } catch (CompileException e) { throw new UCE(e); } }
public void visitCast(Java.Cast c) { try { UnitCompiler.this.compile2(c); } catch (CompileException e) { throw new UCE(e); } }
public void visitClassLiteral(Java.ClassLiteral cl) { try { UnitCompiler.this.compile2(cl); } catch (CompileException e) { throw new UCE(e); } }
public void visitConditionalExpression(Java.ConditionalExpression ce) { try { UnitCompiler.this.compile2(ce); } catch (CompileException e) { throw new UCE(e); } }
public void visitCrement(Java.Crement c) { try { UnitCompiler.this.compile2(c); } catch (CompileException e) { throw new UCE(e); } }
public void visitInstanceof(Java.Instanceof io) { try { UnitCompiler.this.compile2(io); } catch (CompileException e) { throw new UCE(e); } }
public void visitMethodInvocation(Java.MethodInvocation mi) { try { UnitCompiler.this.compile2(mi); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { try { UnitCompiler.this.compile2(smi); } catch (CompileException e) { throw new UCE(e); } }
public void visitLiteral(Java.Literal l) { try { UnitCompiler.this.compile2(l); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { try { UnitCompiler.this.compile2(naci); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewArray(Java.NewArray na) { try { UnitCompiler.this.compile2(na); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { try { UnitCompiler.this.compile2(nia); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewClassInstance(Java.NewClassInstance nci) { try { UnitCompiler.this.compile2(nci); } catch (CompileException e) { throw new UCE(e); } }
public void visitParameterAccess(Java.ParameterAccess pa) { try { UnitCompiler.this.compile2(pa); } catch (CompileException e) { throw new UCE(e); } }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { try { UnitCompiler.this.compile2(qtr); } catch (CompileException e) { throw new UCE(e); } }
public void visitThisReference(Java.ThisReference tr) { try { UnitCompiler.this.compile2(tr); } catch (CompileException e) { throw new UCE(e); } }
public void visitAmbiguousName(Java.AmbiguousName an) { try { UnitCompiler.this.compile2(an); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { try { UnitCompiler.this.compile2(aae); } catch (CompileException e) { throw new UCE(e); } };
public void visitFieldAccess(Java.FieldAccess fa) { try { UnitCompiler.this.compile2(fa); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { try { UnitCompiler.this.compile2(fae); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compile2(scfae); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { try { UnitCompiler.this.compile2(lva); } catch (CompileException e) { throw new UCE(e); } }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { UnitCompiler.this.compile2(pe); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
rv.accept(rvv);
} catch (UCE uce) {
throw uce.ce;
}
}
private void compile2(Java.Rvalue rv) throws CompileException {
this.pop((Locatable) rv, this.compileGetValue(rv));
}
private void compile2(Java.Assignment a) throws CompileException {
if (a.operator == "=") {
this.compileContext(a.lhs);
this.assignmentConversion(
(Locatable) a, // l
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((Locatable) a, lhsCS);
IClass lhsType = this.compileGet(a.lhs);
IClass resultType = this.compileArithmeticBinaryOperation(
(Locatable) a, // l
lhsType, // lhsType
a.operator.substring( // operator
0,
a.operator.length() - 1
).intern(), // <= IMPORTANT!
a.rhs // rhs
);
// Convert the result to LHS type (JLS2 15.26.2).
if (
!this.tryIdentityConversion(resultType, lhsType) &&
!this.tryNarrowingPrimitiveConversion(
(Locatable) a, // l
resultType, // sourceType
lhsType // destinationType
)
) throw new JaninoRuntimeException("SNO: \"" + a.operator + "\" reconversion failed");
this.compileSet(a.lhs);
}
private void compile2(Java.Crement c) throws CompileException {
// Optimized crement of integer local variable.
Java.LocalVariable lv = this.isIntLV(c);
if (lv != null) {
compileLocalVariableCrement(c, lv);
return;
}
int cs = this.compileContext(c.operand);
this.dup((Locatable) c, cs);
IClass type = this.compileGet(c.operand);
IClass promotedType = this.unaryNumericPromotion((Locatable) c, type);
this.writeOpcode(c, UnitCompiler.ilfd(
promotedType,
Opcode.ICONST_1,
Opcode.LCONST_1,
Opcode.FCONST_1,
Opcode.DCONST_1
));
if (c.operator == "++") {
this.writeOpcode(c, Opcode.IADD + UnitCompiler.ilfd(promotedType));
} else
if (c.operator == "--") {
this.writeOpcode(c, Opcode.ISUB + UnitCompiler.ilfd(promotedType));
} else {
this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation());
}
this.reverseUnaryNumericPromotion((Locatable) c, promotedType, type);
this.compileSet(c.operand);
}
private void compile2(Java.ParenthesizedExpression pe) throws CompileException {
this.compile(pe.value);
}
private boolean compile2(Java.AlternateConstructorInvocation aci) throws CompileException {
Java.ConstructorDeclarator declaringConstructor = (Java.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(
(Locatable) aci, // l
(Java.Scope) declaringConstructor, // scope
(Java.Rvalue) null, // optionalEnclosingInstance
declaringIClass, // targetClass
aci.arguments // arguments
);
return true;
}
private boolean compile2(Java.SuperConstructorInvocation sci) throws CompileException {
Java.ConstructorDeclarator declaringConstructor = (Java.ConstructorDeclarator) sci.getEnclosingScope();
this.writeOpcode(sci, Opcode.ALOAD_0);
Java.ClassDeclaration declaringClass = declaringConstructor.getDeclaringClass();
IClass superclass = this.resolve(declaringClass).getSuperclass();
Java.Rvalue optionalEnclosingInstance;
if (sci.optionalQualification != null) {
optionalEnclosingInstance = sci.optionalQualification;
} else {
IClass outerIClassOfSuperclass = superclass.getOuterIClass();
if (outerIClassOfSuperclass == null) {
optionalEnclosingInstance = null;
} else {
optionalEnclosingInstance = new Java.QualifiedThisReference(
sci.getLocation(), // location
new Java.SimpleType( // qualification
sci.getLocation(),
outerIClassOfSuperclass
)
);
optionalEnclosingInstance.setEnclosingBlockStatement(sci);
}
}
this.invokeConstructor(
(Locatable) sci, // l
(Java.Scope) declaringConstructor, // scope
optionalEnclosingInstance, // optionalEnclosingInstance
superclass, // targetClass
sci.arguments // arguments
);
return true;
}
/**
* Some {@link Java.Rvalue}s compile more efficiently when their value is the condition for a branch.
* <p>
* Notice that if "this" is a constant, then either <code>dst</code> is never branched to, or it is unconditionally
* branched to. "Unexamined code" errors may result during bytecode validation.
*/
private void compileBoolean(
Java.Rvalue rv,
final CodeContext.Offset dst, // Where to jump.
final boolean orientation // JUMP_IF_TRUE or JUMP_IF_FALSE.
) throws CompileException {
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.RvalueVisitor rvv = new Visitor.RvalueVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitArrayLength(Java.ArrayLength al) { try { UnitCompiler.this.compileBoolean2(al, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitAssignment(Java.Assignment a) { try { UnitCompiler.this.compileBoolean2(a, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitUnaryOperation(Java.UnaryOperation uo) { try { UnitCompiler.this.compileBoolean2(uo, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitBinaryOperation(Java.BinaryOperation bo) { try { UnitCompiler.this.compileBoolean2(bo, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitCast(Java.Cast c) { try { UnitCompiler.this.compileBoolean2(c, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitClassLiteral(Java.ClassLiteral cl) { try { UnitCompiler.this.compileBoolean2(cl, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitConditionalExpression(Java.ConditionalExpression ce) { try { UnitCompiler.this.compileBoolean2(ce, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitCrement(Java.Crement c) { try { UnitCompiler.this.compileBoolean2(c, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitInstanceof(Java.Instanceof io) { try { UnitCompiler.this.compileBoolean2(io, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitMethodInvocation(Java.MethodInvocation mi) { try { UnitCompiler.this.compileBoolean2(mi, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { try { UnitCompiler.this.compileBoolean2(smi, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitLiteral(Java.Literal l) { try { UnitCompiler.this.compileBoolean2(l, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { try { UnitCompiler.this.compileBoolean2(naci, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewArray(Java.NewArray na) { try { UnitCompiler.this.compileBoolean2(na, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { try { UnitCompiler.this.compileBoolean2(nia, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewClassInstance(Java.NewClassInstance nci) { try { UnitCompiler.this.compileBoolean2(nci, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitParameterAccess(Java.ParameterAccess pa) { try { UnitCompiler.this.compileBoolean2(pa, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { try { UnitCompiler.this.compileBoolean2(qtr, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitThisReference(Java.ThisReference tr) { try { UnitCompiler.this.compileBoolean2(tr, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitAmbiguousName(Java.AmbiguousName an) { try { UnitCompiler.this.compileBoolean2(an, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { try { UnitCompiler.this.compileBoolean2(aae, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccess(Java.FieldAccess fa) { try { UnitCompiler.this.compileBoolean2(fa, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { try { UnitCompiler.this.compileBoolean2(fae, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compileBoolean2(scfae, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { try { UnitCompiler.this.compileBoolean2(lva, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { UnitCompiler.this.compileBoolean2(pe, dst, orientation); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
rv.accept(rvv);
} catch (UCE uce) {
throw uce.ce;
}
}
private void compileBoolean2(
Java.Rvalue rv,
CodeContext.Offset dst, // Where to jump.
boolean orientation // JUMP_IF_TRUE or JUMP_IF_FALSE.
) throws CompileException {
IClass type = this.compileGetValue(rv);
IClassLoader icl = this.iClassLoader;
if (type == icl.BOOLEAN) {
this.unboxingConversion((Locatable) rv, icl.BOOLEAN, IClass.BOOLEAN);
} else
if (type != IClass.BOOLEAN) {
this.compileError("Not a boolean expression", rv.getLocation());
}
this.writeBranch(rv, orientation == Java.Rvalue.JUMP_IF_TRUE ? Opcode.IFNE : Opcode.IFEQ, dst);
}
private void compileBoolean2(
Java.UnaryOperation ue,
CodeContext.Offset dst, // Where to jump.
boolean orientation // JUMP_IF_TRUE or JUMP_IF_FALSE.
) throws CompileException {
if (ue.operator == "!") {
this.compileBoolean(ue.operand, dst, !orientation);
return;
}
this.compileError("Boolean expression expected", ue.getLocation());
}
private void compileBoolean2(
Java.BinaryOperation bo,
CodeContext.Offset dst, // Where to jump.
boolean orientation // JUMP_IF_TRUE or JUMP_IF_FALSE.
) throws CompileException {
if (bo.op == "|" || bo.op == "^" || bo.op == "&") {
this.compileBoolean2((Java.Rvalue) bo, dst, orientation);
return;
}
if (bo.op == "||" || bo.op == "&&") {
Object lhsCV = this.getConstantValue(bo.lhs);
if (lhsCV instanceof Boolean) {
if (((Boolean) lhsCV).booleanValue() ^ bo.op == "||") {
// "true && a", "false || a"
this.compileBoolean(
bo.rhs,
dst,
Java.Rvalue.JUMP_IF_TRUE ^ orientation == Java.Rvalue.JUMP_IF_FALSE
);
} else {
// "false && a", "true || a"
this.compileBoolean(
bo.lhs,
dst,
Java.Rvalue.JUMP_IF_TRUE ^ orientation == Java.Rvalue.JUMP_IF_FALSE
);
this.fakeCompile(bo.rhs);
}
return;
}
Object rhsCV = this.getConstantValue(bo.rhs);
if (rhsCV instanceof Boolean) {
if (((Boolean) rhsCV).booleanValue() ^ bo.op == "||") {
// "a && true", "a || false"
this.compileBoolean(
bo.lhs,
dst,
Java.Rvalue.JUMP_IF_TRUE ^ orientation == Java.Rvalue.JUMP_IF_FALSE
);
} else {
// "a && false", "a || true"
this.pop((Locatable) bo.lhs, this.compileGetValue(bo.lhs));
this.compileBoolean(
bo.rhs,
dst,
Java.Rvalue.JUMP_IF_TRUE ^ orientation == Java.Rvalue.JUMP_IF_FALSE
);
}
return;
}
if (bo.op == "||" ^ orientation == Java.Rvalue.JUMP_IF_FALSE) {
this.compileBoolean(bo.lhs, dst, Java.Rvalue.JUMP_IF_TRUE ^ orientation == Java.Rvalue.JUMP_IF_FALSE);
this.compileBoolean(bo.rhs, dst, Java.Rvalue.JUMP_IF_TRUE ^ orientation == Java.Rvalue.JUMP_IF_FALSE);
} else {
CodeContext.Offset end = this.codeContext.new Offset();
this.compileBoolean(bo.lhs, end, Java.Rvalue.JUMP_IF_FALSE ^ orientation == Java.Rvalue.JUMP_IF_FALSE);
this.compileBoolean(bo.rhs, dst, Java.Rvalue.JUMP_IF_TRUE ^ orientation == Java.Rvalue.JUMP_IF_FALSE);
end.set();
}
return;
}
if (
bo.op == "==" ||
bo.op == "!=" ||
bo.op == "<=" ||
bo.op == ">=" ||
bo.op == "<" ||
bo.op == ">"
) {
int opIdx = (
bo.op == "==" ? 0 :
bo.op == "!=" ? 1 :
bo.op == "<" ? 2 :
bo.op == ">=" ? 3 :
bo.op == ">" ? 4 :
bo.op == "<=" ? 5 : Integer.MIN_VALUE
);
if (orientation == Java.Rvalue.JUMP_IF_FALSE) opIdx ^= 1;
// Comparison with "null".
{
boolean lhsIsNull = this.getConstantValue(bo.lhs) == Java.Rvalue.CONSTANT_VALUE_NULL;
boolean rhsIsNull = this.getConstantValue(bo.rhs) == Java.Rvalue.CONSTANT_VALUE_NULL;
if (lhsIsNull || rhsIsNull) {
if (bo.op != "==" && bo.op != "!=") {
this.compileError(
"Operator \"" + bo.op + "\" not allowed on operand \"null\"",
bo.getLocation()
);
}
// check types for x == null, null == x, but NOT null == null
if (!lhsIsNull || !rhsIsNull) {
IClass ohsType = this.compileGetValue(lhsIsNull ? bo.rhs : bo.lhs);
if (ohsType.isPrimitive()) {
this.compileError(
"Cannot compare \"null\" with primitive type \"" + ohsType.toString() + "\"",
bo.getLocation()
);
}
}
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 == "!=") && !lhsType.isPrimitive() && !rhsType.isPrimitive())
) {
IClass promotedType = this.binaryNumericPromotion((Locatable) 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 == ">=") {
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 == ">=") {
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;
}
// JLS3 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 != "!=") {
this.compileError("Operator \"" + bo.op + "\" not allowed on boolean operands", bo.getLocation());
}
IClassLoader icl = this.iClassLoader;
// Unbox LHS if necessary.
if (lhsType == icl.BOOLEAN) {
this.codeContext.pushInserter(convertLhsInserter);
try {
this.unboxingConversion((Locatable) bo, icl.BOOLEAN, IClass.BOOLEAN);
} finally {
this.codeContext.popInserter();
}
}
// Unbox RHS if necessary.
if (rhsType == icl.BOOLEAN) {
this.unboxingConversion((Locatable) bo, icl.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 != "!=") {
this.compileError("Operator \"" + bo.op + "\" not allowed on reference operands", 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());
}
private void compileBoolean2(
Java.ParenthesizedExpression pe,
CodeContext.Offset dst,
boolean orientation
) throws CompileException {
this.compileBoolean(pe.value, dst, orientation);
}
/**
* Generates code that determines the context of the {@link
* Java.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(Java.Rvalue rv) throws CompileException {
final int[] res = new int[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.RvalueVisitor rvv = new Visitor.RvalueVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitArrayLength(Java.ArrayLength al) { try { res[0] = UnitCompiler.this.compileContext2(al); } catch (CompileException e) { throw new UCE(e); } }
public void visitAssignment(Java.Assignment a) { res[0] = UnitCompiler.this.compileContext2(a); }
public void visitUnaryOperation(Java.UnaryOperation uo) { res[0] = UnitCompiler.this.compileContext2(uo); }
public void visitBinaryOperation(Java.BinaryOperation bo) { res[0] = UnitCompiler.this.compileContext2(bo); }
public void visitCast(Java.Cast c) { res[0] = UnitCompiler.this.compileContext2(c); }
public void visitClassLiteral(Java.ClassLiteral cl) { res[0] = UnitCompiler.this.compileContext2(cl); }
public void visitConditionalExpression(Java.ConditionalExpression ce) { res[0] = UnitCompiler.this.compileContext2(ce); }
public void visitCrement(Java.Crement c) { res[0] = UnitCompiler.this.compileContext2(c); }
public void visitInstanceof(Java.Instanceof io) { res[0] = UnitCompiler.this.compileContext2(io); }
public void visitMethodInvocation(Java.MethodInvocation mi) { res[0] = UnitCompiler.this.compileContext2(mi); }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.compileContext2(smi); }
public void visitLiteral(Java.Literal l) { res[0] = UnitCompiler.this.compileContext2(l); }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.compileContext2(naci); }
public void visitNewArray(Java.NewArray na) { res[0] = UnitCompiler.this.compileContext2(na); }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { res[0] = UnitCompiler.this.compileContext2(nia); }
public void visitNewClassInstance(Java.NewClassInstance nci) { res[0] = UnitCompiler.this.compileContext2(nci); }
public void visitParameterAccess(Java.ParameterAccess pa) { res[0] = UnitCompiler.this.compileContext2(pa); }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { res[0] = UnitCompiler.this.compileContext2(qtr); }
public void visitThisReference(Java.ThisReference tr) { res[0] = UnitCompiler.this.compileContext2(tr); }
public void visitAmbiguousName(Java.AmbiguousName an) { try { res[0] = UnitCompiler.this.compileContext2(an); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.compileContext2(aae); } catch (CompileException e) { throw new UCE(e); } };
public void visitFieldAccess(Java.FieldAccess fa) { try { res[0] = UnitCompiler.this.compileContext2(fa); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.compileContext2(fae); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.compileContext2(scfae); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { res[0] = UnitCompiler.this.compileContext2(lva); }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.compileContext2(pe); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
rv.accept(rvv);
return res[0];
} catch (UCE uce) {
throw uce.ce;
}
}
private int compileContext2(Java.Rvalue rv) {
return 0;
}
private int compileContext2(Java.AmbiguousName an) throws CompileException {
return this.compileContext(this.toRvalueOrCE(this.reclassify(an)));
}
private int compileContext2(Java.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()
);
// JLS3 15.11.1.3.1.1:
this.pop(fa.lhs, this.compileGetValue(rv));
}
return 0;
} else {
this.compileGetValue(this.toRvalueOrCE(fa.lhs));
return 1;
}
}
private int compileContext2(Java.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(Java.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(
(Locatable) aae, // l
indexType, // sourceType
IClass.INT // targetType
)
) this.compileError(
"Index expression of type \"" + indexType + "\" cannot be widened to \"int\"",
aae.getLocation()
);
return 2;
}
private int compileContext2(Java.FieldAccessExpression fae) throws CompileException {
this.determineValue(fae);
return this.compileContext(fae.value);
}
private int compileContext2(Java.SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
return this.compileContext(scfae.value);
}
private int compileContext2(Java.ParenthesizedExpression pe) throws CompileException {
return this.compileContext(pe.value);
}
/**
* Generates code that determines the value of the {@link Java.Rvalue}
* and puts it on the operand stack. This method relies on that the
* "context" of the {@link Java.Rvalue} is on top of the operand stack
* (see {@link #compileContext(Java.Rvalue)}).
*
* @return The type of the {@link Java.Rvalue}
*/
private IClass compileGet(Java.Rvalue rv) throws CompileException {
final IClass[] res = new IClass[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.RvalueVisitor rvv = new Visitor.RvalueVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitArrayLength(Java.ArrayLength al) { res[0] = UnitCompiler.this.compileGet2(al); }
public void visitAssignment(Java.Assignment a) { try { res[0] = UnitCompiler.this.compileGet2(a); } catch (CompileException e) { throw new UCE(e); } }
public void visitUnaryOperation(Java.UnaryOperation uo) { try { res[0] = UnitCompiler.this.compileGet2(uo); } catch (CompileException e) { throw new UCE(e); } }
public void visitBinaryOperation(Java.BinaryOperation bo) { try { res[0] = UnitCompiler.this.compileGet2(bo); } catch (CompileException e) { throw new UCE(e); } }
public void visitCast(Java.Cast c) { try { res[0] = UnitCompiler.this.compileGet2(c); } catch (CompileException e) { throw new UCE(e); } }
public void visitClassLiteral(Java.ClassLiteral cl) { try { res[0] = UnitCompiler.this.compileGet2(cl); } catch (CompileException e) { throw new UCE(e); } }
public void visitConditionalExpression(Java.ConditionalExpression ce) { try { res[0] = UnitCompiler.this.compileGet2(ce); } catch (CompileException e) { throw new UCE(e); } }
public void visitCrement(Java.Crement c) { try { res[0] = UnitCompiler.this.compileGet2(c); } catch (CompileException e) { throw new UCE(e); } }
public void visitInstanceof(Java.Instanceof io) { try { res[0] = UnitCompiler.this.compileGet2(io); } catch (CompileException e) { throw new UCE(e); } }
public void visitMethodInvocation(Java.MethodInvocation mi) { try { res[0] = UnitCompiler.this.compileGet2(mi); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { try { res[0] = UnitCompiler.this.compileGet2(smi); } catch (CompileException e) { throw new UCE(e); } }
public void visitLiteral(Java.Literal l) { try { res[0] = UnitCompiler.this.compileGet2(l); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { try { res[0] = UnitCompiler.this.compileGet2(naci); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewArray(Java.NewArray na) { try { res[0] = UnitCompiler.this.compileGet2(na); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { try { res[0] = UnitCompiler.this.compileGet2(nia); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewClassInstance(Java.NewClassInstance nci) { try { res[0] = UnitCompiler.this.compileGet2(nci); } catch (CompileException e) { throw new UCE(e); } }
public void visitParameterAccess(Java.ParameterAccess pa) { try { res[0] = UnitCompiler.this.compileGet2(pa); } catch (CompileException e) { throw new UCE(e); } }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { try { res[0] = UnitCompiler.this.compileGet2(qtr); } catch (CompileException e) { throw new UCE(e); } }
public void visitThisReference(Java.ThisReference tr) { try { res[0] = UnitCompiler.this.compileGet2(tr); } catch (CompileException e) { throw new UCE(e); } }
public void visitAmbiguousName(Java.AmbiguousName an) { try { res[0] = UnitCompiler.this.compileGet2(an); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.compileGet2(aae); } catch (CompileException e) { throw new UCE(e); } };
public void visitFieldAccess(Java.FieldAccess fa) { try { res[0] = UnitCompiler.this.compileGet2(fa); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.compileGet2(fae); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.compileGet2(scfae); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { res[0] = UnitCompiler.this.compileGet2(lva); }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.compileGet2(pe); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
rv.accept(rvv);
return res[0];
} catch (UCE uce) {
throw uce.ce;
}
}
private IClass compileGet2(Java.BooleanRvalue brv) throws CompileException {
CodeContext.Offset isTrue = this.codeContext.new Offset();
this.compileBoolean(brv, isTrue, Java.Rvalue.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(Java.AmbiguousName an) throws CompileException {
return this.compileGet(this.toRvalueOrCE(this.reclassify(an)));
}
private IClass compileGet2(Java.LocalVariableAccess lva) {
return this.load((Locatable) lva, lva.localVariable);
}
private IClass compileGet2(Java.FieldAccess fa) throws CompileException {
this.checkAccessible(fa.field, fa.getEnclosingBlockStatement());
if (fa.field.isStatic()) {
this.writeOpcode(fa, Opcode.GETSTATIC);
} else {
this.writeOpcode(fa, Opcode.GETFIELD);
}
this.writeConstantFieldrefInfo(
fa.field.getDeclaringIClass().getDescriptor(),
fa.field.getName(), // classFD
fa.field.getType().getDescriptor() // fieldFD
);
return fa.field.getType();
}
private IClass compileGet2(Java.ArrayLength al) {
this.writeOpcode(al, Opcode.ARRAYLENGTH);
return IClass.INT;
}
private IClass compileGet2(Java.ThisReference tr) throws CompileException {
this.referenceThis((Locatable) tr);
return this.getIClass(tr);
}
private IClass compileGet2(Java.QualifiedThisReference qtr) throws CompileException {
this.referenceThis(
(Locatable) qtr, // l
this.getDeclaringClass(qtr), // declaringClass
this.getDeclaringTypeBodyDeclaration(qtr), // declaringTypeBodyDeclaration
this.getTargetIClass(qtr) // targetIClass
);
return this.getTargetIClass(qtr);
}
private IClass compileGet2(Java.ClassLiteral cl) throws CompileException {
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,
"TYPE", // classFD
"Ljava/lang/Class;" // fieldFD
);
return icl.CLASS;
}
// Non-primitive class literal.
Java.TypeDeclaration declaringType;
for (Java.Scope s = cl.getEnclosingBlockStatement();; s = s.getEnclosingScope()) {
if (s instanceof Java.TypeDeclaration) {
declaringType = (Java.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 statics; // TypeBodyDeclaration
if (declaringType instanceof Java.ClassDeclaration) {
statics = ((Java.ClassDeclaration) declaringType).variableDeclaratorsAndInitializers;
} else
if (declaringType instanceof Java.InterfaceDeclaration) {
statics = ((Java.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.
{
boolean hasClassDollarField = false;
BLOCK_STATEMENTS: for (Iterator it = statics.iterator(); it.hasNext();) {
Java.TypeBodyDeclaration tbd = (Java.TypeBodyDeclaration) it.next();
if (!tbd.isStatic()) continue;
if (tbd instanceof Java.FieldDeclaration) {
Java.FieldDeclaration fd = (Java.FieldDeclaration) tbd;
IClass.IField[] fds = this.getIFields(fd);
for (int j = 0; j < fds.length; ++j) {
if (fds[j].getName().equals(classDollarFieldName)) {
hasClassDollarField = true;
break BLOCK_STATEMENTS;
}
}
}
}
if (!hasClassDollarField) {
Java.Type classType = new Java.SimpleType(loc, icl.CLASS);
Java.FieldDeclaration fd = new Java.FieldDeclaration(
loc, // location
null, // optionalDocComment
Mod.STATIC, // modifiers
classType, // type
new Java.VariableDeclarator[] { // variableDeclarators
new Java.VariableDeclarator(
loc, // location
classDollarFieldName, // name
0, // brackets
(Java.Rvalue) null // optionalInitializer
)
}
);
if (declaringType instanceof Java.ClassDeclaration) {
((Java.ClassDeclaration) declaringType).addVariableDeclaratorOrInitializer(fd);
} else
if (declaringType instanceof Java.InterfaceDeclaration) {
((Java.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"));
Java.Type declaringClassOrInterfaceType = new Java.SimpleType(loc, this.resolve(declaringType));
Java.Lvalue classDollarFieldAccess = new Java.FieldAccessExpression(
loc, // location
declaringClassOrInterfaceType, // lhs
classDollarFieldName // fieldName
);
Java.ConditionalExpression ce = new Java.ConditionalExpression(
loc, // location
new Java.BinaryOperation( // lhs
loc, // location
classDollarFieldAccess, // lhs
"!=", // op
new Java.Literal(loc, null) // rhs
),
classDollarFieldAccess, // mhs
new Java.Assignment( // rhs
loc, // location
classDollarFieldAccess, // lhs
"=", // operator
new Java.MethodInvocation( // rhs
loc, // location
declaringClassOrInterfaceType, // optionalTarget
"class$", // methodName
new Java.Rvalue[] { // arguments
new Java.Literal(
loc, // location
className // constantValue
)
}
)
)
);
ce.setEnclosingBlockStatement(cl.getEnclosingBlockStatement());
return this.compileGet(ce);
}
private IClass compileGet2(Java.Assignment a) throws CompileException {
if (a.operator == "=") {
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(
(Locatable) a, // l
rhsType, // sourceType
lhsType, // targetType
rhsCV // optionalConstantValue
);
this.dupx(
(Locatable) a, // l
lhsType, // type
lhsCS // x
);
this.compileSet(a.lhs);
return lhsType;
}
// Implement "|= ^= &= *= /= %= += -= <<= >>= >>>=".
int lhsCS = this.compileContext(a.lhs);
this.dup((Locatable) a, lhsCS);
IClass lhsType = this.compileGet(a.lhs);
IClass resultType = this.compileArithmeticBinaryOperation(
(Locatable) a, // l
lhsType, // lhsType
a.operator.substring( // operator
0,
a.operator.length() - 1
).intern(), // <= IMPORTANT!
a.rhs // rhs
);
// Convert the result to LHS type (JLS2 15.26.2).
if (
!this.tryIdentityConversion(resultType, lhsType) &&
!this.tryNarrowingPrimitiveConversion(
(Locatable) a, // l
resultType, // sourceType
lhsType // destinationType
)
) throw new JaninoRuntimeException("SNO: \"" + a.operator + "\" reconversion failed");
this.dupx(
(Locatable) a, // l
lhsType, // type
lhsCS // x
);
this.compileSet(a.lhs);
return lhsType;
}
private IClass compileGet2(Java.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, Java.Rvalue.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) {
// JLS 15.25.1.1
expressionType = mhsType;
} else
if (mhsType.isPrimitiveNumeric() && rhsType.isPrimitiveNumeric()) {
// JLS 15.25.1.2
// TODO JLS 15.25.1.2.1
// TODO JLS 15.25.1.2.2
// JLS 15.25.1.2.3
expressionType = this.binaryNumericPromotion(
(Locatable) ce, // l
mhsType, // type1
mhsConvertInserter, // convertInserter1
rhsType, // type2
rhsConvertInserter // convertInserter2
);
} else
if (this.getConstantValue(ce.mhs) == Java.Rvalue.CONSTANT_VALUE_NULL && !rhsType.isPrimitive()) {
// JLS 15.25.1.3 (null : reference)
expressionType = rhsType;
} else
if (!mhsType.isPrimitive() && this.getConstantValue(ce.rhs) == Java.Rvalue.CONSTANT_VALUE_NULL) {
// JLS 15.25.1.3 (reference : null)
expressionType = mhsType;
} else
if (!mhsType.isPrimitive() && !rhsType.isPrimitive()) {
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.OBJECT;
}
} else
{
this.compileError(
"Incompatible expression types \"" + mhsType + "\" and \"" + rhsType + "\"",
ce.getLocation()
);
return this.iClassLoader.OBJECT;
}
toEnd.set();
return expressionType;
}
private IClass compileGet2(Java.Crement c) throws CompileException {
// Optimized crement of integer local variable.
Java.LocalVariable lv = this.isIntLV(c);
if (lv != null) {
if (!c.pre) this.load((Locatable) c, lv);
compileLocalVariableCrement(c, lv);
if (c.pre) this.load((Locatable) c, lv);
return lv.type;
}
// Compile operand context.
int cs = this.compileContext(c.operand);
// DUP operand context.
this.dup((Locatable) c, cs);
// Get operand value.
IClass type = this.compileGet(c.operand);
// DUPX operand value.
if (!c.pre) this.dupx((Locatable) c, type, cs);
// Apply "unary numeric promotion".
IClass promotedType = this.unaryNumericPromotion((Locatable) c, type);
// Crement.
this.writeOpcode(c, UnitCompiler.ilfd(
promotedType,
Opcode.ICONST_1,
Opcode.LCONST_1,
Opcode.FCONST_1,
Opcode.DCONST_1
));
if (c.operator == "++") {
this.writeOpcode(c, Opcode.IADD + UnitCompiler.ilfd(promotedType));
} else
if (c.operator == "--") {
this.writeOpcode(c, Opcode.ISUB + UnitCompiler.ilfd(promotedType));
} else {
this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation());
}
this.reverseUnaryNumericPromotion((Locatable) c, promotedType, type);
// DUPX cremented operand value.
if (c.pre) this.dupx((Locatable) c, type, cs);
// Set operand.
this.compileSet(c.operand);
return type;
}
private void compileLocalVariableCrement(Java.Crement c, Java.LocalVariable lv) {
if (lv.getSlotIndex() > 255) {
this.writeOpcode(c, Opcode.WIDE);
this.writeOpcode(c, Opcode.IINC);
this.writeShort(lv.getSlotIndex());
this.writeShort(c.operator == "++" ? 1 : -1);
} else {
this.writeOpcode(c, Opcode.IINC);
this.writeByte(lv.getSlotIndex());
this.writeByte(c.operator == "++" ? 1 : -1);
}
}
private IClass compileGet2(Java.ArrayAccessExpression aae) throws CompileException {
IClass lhsComponentType = this.getType(aae);
this.writeOpcode(aae, Opcode.IALOAD + UnitCompiler.ilfdabcs(lhsComponentType));
return lhsComponentType;
}
private IClass compileGet2(Java.FieldAccessExpression fae) throws CompileException {
this.determineValue(fae);
return this.compileGet(fae.value);
}
private IClass compileGet2(Java.SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
return this.compileGet(scfae.value);
}
private IClass compileGet2(Java.UnaryOperation uo) throws CompileException {
if (uo.operator == "!") {
return this.compileGet2((Java.BooleanRvalue) uo);
}
if (uo.operator == "+") {
return this.unaryNumericPromotion(
(Locatable) uo,
this.convertToPrimitiveNumericType((Locatable) uo, this.compileGetValue(uo.operand))
);
}
if (uo.operator == "-") {
// Special handling for negated literals.
if (uo.operand instanceof Java.Literal) {
Java.Literal l = (Java.Literal) uo.operand;
this.pushConstant((Locatable) uo, this.getNegatedConstantValue2(l));
return this.unaryNumericPromotion((Locatable) uo, this.getType2(l));
}
IClass promotedType = this.unaryNumericPromotion(
(Locatable) uo,
this.convertToPrimitiveNumericType((Locatable) uo, this.compileGetValue(uo.operand))
);
this.writeOpcode(uo, Opcode.INEG + UnitCompiler.ilfd(promotedType));
return promotedType;
}
if (uo.operator == "~") {
IClass operandType = this.compileGetValue(uo.operand);
IClass promotedType = this.unaryNumericPromotion((Locatable) 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.OBJECT;
}
private IClass compileGet2(Java.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(Java.BinaryOperation bo) throws CompileException {
if (
bo.op == "||" ||
bo.op == "&&" ||
bo.op == "==" ||
bo.op == "!=" ||
bo.op == "<" ||
bo.op == ">" ||
bo.op == "<=" ||
bo.op == ">="
) {
// Eventually calls "compileBoolean()".
return this.compileGet2((Java.BooleanRvalue) bo);
}
// Implements "| ^ & * / % + - << >> >>>".
return this.compileArithmeticOperation(
(Locatable) bo, // l
null, // type
bo.unrollLeftAssociation(), // operands
bo.op // operator
);
}
private IClass compileGet2(Java.Cast c) throws CompileException {
// JLS3 5.5 Casting Conversion
IClass tt = this.getType(c.targetType);
IClass vt = this.compileGetValue(c.value);
if (
!this.tryIdentityConversion(vt, tt) &&
!this.tryWideningPrimitiveConversion((Locatable) c, vt, tt) &&
!this.tryNarrowingPrimitiveConversion((Locatable) c, vt, tt) &&
!this.tryWideningReferenceConversion(vt, tt) &&
!this.tryNarrowingReferenceConversion((Locatable) c, vt, tt) &&
!this.tryBoxingConversion((Locatable) c, vt, tt) &&
!this.tryUnboxingConversion((Locatable) c, vt, tt)
) this.compileError("Cannot cast \"" + vt + "\" to \"" + tt + "\"", c.getLocation());
return tt;
}
private IClass compileGet2(Java.ParenthesizedExpression pe) throws CompileException {
return this.compileGet(pe.value);
}
private IClass compileGet2(Java.MethodInvocation mi) throws CompileException {
IClass.IMethod iMethod = this.findIMethod(mi);
if (mi.optionalTarget == null) {
// JLS2 6.5.7.1, 15.12.4.1.1.1
Java.TypeBodyDeclaration scopeTBD;
Java.ClassDeclaration scopeClassDeclaration;
{
Java.Scope s;
for (
s = mi.getEnclosingBlockStatement();
!(s instanceof Java.TypeBodyDeclaration);
s = s.getEnclosingScope()
);
scopeTBD = (Java.TypeBodyDeclaration) s;
if (!(s instanceof Java.ClassDeclaration)) s = s.getEnclosingScope();
scopeClassDeclaration = (Java.ClassDeclaration) s;
}
if (iMethod.isStatic()) {
this.warning(
"IASM",
"Implicit access to static method \"" + iMethod.toString() + "\"",
mi.getLocation()
);
// JLS2 15.12.4.1.1.1.1
;
} else {
this.warning(
"IANSM",
"Implicit access to non-static method \"" + iMethod.toString() + "\"",
mi.getLocation()
);
// JLS2 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(
(Locatable) mi, // l
scopeClassDeclaration, // declaringClass
scopeTBD, // declaringTypeBodyDeclaration
iMethod.getDeclaringIClass() // targetIClass
);
}
} else {
// 6.5.7.2
boolean staticContext = this.isType(mi.optionalTarget);
if (staticContext) {
this.getType(this.toTypeOrCE(mi.optionalTarget));
} else
{
this.compileGetValue(this.toRvalueOrCE(mi.optionalTarget));
}
if (iMethod.isStatic()) {
if (!staticContext) {
// JLS2 15.12.4.1.2.1
this.pop((Locatable) 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.
IClass[] parameterTypes = iMethod.getParameterTypes();
for (int i = 0; i < mi.arguments.length; ++i) {
this.assignmentConversion(
(Locatable) mi, // l
this.compileGetValue(mi.arguments[i]), // sourceType
parameterTypes[i], // targetType
this.getConstantValue(mi.arguments[i]) // optionalConstantValue
);
}
// Invoke!
this.checkAccessible(iMethod, mi.getEnclosingBlockStatement());
if (iMethod.getDeclaringIClass().isInterface()) {
this.writeOpcode(mi, Opcode.INVOKEINTERFACE);
this.writeConstantInterfaceMethodrefInfo(
iMethod.getDeclaringIClass().getDescriptor(), // locatable
iMethod.getName(), // classFD
iMethod.getDescriptor() // methodMD
);
IClass[] pts = iMethod.getParameterTypes();
int count = 1;
for (int i = 0; i < pts.length; ++i) count += Descriptor.size(pts[i].getDescriptor());
this.writeByte(count);
this.writeByte(0);
} 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(), // locatable
iMethod.getName() + '$', // classFD
MethodDescriptor.prependParameter( // methodMD
iMethod.getDescriptor(),
iMethod.getDeclaringIClass().getDescriptor()
)
);
} else
{
byte opcode = iMethod.isStatic() ? Opcode.INVOKESTATIC : Opcode.INVOKEVIRTUAL;
this.writeOpcode(mi, opcode);
this.writeConstantMethodrefInfo(
iMethod.getDeclaringIClass().getDescriptor(), // classFD
iMethod.getName(), // methodName
iMethod.getDescriptor() // methodMD
);
}
}
return iMethod.getReturnType();
}
private IClass compileGet2(Java.SuperclassMethodInvocation scmi) throws CompileException {
IClass.IMethod iMethod = this.findIMethod(scmi);
Java.Scope s;
for (
s = scmi.getEnclosingBlockStatement();
s instanceof Java.Statement || s instanceof Java.CatchClause;
s = s.getEnclosingScope()
);
Java.FunctionDeclarator fd = s instanceof Java.FunctionDeclarator ? (Java.FunctionDeclarator) s : null;
if (fd == null) {
this.compileError("Cannot invoke superclass method in non-method scope", scmi.getLocation());
return IClass.INT;
}
if ((fd.modifiers & Mod.STATIC) != 0) {
this.compileError("Cannot invoke superclass method in static context", scmi.getLocation());
}
this.load((Locatable) scmi, this.resolve(fd.getDeclaringType()), 0);
// Evaluate method parameters.
IClass[] parameterTypes = iMethod.getParameterTypes();
for (int i = 0; i < scmi.arguments.length; ++i) {
this.assignmentConversion(
(Locatable) scmi, // l
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(Java.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.
Java.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 (JLS 15.9.2.BL1.B3.B2).
optionalEnclosingInstance = nci.optionalQualification;
} else {
Java.Scope s = nci.getEnclosingBlockStatement();
for (; !(s instanceof Java.TypeBodyDeclaration); s = s.getEnclosingScope());
Java.TypeBodyDeclaration enclosingTypeBodyDeclaration = (Java.TypeBodyDeclaration) s;
Java.TypeDeclaration enclosingTypeDeclaration = (Java.TypeDeclaration) s.getEnclosingScope();
if (
!(enclosingTypeDeclaration instanceof Java.ClassDeclaration)
|| enclosingTypeBodyDeclaration.isStatic()
) {
// No enclosing instance in
// + interface method declaration or
// + static type body declaration (here: method or initializer or field declarator)
// context (JLS 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 (JLS
// 15.9.2.BL1.B3.B1.B2).
// Java.ClassDeclaration outerClassDeclaration = (Java.ClassDeclaration) enclosingTypeDeclaration;
// optionalEnclosingInstance = new Java.QualifiedThisReference(
// nci.getLocation(), // location
// outerClassDeclaration, // declaringClass
// enclosingTypeBodyDeclaration, // declaringTypeBodyDeclaration
// optionalOuterIClass // targetIClass
// );
optionalEnclosingInstance = new Java.QualifiedThisReference(
nci.getLocation(), // location
new Java.SimpleType( // qualification
nci.getLocation(),
optionalOuterIClass
)
);
optionalEnclosingInstance.setEnclosingBlockStatement(nci.getEnclosingBlockStatement());
}
}
}
this.invokeConstructor(
(Locatable) nci, // l
nci.getEnclosingBlockStatement(), // scope
optionalEnclosingInstance, // optionalEnclosingInstance
nci.iClass, // targetClass
nci.arguments // arguments
);
return nci.iClass;
}
private IClass compileGet2(Java.NewAnonymousClassInstance naci) throws CompileException {
// Find constructors.
Java.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(
(Locatable) naci, // l
iConstructors, // iInvocables
naci.arguments, // arguments
acd
);
IClass[] pts = iConstructor.getParameterTypes();
// Determine formal parameters of anonymous constructor.
Java.FunctionDeclarator.FormalParameter[] fps;
Location loc = naci.getLocation();
{
List l = new ArrayList(); // FormalParameter
// Pass the enclosing instance of the base class as parameter #1.
if (naci.optionalQualification != null) l.add(new Java.FunctionDeclarator.FormalParameter(
loc, // location
true, // finaL
new Java.SimpleType(loc, this.getType(naci.optionalQualification)), // type
"this$base" // name
));
for (int i = 0; i < pts.length; ++i) l.add(new Java.FunctionDeclarator.FormalParameter(
loc, // location
true, // finaL
new Java.SimpleType(loc, pts[i]), // type
"p" + i // name
));
fps = (Java.FunctionDeclarator.FormalParameter[]) l.toArray(
new Java.FunctionDeclarator.FormalParameter[l.size()]
);
}
// Determine thrown exceptions of anonymous constructor.
IClass[] tes = iConstructor.getThrownExceptions();
Java.Type[] tets = new Java.Type[tes.length];
for (int i = 0; i < tes.length; ++i) tets[i] = new Java.SimpleType(loc, tes[i]);
// The anonymous constructor merely invokes the constructor of its superclass.
int j = 0;
Java.Rvalue optionalQualificationAccess;
if (naci.optionalQualification == null) {
optionalQualificationAccess = null;
} else
{
optionalQualificationAccess = new Java.ParameterAccess(loc, fps[j++]);
}
Java.Rvalue[] parameterAccesses = new Java.Rvalue[pts.length];
for (int i = 0; i < pts.length; ++i) {
parameterAccesses[i] = new Java.ParameterAccess(loc, fps[j++]);
}
// Generate the anonymous constructor for the anonymous class (JLS 15.9.5.1).
Java.ConstructorDeclarator anonymousConstructor = new Java.ConstructorDeclarator(
loc, // location
null, // optionalDocComment
Mod.PACKAGE, // modifiers
fps, // formalParameters
tets, // thrownExceptions
new Java.SuperConstructorInvocation( // optionalExplicitConstructorInvocation
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());
// Invoke the anonymous constructor.
this.writeOpcode(naci, Opcode.DUP);
Java.Rvalue[] arguments2;
if (naci.optionalQualification == null) {
arguments2 = naci.arguments;
} else {
arguments2 = new Java.Rvalue[naci.arguments.length + 1];
arguments2[0] = naci.optionalQualification;
System.arraycopy(naci.arguments, 0, arguments2, 1, naci.arguments.length);
}
// Notice: The enclosing instance of the anonymous class is "this", not the
// qualification of the NewAnonymousClassInstance.
Java.Scope s;
for (
s = naci.getEnclosingBlockStatement();
!(s instanceof Java.TypeBodyDeclaration);
s = s.getEnclosingScope()
);
Java.ThisReference oei;
if (((Java.TypeBodyDeclaration) s).isStatic()) {
oei = null;
} else
{
oei = new Java.ThisReference(loc);
oei.setEnclosingBlockStatement(naci.getEnclosingBlockStatement());
}
this.invokeConstructor(
(Locatable) naci, // l
(Java.Scope) 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(Java.ParameterAccess pa) throws CompileException {
Java.LocalVariable lv = this.getLocalVariable(pa.formalParameter);
this.load((Locatable) pa, lv);
return lv.type;
}
private IClass compileGet2(Java.NewArray na) throws CompileException {
for (int i = 0; i < na.dimExprs.length; ++i) {
IClass dimType = this.compileGetValue(na.dimExprs[i]);
if (dimType != IClass.INT && this.unaryNumericPromotion(
(Locatable) na, // l
dimType // type
) != IClass.INT) this.compileError("Invalid array size expression type", na.getLocation());
}
return this.newArray(
(Locatable) na, // l
na.dimExprs.length, // dimExprCount
na.dims, // dims
this.getType(na.type) // componentType
);
}
private IClass compileGet2(Java.NewInitializedArray nia) throws CompileException {
IClass at = this.getType(nia.arrayType);
this.compileGetValue(nia.arrayInitializer, at);
return at;
}
private void compileGetValue(Java.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((Locatable) ai, new Integer(ai.values.length));
this.newArray(
(Locatable) ai, // l
1, // dimExprCount,
0, // dims,
ct // componentType
);
for (int i = 0; i < ai.values.length; ++i) {
this.writeOpcode(ai, Opcode.DUP);
this.pushConstant((Locatable) ai, new Integer(i));
Java.ArrayInitializerOrRvalue aiorv = ai.values[i];
if (aiorv instanceof Java.Rvalue) {
Java.Rvalue rv = (Java.Rvalue) aiorv;
this.assignmentConversion(
(Locatable) ai, // l
this.compileGetValue(rv), // sourceType
ct, // targetType
this.getConstantValue(rv) // optionalConstantValue
);
} else
if (aiorv instanceof Java.ArrayInitializer) {
this.compileGetValue((Java.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(Java.Literal l) throws CompileException {
if (
l.value == Scanner.MAGIC_INTEGER ||
l.value == Scanner.MAGIC_LONG
) this.compileError("This literal value may only appear in a negated context", l.getLocation());
return this.pushConstant((Locatable) l, l.value == null ? Java.Rvalue.CONSTANT_VALUE_NULL : l.value);
}
/**
* Convenience function that calls {@link #compileContext(Java.Rvalue)}
* and {@link #compileGet(Java.Rvalue)}.
*
* @return The type of the Rvalue
*/
private IClass compileGetValue(Java.Rvalue rv) throws CompileException {
Object cv = this.getConstantValue(rv);
if (cv != null) {
this.fakeCompile(rv); // To check that, e.g., "a" compiles in "true || a".
this.pushConstant((Locatable) rv, cv);
return this.getType(rv);
}
this.compileContext(rv);
return this.compileGet(rv);
}
// -------------------- Rvalue.getConstantValue() -----------------
/**
* Attempts to evaluate as a constant expression.
* <p>
* <table>
* <tr><th>Expression type</th><th>Return value type</th></tr>
* <tr><td>String</td><td>String</td></tr>
* <tr><td>byte</td><td>Byte</td></tr>
* <tr><td>short</td><td>Short</td></tr>
* <tr><td>int</td><td>Integer</td></tr>
* <tr><td>boolean</td><td>Boolean</td></tr>
* <tr><td>char</td><td>Character</td></tr>
* <tr><td>float</td><td>Float</td></tr>
* <tr><td>long</td><td>Long</td></tr>
* <tr><td>double</td><td>Double</td></tr>
* <tr><td>null</td><td>{@link Java.Rvalue#CONSTANT_VALUE_NULL}</td></tr>
* </table>
*
* @return <code>null</code> iff the rvalue is not a constant value
*/
public final Object getConstantValue(Java.Rvalue rv) throws CompileException {
if (rv.constantValue != Java.Rvalue.CONSTANT_VALUE_UNKNOWN) return rv.constantValue;
final Object[] res = new Object[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.RvalueVisitor rvv = new Visitor.RvalueVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitArrayLength(Java.ArrayLength al) { res[0] = UnitCompiler.this.getConstantValue2(al); }
public void visitAssignment(Java.Assignment a) { res[0] = UnitCompiler.this.getConstantValue2(a); }
public void visitUnaryOperation(Java.UnaryOperation uo) { try { res[0] = UnitCompiler.this.getConstantValue2(uo); } catch (CompileException e) { throw new UCE(e); } }
public void visitBinaryOperation(Java.BinaryOperation bo) { try { res[0] = UnitCompiler.this.getConstantValue2(bo); } catch (CompileException e) { throw new UCE(e); } }
public void visitCast(Java.Cast c) { try { res[0] = UnitCompiler.this.getConstantValue2(c); } catch (CompileException e) { throw new UCE(e); } }
public void visitClassLiteral(Java.ClassLiteral cl) { res[0] = UnitCompiler.this.getConstantValue2(cl); }
public void visitConditionalExpression(Java.ConditionalExpression ce) { try { res[0] = UnitCompiler.this.getConstantValue2(ce); } catch (CompileException e) { throw new UCE(e); } }
public void visitCrement(Java.Crement c) { res[0] = UnitCompiler.this.getConstantValue2(c); }
public void visitInstanceof(Java.Instanceof io) { res[0] = UnitCompiler.this.getConstantValue2(io); }
public void visitMethodInvocation(Java.MethodInvocation mi) { res[0] = UnitCompiler.this.getConstantValue2(mi); }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.getConstantValue2(smi); }
public void visitLiteral(Java.Literal l) { try { res[0] = UnitCompiler.this.getConstantValue2(l); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.getConstantValue2(naci); }
public void visitNewArray(Java.NewArray na) { res[0] = UnitCompiler.this.getConstantValue2(na); }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { res[0] = UnitCompiler.this.getConstantValue2(nia); }
public void visitNewClassInstance(Java.NewClassInstance nci) { res[0] = UnitCompiler.this.getConstantValue2(nci); }
public void visitParameterAccess(Java.ParameterAccess pa) { res[0] = UnitCompiler.this.getConstantValue2(pa); }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { res[0] = UnitCompiler.this.getConstantValue2(qtr); }
public void visitThisReference(Java.ThisReference tr) { res[0] = UnitCompiler.this.getConstantValue2(tr); }
public void visitAmbiguousName(Java.AmbiguousName an) { try { res[0] = UnitCompiler.this.getConstantValue2(an); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { res[0] = UnitCompiler.this.getConstantValue2(aae); }
public void visitFieldAccess(Java.FieldAccess fa) { try { res[0] = UnitCompiler.this.getConstantValue2(fa); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { res[0] = UnitCompiler.this.getConstantValue2(fae); }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { res[0] = UnitCompiler.this.getConstantValue2(scfae); }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { res[0] = UnitCompiler.this.getConstantValue2(lva); }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.getConstantValue2(pe); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
rv.accept(rvv);
rv.constantValue = res[0];
return rv.constantValue;
} catch (UCE uce) {
throw uce.ce;
}
}
private Object getConstantValue2(Java.Rvalue rv) {
return null;
}
private Object getConstantValue2(Java.AmbiguousName an) throws CompileException {
return this.getConstantValue(this.toRvalueOrCE(this.reclassify(an)));
}
private Object getConstantValue2(Java.FieldAccess fa) throws CompileException {
return fa.field.getConstantValue();
}
private Object getConstantValue2(Java.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 instanceof Boolean ? (
((Boolean) cv).booleanValue() ? Boolean.FALSE : Boolean.TRUE
) : null;
}
return null;
}
private Object getConstantValue2(Java.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 null;
}
private Object getConstantValue2(Java.BinaryOperation bo) throws CompileException {
// null == null
// null != null
if (
(bo.op == "==" || bo.op == "!=") &&
this.getConstantValue(bo.lhs) == Java.Rvalue.CONSTANT_VALUE_NULL &&
this.getConstantValue(bo.rhs) == Java.Rvalue.CONSTANT_VALUE_NULL
) return bo.op == "==" ? Boolean.TRUE : Boolean.FALSE;
// "|", "^", "&", "*", "/", "%", "+", "-".
if (
bo.op == "|" ||
bo.op == "^" ||
bo.op == "&" ||
bo.op == "*" ||
bo.op == "/" ||
bo.op == "%" ||
bo.op == "+" ||
bo.op == "-"
) {
// Unroll the constant operands.
List cvs = new ArrayList();
for (Iterator it = bo.unrollLeftAssociation(); it.hasNext();) {
Object cv = this.getConstantValue(((Java.Rvalue) it.next()));
if (cv == null) return null;
cvs.add(cv);
}
// Compute the constant value of the unrolled binary operation.
Iterator it = cvs.iterator();
Object lhs = it.next();
while (it.hasNext()) {
Object rhs = it.next();
// String concatenation?
if (bo.op == "+" && (lhs instanceof String || rhs instanceof String)) {
StringBuffer sb = new StringBuffer();
sb.append(lhs.toString()).append(rhs.toString());
while (it.hasNext()) sb.append(it.next().toString());
return sb.toString();
}
if (!(lhs instanceof Number) || !(rhs instanceof Number)) return null;
try {
// Numeric binary operation.
if (lhs instanceof Double || rhs instanceof Double) {
double lhsD = ((Number) lhs).doubleValue();
double rhsD = ((Number) rhs).doubleValue();
lhs = (
bo.op == "*" ? new Double(lhsD * rhsD) :
bo.op == "/" ? new Double(lhsD / rhsD) :
bo.op == "%" ? new Double(lhsD % rhsD) :
bo.op == "+" ? new Double(lhsD + rhsD) :
bo.op == "-" ? new Double(lhsD - rhsD) :
null
);
} else
if (lhs instanceof Float || rhs instanceof Float) {
float lhsF = ((Number) lhs).floatValue();
float rhsF = ((Number) rhs).floatValue();
lhs = (
bo.op == "*" ? new Float(lhsF * rhsF) :
bo.op == "/" ? new Float(lhsF / rhsF) :
bo.op == "%" ? new Float(lhsF % rhsF) :
bo.op == "+" ? new Float(lhsF + rhsF) :
bo.op == "-" ? new Float(lhsF - rhsF) :
null
);
} else
if (lhs instanceof Long || rhs instanceof Long) {
long lhsL = ((Number) lhs).longValue();
long rhsL = ((Number) rhs).longValue();
lhs = (
bo.op == "|" ? new Long(lhsL | rhsL) :
bo.op == "^" ? new Long(lhsL ^ rhsL) :
bo.op == "&" ? new Long(lhsL & rhsL) :
bo.op == "*" ? new Long(lhsL * rhsL) :
bo.op == "/" ? new Long(lhsL / rhsL) :
bo.op == "%" ? new Long(lhsL % rhsL) :
bo.op == "+" ? new Long(lhsL + rhsL) :
bo.op == "-" ? new Long(lhsL - rhsL) :
null
);
} else
{
int lhsI = ((Number) lhs).intValue();
int rhsI = ((Number) rhs).intValue();
lhs = (
bo.op == "|" ? new Integer(lhsI | rhsI) :
bo.op == "^" ? new Integer(lhsI ^ rhsI) :
bo.op == "&" ? new Integer(lhsI & rhsI) :
bo.op == "*" ? new Integer(lhsI * rhsI) :
bo.op == "/" ? new Integer(lhsI / rhsI) :
bo.op == "%" ? new Integer(lhsI % rhsI) :
bo.op == "+" ? new Integer(lhsI + rhsI) :
bo.op == "-" ? new Integer(lhsI - rhsI) :
null
);
}
if (lhs == null) return null;
} catch (ArithmeticException ae) {
// Most likely a divide by zero or modulo by zero.
// Guess we can't make this expression into a constant.
return null;
}
}
return lhs;
}
// "&&" and "||" with constant LHS operand.
if (
bo.op == "&&" ||
bo.op == "||"
) {
Object lhsValue = this.getConstantValue(bo.lhs);
if (lhsValue instanceof Boolean) {
boolean lhsBV = ((Boolean) lhsValue).booleanValue();
return (
bo.op == "&&" ?
(lhsBV ? this.getConstantValue(bo.rhs) : Boolean.FALSE) :
(lhsBV ? Boolean.TRUE : this.getConstantValue(bo.rhs))
);
}
}
return null;
}
private Object getConstantValue2(Java.Cast c) throws CompileException {
Object cv = this.getConstantValue(c.value);
if (cv == null) return null;
if (cv instanceof Number) {
IClass tt = this.getType(c.targetType);
if (tt == IClass.BYTE) return new Byte(((Number) cv).byteValue());
if (tt == IClass.SHORT) return new Short(((Number) cv).shortValue());
if (tt == IClass.INT) return new Integer(((Number) cv).intValue());
if (tt == IClass.LONG) return new Long(((Number) cv).longValue());
if (tt == IClass.FLOAT) return new Float(((Number) cv).floatValue());
if (tt == IClass.DOUBLE) return new Double(((Number) cv).doubleValue());
}
return null;
}
private Object getConstantValue2(Java.ParenthesizedExpression pe) throws CompileException {
return this.getConstantValue(pe.value);
}
private Object getConstantValue2(Java.Literal l) throws CompileException {
if (
l.value == Scanner.MAGIC_INTEGER ||
l.value == Scanner.MAGIC_LONG
) this.compileError("This literal value may only appear in a negated context", l.getLocation());
return l.value == null ? Java.Rvalue.CONSTANT_VALUE_NULL : l.value;
}
/**
* Attempts to evaluate the negated value of a constant {@link Java.Rvalue}.
* This is particularly relevant for the smallest value of an integer or
* long literal.
*
* @return null if value is not constant; otherwise a String, Byte,
* Short, Integer, Boolean, Character, Float, Long or Double
*/
private Object getNegatedConstantValue(Java.Rvalue rv) throws CompileException {
final Object[] res = new Object[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.RvalueVisitor rvv = new Visitor.RvalueVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitArrayLength(Java.ArrayLength al) { res[0] = UnitCompiler.this.getNegatedConstantValue2(al); }
public void visitAssignment(Java.Assignment a) { res[0] = UnitCompiler.this.getNegatedConstantValue2(a); }
public void visitUnaryOperation(Java.UnaryOperation uo) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(uo); } catch (CompileException e) { throw new UCE(e); } }
public void visitBinaryOperation(Java.BinaryOperation bo) { res[0] = UnitCompiler.this.getNegatedConstantValue2(bo); }
public void visitCast(Java.Cast c) { res[0] = UnitCompiler.this.getNegatedConstantValue2(c); }
public void visitClassLiteral(Java.ClassLiteral cl) { res[0] = UnitCompiler.this.getNegatedConstantValue2(cl); }
public void visitConditionalExpression(Java.ConditionalExpression ce) { res[0] = UnitCompiler.this.getNegatedConstantValue2(ce); }
public void visitCrement(Java.Crement c) { res[0] = UnitCompiler.this.getNegatedConstantValue2(c); }
public void visitInstanceof(Java.Instanceof io) { res[0] = UnitCompiler.this.getNegatedConstantValue2(io); }
public void visitMethodInvocation(Java.MethodInvocation mi) { res[0] = UnitCompiler.this.getNegatedConstantValue2(mi); }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.getNegatedConstantValue2(smi); }
public void visitLiteral(Java.Literal l) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(l); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.getNegatedConstantValue2(naci); }
public void visitNewArray(Java.NewArray na) { res[0] = UnitCompiler.this.getNegatedConstantValue2(na); }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { res[0] = UnitCompiler.this.getNegatedConstantValue2(nia); }
public void visitNewClassInstance(Java.NewClassInstance nci) { res[0] = UnitCompiler.this.getNegatedConstantValue2(nci); }
public void visitParameterAccess(Java.ParameterAccess pa) { res[0] = UnitCompiler.this.getNegatedConstantValue2(pa); }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { res[0] = UnitCompiler.this.getNegatedConstantValue2(qtr); }
public void visitThisReference(Java.ThisReference tr) { res[0] = UnitCompiler.this.getNegatedConstantValue2(tr); }
public void visitAmbiguousName(Java.AmbiguousName an) { res[0] = UnitCompiler.this.getNegatedConstantValue2(an); }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { res[0] = UnitCompiler.this.getNegatedConstantValue2(aae); }
public void visitFieldAccess(Java.FieldAccess fa) { res[0] = UnitCompiler.this.getNegatedConstantValue2(fa); }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { res[0] = UnitCompiler.this.getNegatedConstantValue2(fae); }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { res[0] = UnitCompiler.this.getNegatedConstantValue2(scfae); }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { res[0] = UnitCompiler.this.getNegatedConstantValue2(lva); }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(pe); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
rv.accept(rvv);
return res[0];
} catch (UCE uce) {
throw uce.ce;
}
}
private Object getNegatedConstantValue2(Java.Rvalue rv) {
return null;
}
private Object getNegatedConstantValue2(Java.UnaryOperation uo) throws CompileException {
return (
uo.operator.equals("+") ? this.getNegatedConstantValue(uo.operand) :
uo.operator.equals("-") ? this.getConstantValue(uo.operand) :
null
);
}
private Object getNegatedConstantValue2(Java.ParenthesizedExpression pe) throws CompileException {
return this.getNegatedConstantValue(pe.value);
}
private Object getNegatedConstantValue2(Java.Literal l) throws CompileException {
if (l.value instanceof Byte) return new Byte((byte)-((Byte) l.value).byteValue());
if (l.value instanceof Short) return new Short((short)-((Short) l.value).shortValue());
if (l.value instanceof Integer) return new Integer(-((Integer) l.value).intValue());
if (l.value instanceof Long) return new Long(-((Long) l.value).longValue());
if (l.value instanceof Float) return new Float(-((Float) l.value).floatValue());
if (l.value instanceof Double) return new Double(-((Double) l.value).doubleValue());
this.compileError("Cannot negate this literal", l.getLocation());
return null;
}
// ------------ BlockStatement.generatesCode() -------------
/**
* Check whether invocation of {@link #compile(Java.BlockStatement)} would
* generate more than zero code bytes.
*/
private boolean generatesCode(Java.BlockStatement bs) throws CompileException {
final boolean[] res = new boolean[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.BlockStatementVisitor bsv = new Visitor.BlockStatementVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitInitializer(Java.Initializer i) { try { res[0] = UnitCompiler.this.generatesCode2(i); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldDeclaration(Java.FieldDeclaration fd) { try { res[0] = UnitCompiler.this.generatesCode2(fd); } catch (CompileException e) { throw new UCE(e); } }
public void visitLabeledStatement(Java.LabeledStatement ls) { res[0] = UnitCompiler.this.generatesCode2(ls); }
public void visitBlock(Java.Block b) { try { res[0] = UnitCompiler.this.generatesCode2(b); } catch (CompileException e) { throw new UCE(e); } }
public void visitExpressionStatement(Java.ExpressionStatement es) { res[0] = UnitCompiler.this.generatesCode2(es); }
public void visitIfStatement(Java.IfStatement is) { res[0] = UnitCompiler.this.generatesCode2(is); }
public void visitForStatement(Java.ForStatement fs) { res[0] = UnitCompiler.this.generatesCode2(fs); }
public void visitWhileStatement(Java.WhileStatement ws) { res[0] = UnitCompiler.this.generatesCode2(ws); }
public void visitTryStatement(Java.TryStatement ts) { res[0] = UnitCompiler.this.generatesCode2(ts); }
public void visitSwitchStatement(Java.SwitchStatement ss) { res[0] = UnitCompiler.this.generatesCode2(ss); }
public void visitSynchronizedStatement(Java.SynchronizedStatement ss) { res[0] = UnitCompiler.this.generatesCode2(ss); }
public void visitDoStatement(Java.DoStatement ds) { res[0] = UnitCompiler.this.generatesCode2(ds); }
public void visitLocalVariableDeclarationStatement(Java.LocalVariableDeclarationStatement lvds) { res[0] = UnitCompiler.this.generatesCode2(lvds); }
public void visitReturnStatement(Java.ReturnStatement rs) { res[0] = UnitCompiler.this.generatesCode2(rs); }
public void visitThrowStatement(Java.ThrowStatement ts) { res[0] = UnitCompiler.this.generatesCode2(ts); }
public void visitBreakStatement(Java.BreakStatement bs) { res[0] = UnitCompiler.this.generatesCode2(bs); }
public void visitContinueStatement(Java.ContinueStatement cs) { res[0] = UnitCompiler.this.generatesCode2(cs); }
public void visitEmptyStatement(Java.EmptyStatement es) { res[0] = UnitCompiler.this.generatesCode2(es); }
public void visitLocalClassDeclarationStatement(Java.LocalClassDeclarationStatement lcds) { res[0] = UnitCompiler.this.generatesCode2(lcds); }
public void visitAlternateConstructorInvocation(Java.AlternateConstructorInvocation aci) { res[0] = UnitCompiler.this.generatesCode2(aci); }
public void visitSuperConstructorInvocation(Java.SuperConstructorInvocation sci) { res[0] = UnitCompiler.this.generatesCode2(sci); }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
bs.accept(bsv);
return res[0];
} catch (UCE uce) {
throw uce.ce;
}
}
public boolean generatesCode2(Java.BlockStatement bs) { return true; }
public boolean generatesCode2(Java.EmptyStatement es) { return false; }
public boolean generatesCode2(Java.LocalClassDeclarationStatement lcds) { return false; }
public boolean generatesCode2(Java.Initializer i) throws CompileException { return this.generatesCode(i.block); }
public boolean generatesCode2ListStatements(List/*<BlockStatement>*/ l) throws CompileException {
for (int i = 0; i < l.size(); ++i) {
if (this.generatesCode(((Java.BlockStatement) l.get(i)))) return true;
}
return false;
}
public boolean generatesCode2(Java.Block b) throws CompileException {
return generatesCode2ListStatements(b.statements);
}
public boolean generatesCode2(Java.FieldDeclaration fd) throws CompileException {
// Code is only generated if at least one of the declared variables has a
// non-constant-final initializer.
for (int i = 0; i < fd.variableDeclarators.length; ++i) {
Java.VariableDeclarator vd = fd.variableDeclarators[i];
if (this.getNonConstantFinalInitializer(fd, vd) != null) return true;
}
return false;
}
// ------------ BlockStatement.leave() -------------
/**
* Clean up the statement context. This is currently relevant for "try ... catch ... finally" statements (execute
* "finally" clause) and "synchronized" statements (monitorexit).
* <p>
* Statements like "return", "break", "continue" must call this method for all the statements they terminate.
* <p>
* Notice: If <code>optionalStackValueType</code> is <code>null</code>, then the operand stack is empty; otherwise
* exactly one operand with that type is on the stack. This information is vital to implementations of {@link
* #leave(Java.BlockStatement, IClass)} that require a specific operand stack state (e.g. an empty operand stack for
* JSR).
*/
private void leave(Java.BlockStatement bs, final IClass optionalStackValueType) {
Visitor.BlockStatementVisitor bsv = new Visitor.BlockStatementVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitInitializer(Java.Initializer i) { UnitCompiler.this.leave2(i, optionalStackValueType); }
public void visitFieldDeclaration(Java.FieldDeclaration fd) { UnitCompiler.this.leave2(fd, optionalStackValueType); }
public void visitLabeledStatement(Java.LabeledStatement ls) { UnitCompiler.this.leave2(ls, optionalStackValueType); }
public void visitBlock(Java.Block b) { UnitCompiler.this.leave2(b, optionalStackValueType); }
public void visitExpressionStatement(Java.ExpressionStatement es) { UnitCompiler.this.leave2(es, optionalStackValueType); }
public void visitIfStatement(Java.IfStatement is) { UnitCompiler.this.leave2(is, optionalStackValueType); }
public void visitForStatement(Java.ForStatement fs) { UnitCompiler.this.leave2(fs, optionalStackValueType); }
public void visitWhileStatement(Java.WhileStatement ws) { UnitCompiler.this.leave2(ws, optionalStackValueType); }
public void visitTryStatement(Java.TryStatement ts) { UnitCompiler.this.leave2(ts, optionalStackValueType); }
public void visitSwitchStatement(Java.SwitchStatement ss) { UnitCompiler.this.leave2(ss, optionalStackValueType); }
public void visitSynchronizedStatement(Java.SynchronizedStatement ss) { UnitCompiler.this.leave2(ss, optionalStackValueType); }
public void visitDoStatement(Java.DoStatement ds) { UnitCompiler.this.leave2(ds, optionalStackValueType); }
public void visitLocalVariableDeclarationStatement(Java.LocalVariableDeclarationStatement lvds) { UnitCompiler.this.leave2(lvds, optionalStackValueType); }
public void visitReturnStatement(Java.ReturnStatement rs) { UnitCompiler.this.leave2(rs, optionalStackValueType); }
public void visitThrowStatement(Java.ThrowStatement ts) { UnitCompiler.this.leave2(ts, optionalStackValueType); }
public void visitBreakStatement(Java.BreakStatement bs) { UnitCompiler.this.leave2(bs, optionalStackValueType); }
public void visitContinueStatement(Java.ContinueStatement cs) { UnitCompiler.this.leave2(cs, optionalStackValueType); }
public void visitEmptyStatement(Java.EmptyStatement es) { UnitCompiler.this.leave2(es, optionalStackValueType); }
public void visitLocalClassDeclarationStatement(Java.LocalClassDeclarationStatement lcds) { UnitCompiler.this.leave2(lcds, optionalStackValueType); }
public void visitAlternateConstructorInvocation(Java.AlternateConstructorInvocation aci) { UnitCompiler.this.leave2(aci, optionalStackValueType); }
public void visitSuperConstructorInvocation(Java.SuperConstructorInvocation sci) { UnitCompiler.this.leave2(sci, optionalStackValueType); }
// CHECKSTYLE(LineLengthCheck):ON
};
bs.accept(bsv);
}
public void leave2(Java.BlockStatement bs, IClass optionalStackValueType) { ; }
public void leave2(Java.SynchronizedStatement ss, IClass optionalStackValueType) {
this.load((Locatable) ss, this.iClassLoader.OBJECT, ss.monitorLvIndex);
this.writeOpcode(ss, Opcode.MONITOREXIT);
}
public void leave2(Java.TryStatement ts, IClass optionalStackValueType) {
if (ts.finallyOffset != null) {
this.codeContext.saveLocalVariables();
try {
short sv = 0;
// Obviously, JSR must always be executed with the operand stack being
// empty; otherwise we get "java.lang.VerifyError: Inconsistent stack height
// 1 != 2"
if (optionalStackValueType != null) {
sv = this.codeContext.allocateLocalVariable(
Descriptor.size(optionalStackValueType.getDescriptor())
);
this.store((Locatable) ts, optionalStackValueType, sv);
}
this.writeBranch(ts, Opcode.JSR, ts.finallyOffset);
if (optionalStackValueType != null) {
this.load((Locatable) ts, optionalStackValueType, sv);
}
} finally {
this.codeContext.restoreLocalVariables();
}
}
}
// ---------------- Lvalue.compileSet() -----------------
/**
* Generates code that stores a value in the {@link Java.Lvalue}.
* Expects the {@link Java.Lvalue}'s context (see {@link
* #compileContext}) and a value of the {@link Java.Lvalue}'s type
* on the operand stack.
*/
private void compileSet(Java.Lvalue lv) throws CompileException {
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.LvalueVisitor lvv = new Visitor.LvalueVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
public void visitAmbiguousName(Java.AmbiguousName an) { try { UnitCompiler.this.compileSet2(an); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { try { UnitCompiler.this.compileSet2(aae); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccess(Java.FieldAccess fa) { try { UnitCompiler.this.compileSet2(fa); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { try { UnitCompiler.this.compileSet2(fae); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compileSet2(scfae); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { UnitCompiler.this.compileSet2(lva); }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { UnitCompiler.this.compileSet2(pe); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
lv.accept(lvv);
} catch (UCE uce) {
throw uce.ce;
}
}
private void compileSet2(Java.AmbiguousName an) throws CompileException {
this.compileSet(this.toLvalueOrCE(this.reclassify(an)));
}
private void compileSet2(Java.LocalVariableAccess lva) {
this.store(
(Locatable) lva,
lva.localVariable.type,
lva.localVariable
);
}
private void compileSet2(Java.FieldAccess fa) throws CompileException {
this.checkAccessible(fa.field, fa.getEnclosingBlockStatement());
this.writeOpcode(fa, (
fa.field.isStatic() ?
Opcode.PUTSTATIC :
Opcode.PUTFIELD
));
this.writeConstantFieldrefInfo(
fa.field.getDeclaringIClass().getDescriptor(),
fa.field.getName(), // classFD
fa.field.getDescriptor() // fieldFD
);
}
private void compileSet2(Java.ArrayAccessExpression aae) throws CompileException {
this.writeOpcode(aae, Opcode.IASTORE + UnitCompiler.ilfdabcs(this.getType(aae)));
}
private void compileSet2(Java.FieldAccessExpression fae) throws CompileException {
this.determineValue(fae);
this.compileSet(this.toLvalueOrCE(fae.value));
}
private void compileSet2(Java.SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
this.compileSet(this.toLvalueOrCE(scfae.value));
}
private void compileSet2(Java.ParenthesizedExpression pe) throws CompileException {
this.compileSet(this.toLvalueOrCE(pe.value));
}
// ---------------- Atom.getType() ----------------
private IClass getType(Java.Atom a) throws CompileException {
final IClass[] res = new IClass[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.AtomVisitor av = new Visitor.AtomVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
// AtomVisitor
public void visitPackage(Java.Package p) { try { res[0] = UnitCompiler.this.getType2(p); } catch (CompileException e) { throw new UCE(e); } }
// TypeVisitor
public void visitArrayType(Java.ArrayType at) { try { res[0] = UnitCompiler.this.getType2(at); } catch (CompileException e) { throw new UCE(e); } }
public void visitBasicType(Java.BasicType bt) { res[0] = UnitCompiler.this.getType2(bt); }
public void visitReferenceType(Java.ReferenceType rt) { try { res[0] = UnitCompiler.this.getType2(rt); } catch (CompileException e) { throw new UCE(e); } }
public void visitRvalueMemberType(Java.RvalueMemberType rmt) { try { res[0] = UnitCompiler.this.getType2(rmt); } catch (CompileException e) { throw new UCE(e); } }
public void visitSimpleType(Java.SimpleType st) { res[0] = UnitCompiler.this.getType2(st); }
// RvalueVisitor
public void visitArrayLength(Java.ArrayLength al) { res[0] = UnitCompiler.this.getType2(al); }
public void visitAssignment(Java.Assignment a) { try { res[0] = UnitCompiler.this.getType2(a); } catch (CompileException e) { throw new UCE(e); } }
public void visitUnaryOperation(Java.UnaryOperation uo) { try { res[0] = UnitCompiler.this.getType2(uo); } catch (CompileException e) { throw new UCE(e); } }
public void visitBinaryOperation(Java.BinaryOperation bo) { try { res[0] = UnitCompiler.this.getType2(bo); } catch (CompileException e) { throw new UCE(e); } }
public void visitCast(Java.Cast c) { try { res[0] = UnitCompiler.this.getType2(c); } catch (CompileException e) { throw new UCE(e); } }
public void visitClassLiteral(Java.ClassLiteral cl) { res[0] = UnitCompiler.this.getType2(cl); }
public void visitConditionalExpression(Java.ConditionalExpression ce) { try { res[0] = UnitCompiler.this.getType2(ce); } catch (CompileException e) { throw new UCE(e); } }
public void visitCrement(Java.Crement c) { try { res[0] = UnitCompiler.this.getType2(c); } catch (CompileException e) { throw new UCE(e); } }
public void visitInstanceof(Java.Instanceof io) { res[0] = UnitCompiler.this.getType2(io); }
public void visitMethodInvocation(Java.MethodInvocation mi) { try { res[0] = UnitCompiler.this.getType2(mi); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { try { res[0] = UnitCompiler.this.getType2(smi); } catch (CompileException e) { throw new UCE(e); } }
public void visitLiteral(Java.Literal l) { res[0] = UnitCompiler.this.getType2(l); }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.getType2(naci); }
public void visitNewArray(Java.NewArray na) { try { res[0] = UnitCompiler.this.getType2(na); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { try { res[0] = UnitCompiler.this.getType2(nia); } catch (CompileException e) { throw new UCE(e); } }
public void visitNewClassInstance(Java.NewClassInstance nci) { try { res[0] = UnitCompiler.this.getType2(nci); } catch (CompileException e) { throw new UCE(e); } }
public void visitParameterAccess(Java.ParameterAccess pa) { try { res[0] = UnitCompiler.this.getType2(pa); } catch (CompileException e) { throw new UCE(e); } }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { try { res[0] = UnitCompiler.this.getType2(qtr); } catch (CompileException e) { throw new UCE(e); } }
public void visitThisReference(Java.ThisReference tr) { try { res[0] = UnitCompiler.this.getType2(tr); } catch (CompileException e) { throw new UCE(e); } }
// LvalueVisitor
public void visitAmbiguousName(Java.AmbiguousName an) { try { res[0] = UnitCompiler.this.getType2(an); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.getType2(aae); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccess(Java.FieldAccess fa) { try { res[0] = UnitCompiler.this.getType2(fa); } catch (CompileException e) { throw new UCE(e); } }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.getType2(fae); } catch (CompileException e) { throw new UCE(e); } }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.getType2(scfae); } catch (CompileException e) { throw new UCE(e); } }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { res[0] = UnitCompiler.this.getType2(lva); }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.getType2(pe); } catch (CompileException e) { throw new UCE(e); } }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
a.accept(av);
return res[0] != null ? res[0] : this.iClassLoader.OBJECT;
} catch (UCE uce) {
throw uce.ce;
}
}
private IClass getType2(Java.SimpleType st) { return st.iClass; }
private IClass getType2(Java.BasicType bt) {
switch (bt.index) {
case Java.BasicType.VOID: return IClass.VOID;
case Java.BasicType.BYTE: return IClass.BYTE;
case Java.BasicType.SHORT: return IClass.SHORT;
case Java.BasicType.CHAR: return IClass.CHAR;
case Java.BasicType.INT: return IClass.INT;
case Java.BasicType.LONG: return IClass.LONG;
case Java.BasicType.FLOAT: return IClass.FLOAT;
case Java.BasicType.DOUBLE: return IClass.DOUBLE;
case Java.BasicType.BOOLEAN: return IClass.BOOLEAN;
default: throw new JaninoRuntimeException("Invalid index " + bt.index);
}
}
private IClass getType2(Java.ReferenceType rt) throws CompileException {
Java.BlockStatement scopeBlockStatement = null;
Java.TypeDeclaration scopeTypeDeclaration = null;
Java.CompilationUnit scopeCompilationUnit;
for (Java.Scope s = rt.getEnclosingScope();; s = s.getEnclosingScope()) {
if (s instanceof Java.BlockStatement && scopeBlockStatement == null) {
scopeBlockStatement = (Java.BlockStatement) s;
}
if (s instanceof Java.TypeDeclaration && scopeTypeDeclaration == null) {
scopeTypeDeclaration = (Java.TypeDeclaration) s;
}
if (s instanceof Java.CompilationUnit) {
scopeCompilationUnit = (Java.CompilationUnit) s;
break;
}
}
if (rt.identifiers.length == 1) {
// 6.5.5.1 Simple type name (single identifier).
String simpleTypeName = rt.identifiers[0];
// 6.5.5.1.1 Local class.
{
Java.LocalClassDeclaration lcd = this.findLocalClassDeclaration(rt.getEnclosingScope(), simpleTypeName);
if (lcd != null) return this.resolve(lcd);
}
// 6.5.5.1.2 Member type.
if (scopeTypeDeclaration != null) { // If enclosed by another type declaration...
for (
Java.Scope s = scopeTypeDeclaration;
!(s instanceof Java.CompilationUnit);
s = s.getEnclosingScope()
) {
if (s instanceof Java.TypeDeclaration) {
IClass mt = this.findMemberType(
this.resolve((Java.AbstractTypeDeclaration) s),
simpleTypeName,
rt.getLocation()
);
if (mt != null) return mt;
}
}
}
{
// 6.5.5.1.4a Single-type import.
{
IClass importedClass = this.importSingleType(simpleTypeName, rt.getLocation());
if (importedClass != null) return importedClass;
}
// 6.5.5.1.4b Type declared in same compilation unit.
{
Java.PackageMemberTypeDeclaration pmtd = (
scopeCompilationUnit.getPackageMemberTypeDeclaration(simpleTypeName)
);
if (pmtd != null) return this.resolve((Java.AbstractTypeDeclaration) pmtd);
}
}
// 6.5.5.1.5 Type declared in other compilation unit of same
// package.
{
String pkg = (
scopeCompilationUnit.optionalPackageDeclaration == null ? null :
scopeCompilationUnit.optionalPackageDeclaration.packageName
);
String className = pkg == null ? simpleTypeName : pkg + "." + simpleTypeName;
IClass result = findClassByName(rt.getLocation(), className);
if (result != null) return result;
}
// 6.5.5.1.6 Type-import-on-demand declaration.
{
IClass importedClass = this.importTypeOnDemand(simpleTypeName, rt.getLocation());
if (importedClass != null) return importedClass;
}
// JLS3 ???: Type imported through single static import.
{
List l = (List) this.singleStaticImports.get(simpleTypeName);
if (l != null) {
IClass importedMemberType = null;
for (Iterator it = l.iterator(); it.hasNext();) {
Object o = it.next();
if (o instanceof IClass) {
IClass mt = (IClass) o;
if (!UnitCompiler.this.isAccessible(mt, scopeBlockStatement)) continue;
if (importedMemberType != null && importedMemberType != mt) {
UnitCompiler.this.compileError(
"Ambiguous static imports: \""
+ importedMemberType.toString()
+ "\" vs. \""
+ mt.toString()
+ "\""
);
}
importedMemberType = mt;
}
}
if (importedMemberType != null) return importedMemberType;
}
}
// JLS3 ???: Type imported through static-import-on-demand.
{
IClass importedMemberType = null;
for (Iterator it = this.staticImportsOnDemand.iterator(); it.hasNext();) {
IClass ic = (IClass) it.next();
IClass[] memberTypes = ic.getDeclaredIClasses();
for (int i = 0; i < memberTypes.length; ++i) {
IClass mt = memberTypes[i];
if (!UnitCompiler.this.isAccessible(mt, scopeBlockStatement)) continue;
if (mt.getDescriptor().endsWith('$' + simpleTypeName + ';')) {
if (importedMemberType != null) {
UnitCompiler.this.compileError(
"Ambiguous static imports: \""
+ importedMemberType.toString()
+ "\" vs. \""
+ mt.toString()
+ "\""
);
}
importedMemberType = mt;
}
}
}
if (importedMemberType != null) return importedMemberType;
}
// 6.5.5.1.8 Give up.
this.compileError("Cannot determine simple type name \"" + simpleTypeName + "\"", rt.getLocation());
return this.iClassLoader.OBJECT;
} else {
// 6.5.5.2 Qualified type name (two or more identifiers).
Java.Atom q = this.reclassifyName(
rt.getLocation(),
rt.getEnclosingScope(),
rt.identifiers,
rt.identifiers.length - 1
);
// 6.5.5.2.1 PACKAGE.CLASS
if (q instanceof Java.Package) {
String className = Java.join(rt.identifiers, ".");
IClass result = findClassByName(rt.getLocation(), className);
if (result != null) return result;
this.compileError("Class \"" + className + "\" not found", rt.getLocation());
return this.iClassLoader.OBJECT;
}
// 6.5.5.2.2 CLASS.CLASS (member type)
String memberTypeName = rt.identifiers[rt.identifiers.length - 1];
IClass[] types = this.getType(this.toTypeOrCE(q)).findMemberType(memberTypeName);
if (types.length == 1) return types[0];
if (types.length == 0) {
this.compileError("\"" + q + "\" declares no member type \"" + memberTypeName + "\"", rt.getLocation());
} else
{
this.compileError(
"\"" + q + "\" and its supertypes declare more than one member type \"" + memberTypeName + "\"",
rt.getLocation()
);
}
return this.iClassLoader.OBJECT;
}
}
private IClass getType2(Java.RvalueMemberType rvmt) throws CompileException {
IClass rvt = this.getType(rvmt.rvalue);
IClass memberType = this.findMemberType(rvt, rvmt.identifier, rvmt.getLocation());
if (memberType == null) {
this.compileError("\"" + rvt + "\" has no member type \"" + rvmt.identifier + "\"", rvmt.getLocation());
}
return memberType;
}
private IClass getType2(Java.ArrayType at) throws CompileException {
return this.getType(at.componentType).getArrayIClass(this.iClassLoader.OBJECT);
}
private IClass getType2(Java.AmbiguousName an) throws CompileException {
return this.getType(this.reclassify(an));
}
private IClass getType2(Java.Package p) throws CompileException {
this.compileError("Unknown variable or type \"" + p.name + "\"", p.getLocation());
return this.iClassLoader.OBJECT;
}
private IClass getType2(Java.LocalVariableAccess lva) {
return lva.localVariable.type;
}
private IClass getType2(Java.FieldAccess fa) throws CompileException {
return fa.field.getType();
}
private IClass getType2(Java.ArrayLength al) {
return IClass.INT;
}
private IClass getType2(Java.ThisReference tr) throws CompileException {
return this.getIClass(tr);
}
private IClass getType2(Java.QualifiedThisReference qtr) throws CompileException {
return this.getTargetIClass(qtr);
}
private IClass getType2(Java.ClassLiteral cl) {
return this.iClassLoader.CLASS;
}
private IClass getType2(Java.Assignment a) throws CompileException {
return this.getType(a.lhs);
}
private IClass getType2(Java.ConditionalExpression ce) throws CompileException {
IClass mhsType = this.getType(ce.mhs);
IClass rhsType = this.getType(ce.rhs);
if (mhsType == rhsType) {
// JLS 15.25.1.1
return mhsType;
} else
if (mhsType.isPrimitiveNumeric() && rhsType.isPrimitiveNumeric()) {
// JLS 15.25.1.2
// TODO JLS 15.25.1.2.1
// TODO JLS 15.25.1.2.2
// JLS 15.25.1.2.3
return this.binaryNumericPromotionType((Java.Locatable) ce, mhsType, rhsType);
} else
if (this.getConstantValue(ce.mhs) == Java.Rvalue.CONSTANT_VALUE_NULL && !rhsType.isPrimitive()) {
// JLS 15.25.1.3 (null : reference)
return rhsType;
} else
if (!mhsType.isPrimitive() && this.getConstantValue(ce.rhs) == Java.Rvalue.CONSTANT_VALUE_NULL) {
// JLS 15.25.1.3 (reference : null)
return mhsType;
} else
if (!mhsType.isPrimitive() && !rhsType.isPrimitive()) {
if (mhsType.isAssignableFrom(rhsType)) {
return mhsType;
} else
if (rhsType.isAssignableFrom(mhsType)) {
return rhsType;
} else {
this.compileError(
"Reference types \"" + mhsType + "\" and \"" + rhsType + "\" don't match",
ce.getLocation()
);
return this.iClassLoader.OBJECT;
}
} else
{
this.compileError(
"Incompatible expression types \"" + mhsType + "\" and \"" + rhsType + "\"",
ce.getLocation()
);
return this.iClassLoader.OBJECT;
}
}
private IClass getType2(Java.Crement c) throws CompileException {
return this.getType(c.operand);
}
private IClass getType2(Java.ArrayAccessExpression aae) throws CompileException {
return this.getType(aae.lhs).getComponentType();
}
private IClass getType2(Java.FieldAccessExpression fae) throws CompileException {
this.determineValue(fae);
return this.getType(fae.value);
}
private IClass getType2(Java.SuperclassFieldAccessExpression scfae) throws CompileException {
this.determineValue(scfae);
return this.getType(scfae.value);
}
private IClass getType2(Java.UnaryOperation uo) throws CompileException {
if (uo.operator == "!") return IClass.BOOLEAN;
if (
uo.operator == "+" ||
uo.operator == "-" ||
uo.operator == "~"
) return this.unaryNumericPromotionType((Locatable) uo, this.getUnboxedType(this.getType(uo.operand)));
this.compileError("Unexpected operator \"" + uo.operator + "\"", uo.getLocation());
return IClass.BOOLEAN;
}
private IClass getType2(Java.Instanceof io) {
return IClass.BOOLEAN;
}
private IClass getType2(Java.BinaryOperation bo) throws CompileException {
if (
bo.op == "||" ||
bo.op == "&&" ||
bo.op == "==" ||
bo.op == "!=" ||
bo.op == "<" ||
bo.op == ">" ||
bo.op == "<=" ||
bo.op == ">="
) return IClass.BOOLEAN;
if (
bo.op == "|" ||
bo.op == "^" ||
bo.op == "&"
) {
IClass lhsType = this.getType(bo.lhs);
return (
lhsType == IClass.BOOLEAN || lhsType == this.iClassLoader.BOOLEAN ?
IClass.BOOLEAN :
this.binaryNumericPromotionType(
(Java.Locatable) bo,
lhsType,
this.getType(bo.rhs)
)
);
}
if (
bo.op == "*" ||
bo.op == "/" ||
bo.op == "%" ||
bo.op == "+" ||
bo.op == "-"
) {
IClassLoader icl = this.iClassLoader;
// Unroll the operands of this binary operation.
Iterator ops = bo.unrollLeftAssociation();
// Check the far left operand type.
IClass lhsType = this.getUnboxedType(this.getType(((Java.Rvalue) ops.next())));
if (bo.op == "+" && lhsType == icl.STRING) return icl.STRING;
// Determine the expression type.
do {
IClass rhsType = this.getUnboxedType(this.getType(((Java.Rvalue) ops.next())));
if (bo.op == "+" && rhsType == icl.STRING) return icl.STRING;
lhsType = this.binaryNumericPromotionType((Java.Locatable) bo, lhsType, rhsType);
} while (ops.hasNext());
return lhsType;
}
if (
bo.op == "<<" ||
bo.op == ">>" ||
bo.op == ">>>"
) {
IClass lhsType = this.getType(bo.lhs);
return this.unaryNumericPromotionType((Locatable) bo, lhsType);
}
this.compileError("Unexpected operator \"" + bo.op + "\"", bo.getLocation());
return this.iClassLoader.OBJECT;
}
private IClass getUnboxedType(IClass type) {
IClass c = this.isUnboxingConvertible(type);
return c != null ? c : type;
}
private IClass getType2(Java.Cast c) throws CompileException {
return this.getType(c.targetType);
}
private IClass getType2(Java.ParenthesizedExpression pe) throws CompileException {
return this.getType(pe.value);
}
private IClass getType2(Java.MethodInvocation mi) throws CompileException {
if (mi.iMethod == null) {
mi.iMethod = this.findIMethod(mi);
}
return mi.iMethod.getReturnType();
}
private IClass getType2(Java.SuperclassMethodInvocation scmi) throws CompileException {
return this.findIMethod(scmi).getReturnType();
}
private IClass getType2(Java.NewClassInstance nci) throws CompileException {
if (nci.iClass == null) nci.iClass = this.getType(nci.type);
return nci.iClass;
}
private IClass getType2(Java.NewAnonymousClassInstance naci) {
return this.resolve(naci.anonymousClassDeclaration);
}
private IClass getType2(Java.ParameterAccess pa) throws CompileException {
return this.getLocalVariable(pa.formalParameter).type;
}
private IClass getType2(Java.NewArray na) throws CompileException {
IClass res = this.getType(na.type);
return res.getArrayIClass(na.dimExprs.length + na.dims, this.iClassLoader.OBJECT);
}
private IClass getType2(Java.NewInitializedArray nia) throws CompileException {
return this.getType(nia.arrayType);
}
private IClass getType2(Java.Literal l) {
if (l.value instanceof Short) return IClass.SHORT;
if (l.value instanceof Byte) return IClass.BYTE;
if (l.value instanceof Integer) return IClass.INT;
if (l.value instanceof Long) return IClass.LONG;
if (l.value instanceof Float) return IClass.FLOAT;
if (l.value instanceof Double) return IClass.DOUBLE;
if (l.value instanceof String) return this.iClassLoader.STRING;
if (l.value instanceof Character) return IClass.CHAR;
if (l.value instanceof Boolean) return IClass.BOOLEAN;
if (l.value == null) return IClass.VOID;
throw new JaninoRuntimeException("SNO: Unidentifiable literal type \"" + l.value.getClass().getName() + "\"");
}
// ---------------- Atom.isType() ---------------
private boolean isType(Java.Atom a) throws CompileException {
final boolean[] res = new boolean[1];
class UCE extends RuntimeException { final CompileException ce; UCE(CompileException ce) { this.ce = ce; } }
Visitor.AtomVisitor av = new Visitor.AtomVisitor() {
// CHECKSTYLE(LineLengthCheck):OFF
// AtomVisitor
public void visitPackage(Java.Package p) { res[0] = UnitCompiler.this.isType2(p); }
// TypeVisitor
public void visitArrayType(Java.ArrayType at) { res[0] = UnitCompiler.this.isType2(at); }
public void visitBasicType(Java.BasicType bt) { res[0] = UnitCompiler.this.isType2(bt); }
public void visitReferenceType(Java.ReferenceType rt) { res[0] = UnitCompiler.this.isType2(rt); }
public void visitRvalueMemberType(Java.RvalueMemberType rmt) { res[0] = UnitCompiler.this.isType2(rmt); }
public void visitSimpleType(Java.SimpleType st) { res[0] = UnitCompiler.this.isType2(st); }
// RvalueVisitor
public void visitArrayLength(Java.ArrayLength al) { res[0] = UnitCompiler.this.isType2(al); }
public void visitAssignment(Java.Assignment a) { res[0] = UnitCompiler.this.isType2(a); }
public void visitUnaryOperation(Java.UnaryOperation uo) { res[0] = UnitCompiler.this.isType2(uo); }
public void visitBinaryOperation(Java.BinaryOperation bo) { res[0] = UnitCompiler.this.isType2(bo); }
public void visitCast(Java.Cast c) { res[0] = UnitCompiler.this.isType2(c); }
public void visitClassLiteral(Java.ClassLiteral cl) { res[0] = UnitCompiler.this.isType2(cl); }
public void visitConditionalExpression(Java.ConditionalExpression ce) { res[0] = UnitCompiler.this.isType2(ce); }
public void visitCrement(Java.Crement c) { res[0] = UnitCompiler.this.isType2(c); }
public void visitInstanceof(Java.Instanceof io) { res[0] = UnitCompiler.this.isType2(io); }
public void visitMethodInvocation(Java.MethodInvocation mi) { res[0] = UnitCompiler.this.isType2(mi); }
public void visitSuperclassMethodInvocation(Java.SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.isType2(smi); }
public void visitLiteral(Java.Literal l) { res[0] = UnitCompiler.this.isType2(l); }
public void visitNewAnonymousClassInstance(Java.NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.isType2(naci); }
public void visitNewArray(Java.NewArray na) { res[0] = UnitCompiler.this.isType2(na); }
public void visitNewInitializedArray(Java.NewInitializedArray nia) { res[0] = UnitCompiler.this.isType2(nia); }
public void visitNewClassInstance(Java.NewClassInstance nci) { res[0] = UnitCompiler.this.isType2(nci); }
public void visitParameterAccess(Java.ParameterAccess pa) { res[0] = UnitCompiler.this.isType2(pa); }
public void visitQualifiedThisReference(Java.QualifiedThisReference qtr) { res[0] = UnitCompiler.this.isType2(qtr); }
public void visitThisReference(Java.ThisReference tr) { res[0] = UnitCompiler.this.isType2(tr); }
// LvalueVisitor
public void visitAmbiguousName(Java.AmbiguousName an) { try { res[0] = UnitCompiler.this.isType2(an); } catch (CompileException e) { throw new UCE(e); } }
public void visitArrayAccessExpression(Java.ArrayAccessExpression aae) { res[0] = UnitCompiler.this.isType2(aae); }
public void visitFieldAccess(Java.FieldAccess fa) { res[0] = UnitCompiler.this.isType2(fa); }
public void visitFieldAccessExpression(Java.FieldAccessExpression fae) { res[0] = UnitCompiler.this.isType2(fae); }
public void visitSuperclassFieldAccessExpression(Java.SuperclassFieldAccessExpression scfae) { res[0] = UnitCompiler.this.isType2(scfae); }
public void visitLocalVariableAccess(Java.LocalVariableAccess lva) { res[0] = UnitCompiler.this.isType2(lva); }
public void visitParenthesizedExpression(Java.ParenthesizedExpression pe) { res[0] = UnitCompiler.this.isType2(pe); }
// CHECKSTYLE(LineLengthCheck):ON
};
try {
a.accept(av);
return res[0];
} catch (UCE uce) {
throw uce.ce;
}
}
private boolean isType2(Java.Atom a) {
return a instanceof Java.Type;
}
private boolean isType2(Java.AmbiguousName an) throws CompileException {
return this.isType(this.reclassify(an));
}
/**
* Determine whether the given {@link IClass.IMember} is accessible in the given context,
* according to JLS 6.6.1.4. Issues a {@link #compileError(String)} if not.
*/
private boolean isAccessible(
IClass.IMember member,
Java.Scope contextScope
) throws CompileException {
// you have to check that both the class and member are accessible in this scope
IClass declaringIClass = member.getDeclaringIClass();
boolean acc = this.isAccessible(declaringIClass, contextScope);
acc = acc && this.isAccessible(declaringIClass, member.getAccess(), contextScope);
return acc;
}
/**
* Check whether the given {@link IClass.IMember} is accessible in the given context,
* according to JLS 6.6.1.4. Issue a {@link #compileError(String)} if not.
*/
private void checkAccessible(
IClass.IMember member,
Java.BlockStatement contextBlockStatement
) throws CompileException {
// you have to check that both the class and member are accessible in this scope
IClass declaringIClass = member.getDeclaringIClass();
this.checkAccessible(declaringIClass, contextBlockStatement);
this.checkAccessible(declaringIClass, member.getAccess(), contextBlockStatement);
}
/**
* Determine whether a member (class, interface, field or method) declared in a
* given class is accessible from a given block statement context, according
* to JLS2 6.6.1.4.
*/
private boolean isAccessible(
IClass iClassDeclaringMember,
Access memberAccess,
Java.Scope contextScope
) throws CompileException {
return null == this.internalCheckAccessible(iClassDeclaringMember, memberAccess, contextScope);
}
/**
* Verify that a member (class, interface, field or method) declared in a
* given class is accessible from a given block statement context, according
* to JLS2 6.6.1.4. Issue a {@link #compileError(String)} if not.
*/
private void checkAccessible(
IClass iClassDeclaringMember,
Access memberAccess,
Java.BlockStatement contextBlockStatement
) throws CompileException {
String message = this.internalCheckAccessible(iClassDeclaringMember, memberAccess, contextBlockStatement);
if (message != null) this.compileError(message, contextBlockStatement.getLocation());
}
/**
* @return a descriptive text iff a member declared in that {@link IClass} with that {@link Access} is inaccessible
*/
private String internalCheckAccessible(
IClass iClassDeclaringMember,
Access memberAccess,
Java.Scope contextScope
) throws CompileException {
// At this point, memberAccess is PUBLIC, DEFAULT, PROECTEDED or PRIVATE.
// PUBLIC members are always accessible.
if (memberAccess == Access.PUBLIC) return null;
// At this point, the member is DEFAULT, PROECTEDED or PRIVATE accessible.
// Determine the class declaring the context block statement.
IClass iClassDeclaringContextBlockStatement;
for (Java.Scope s = contextScope;; s = s.getEnclosingScope()) {
if (s instanceof Java.TypeDeclaration) {
iClassDeclaringContextBlockStatement = this.resolve((Java.TypeDeclaration) s);
break;
}
}
// Access is always allowed for block statements declared in the same class as the member.
if (iClassDeclaringContextBlockStatement == iClassDeclaringMember) return null;
// Check whether the member and the context block statement are enclosed by the same
// top-level type.
{
IClass topLevelIClassEnclosingMember = iClassDeclaringMember;
for (IClass c = iClassDeclaringMember.getDeclaringIClass(); c != null; c = c.getDeclaringIClass()) {
topLevelIClassEnclosingMember = c;
}
IClass topLevelIClassEnclosingContextBlockStatement = iClassDeclaringContextBlockStatement;
for (
IClass c = iClassDeclaringContextBlockStatement.getDeclaringIClass();
c != null;
c = c.getDeclaringIClass()
) topLevelIClassEnclosingContextBlockStatement = c;
if (topLevelIClassEnclosingMember == topLevelIClassEnclosingContextBlockStatement) return null;
}
if (memberAccess == Access.PRIVATE) {
return "Private member cannot be accessed from type \"" + iClassDeclaringContextBlockStatement + "\".";
}
// At this point, the member is DEFAULT or PROTECTED accessible.
// Check whether the member and the context block statement are declared in the same
// package.
if (Descriptor.areInSamePackage(
iClassDeclaringMember.getDescriptor(),
iClassDeclaringContextBlockStatement.getDescriptor()
)) return null;
if (memberAccess == Access.DEFAULT) {
return (
"Member with \""
+ memberAccess
+ "\" access cannot be accessed from type \""
+ iClassDeclaringContextBlockStatement
+ "\"."
);
}
// At this point, the member is PROTECTED accessible.
// Check whether the class declaring the context block statement is a subclass of the
// class declaring the member or a nested class whose parent is a subclass
IClass parentClass = iClassDeclaringContextBlockStatement;
do {
if (iClassDeclaringMember.isAssignableFrom(parentClass)) {
return null;
}
parentClass = parentClass.getOuterIClass();
} while(parentClass != null);
return (
"Protected member cannot be accessed from type \""
+ iClassDeclaringContextBlockStatement
+ "\", which is neither declared in the same package as nor is a subclass of \""
+ iClassDeclaringMember
+ "\"."
);
}
/**
* Determine whether the given {@link IClass} is accessible in the given context,
* according to JLS2 6.6.1.2 and 6.6.1.4.
*/
private boolean isAccessible(
IClass type,
Java.Scope contextScope
) throws CompileException {
return null == this.internalCheckAccessible(type, contextScope);
}
/**
* Check whether the given {@link IClass} is accessible in the given context,
* according to JLS2 6.6.1.2 and 6.6.1.4. Issues a {@link #compileError(String)} if not.
*/
private void checkAccessible(
IClass type,
Java.BlockStatement contextBlockStatement
) throws CompileException {
String message = this.internalCheckAccessible(type, contextBlockStatement);
if (message != null) this.compileError(message, contextBlockStatement.getLocation());
}
private String internalCheckAccessible(
IClass type,
Java.Scope contextScope
) throws CompileException {
// Determine the type declaring the type.
IClass iClassDeclaringType = type.getDeclaringIClass();
// Check accessibility of package member type.
if (iClassDeclaringType == null) {
if (type.getAccess() == Access.PUBLIC) {
return null;
} else
if (type.getAccess() == Access.DEFAULT) {
// Determine the type declaring the context block statement.
IClass iClassDeclaringContextBlockStatement;
for (Java.Scope s = contextScope;; s = s.getEnclosingScope()) {
if (s instanceof Java.TypeDeclaration) {
iClassDeclaringContextBlockStatement = this.resolve((Java.TypeDeclaration) s);
break;
}
}
// Check whether the type is accessed from within the same package.
String packageDeclaringType = Descriptor.getPackageName(type.getDescriptor());
String contextPackage = Descriptor.getPackageName(iClassDeclaringContextBlockStatement.getDescriptor());
if (
packageDeclaringType == null
? contextPackage != null
: !packageDeclaringType.equals(contextPackage)
) return "\"" + type + "\" is inaccessible from this package";
return null;
} else
{
throw new JaninoRuntimeException("\"" + type + "\" has unexpected access \"" + type.getAccess() + "\"");
}
}
// "type" is a member type at this point.
return this.internalCheckAccessible(iClassDeclaringType, type.getAccess(), contextScope);
}
private Java.Type toTypeOrCE(Java.Atom a) throws CompileException {
Java.Type result = a.toType();
if (result == null) {
this.compileError("Expression \"" + a.toString() + "\" is not a type", a.getLocation());
return new Java.SimpleType(a.getLocation(), this.iClassLoader.OBJECT);
}
return result;
}
private Java.Rvalue toRvalueOrCE(final Java.Atom a) throws CompileException {
Java.Rvalue result = a.toRvalue();
if (result == null) {
this.compileError("Expression \"" + a.toString() + "\" is not an rvalue", a.getLocation());
return new Java.Literal(a.getLocation(), "X");
}
return result;
}
public final Java.Lvalue toLvalueOrCE(final Java.Atom a) throws CompileException {
Java.Lvalue result = a.toLvalue();
if (result == null) {
this.compileError("Expression \"" + a.toString() + "\" is not an lvalue", a.getLocation());
return new Java.Lvalue(a.getLocation()) {
public String toString() { return a.toString(); }
public void accept(Visitor.AtomVisitor visitor) {}
public void accept(Visitor.RvalueVisitor visitor) {}
public void accept(Visitor.LvalueVisitor visitor) {}
};
}
return result;
}
/**
* Copies the values of the synthetic parameters of this constructor ("this$..." and
* "val$...") to the synthetic fields of the object ("this$..." and "val$...").
*/
void assignSyntheticParametersToSyntheticFields(Java.ConstructorDeclarator cd) throws CompileException {
for (Iterator it = cd.getDeclaringClass().syntheticFields.values().iterator(); it.hasNext();) {
IClass.IField sf = (IClass.IField) it.next();
Java.LocalVariable syntheticParameter = (Java.LocalVariable) cd.syntheticParameters.get(sf.getName());
if (syntheticParameter == null) {
throw new JaninoRuntimeException(
"SNO: Synthetic parameter for synthetic field \""
+ sf.getName()
+ "\" not found"
);
}
Java.ExpressionStatement es = new Java.ExpressionStatement(new Java.Assignment(
cd.getLocation(), // location
new Java.FieldAccess( // lhs
cd.getLocation(), // location
new Java.ThisReference(cd.getLocation()), // lhs
sf // field
),
"=", // operator
new Java.LocalVariableAccess( // rhs
cd.getLocation(), // location
syntheticParameter // localVariable
)
));
es.setEnclosingScope(cd);
this.compile(es);
}
}
/**
* Compiles the instance variable initializers and the instance initializers in their
* lexical order.
*/
void initializeInstanceVariablesAndInvokeInstanceInitializers(
Java.ConstructorDeclarator cd
) throws CompileException {
for (int i = 0; i < cd.getDeclaringClass().variableDeclaratorsAndInitializers.size(); ++i) {
Java.TypeBodyDeclaration tbd = (
(Java.TypeBodyDeclaration) cd.getDeclaringClass().variableDeclaratorsAndInitializers.get(i)
);
if (!tbd.isStatic()) {
Java.BlockStatement bs = (Java.BlockStatement) tbd;
if (!this.compile(bs)) {
this.compileError(
"Instance variable declarator or instance initializer does not complete normally",
bs.getLocation()
);
}
}
}
}
/**
* Statements that jump out of blocks ("return", "break", "continue")
* must call this method to make sure that the "finally" clauses of all
* "try...catch" statements are executed.
*/
private void leaveStatements(
Java.Scope from,
Java.Scope to,
IClass optionalStackValueType
) {
for (Java.Scope s = from; s != to; s = s.getEnclosingScope()) {
if (s instanceof Java.BlockStatement) {
this.leave((Java.BlockStatement) s, optionalStackValueType);
}
}
}
/**
* The LHS operand of type <code>lhsType</code> is expected on the stack.
* <p>
* The following operators are supported:
* <code> | ^ & * / % + - << >> >>></code>
*/
private IClass compileArithmeticBinaryOperation(
Locatable l,
IClass lhsType,
String operator,
Java.Rvalue rhs
) throws CompileException {
return this.compileArithmeticOperation(
l,
lhsType,
Arrays.asList(new Java.Rvalue[] { rhs }).iterator(),
operator
);
}
/**
* Execute an arithmetic operation on a sequence of <code>operands</code>. If
* <code>type</code> is non-null, the first operand with that type is already on the stack.
* <p>
* The following operators are supported:
* <code> | ^ & * / % + - << >> >>></code>
*/
private IClass compileArithmeticOperation(
final Locatable l,
IClass type,
Iterator operands,
String operator
) throws CompileException {
if (
operator == "|" ||
operator == "^" ||
operator == "&"
) {
final int iopcode = (
operator == "&" ? Opcode.IAND :
operator == "|" ? Opcode.IOR :
operator == "^" ? Opcode.IXOR : Integer.MAX_VALUE
);
do {
Java.Rvalue operand = (Java.Rvalue) operands.next();
if (type == null) {
type = this.compileGetValue(operand);
} else {
CodeContext.Inserter convertLhsInserter = this.codeContext.newInserter();
IClass rhsType = this.compileGetValue(operand);
if (
type.isPrimitiveNumeric() &&
rhsType.isPrimitiveNumeric()
) {
IClass promotedType = this.binaryNumericPromotion(l, type, convertLhsInserter, rhsType);
if (promotedType == IClass.INT) {
this.writeOpcode(l, iopcode);
} else
if (promotedType == IClass.LONG) {
this.writeOpcode(l, iopcode + 1);
} else
{
this.compileError((
"Operator \""
+ operator
+ "\" not defined on types \""
+ type
+ "\" and \""
+ rhsType
+ "\""
), l.getLocation());
}
type = promotedType;
} else
if (
(type == IClass.BOOLEAN && this.getUnboxedType(rhsType) == IClass.BOOLEAN) ||
(this.getUnboxedType(type) == IClass.BOOLEAN && rhsType == IClass.BOOLEAN)
) {
IClassLoader icl = this.iClassLoader;
if (type == icl.BOOLEAN) {
this.codeContext.pushInserter(convertLhsInserter);
try {
this.unboxingConversion(l, icl.BOOLEAN, IClass.BOOLEAN);
} finally {
this.codeContext.popInserter();
}
}
if (rhsType == icl.BOOLEAN) {
this.unboxingConversion(l, icl.BOOLEAN, IClass.BOOLEAN);
}
this.writeOpcode(l, iopcode);
type = IClass.BOOLEAN;
} else
{
this.compileError((
"Operator \""
+ operator
+ "\" not defined on types \""
+ type
+ "\" and \""
+ rhsType
+ "\""
), l.getLocation());
type = IClass.INT;
}
}
} while (operands.hasNext());
return type;
}
if (
operator == "*" ||
operator == "/" ||
operator == "%" ||
operator == "+" ||
operator == "-"
) {
final int iopcode = (
operator == "*" ? Opcode.IMUL :
operator == "/" ? Opcode.IDIV :
operator == "%" ? Opcode.IREM :
operator == "+" ? Opcode.IADD :
operator == "-" ? Opcode.ISUB : Integer.MAX_VALUE
);
do {
Java.Rvalue operand = (Java.Rvalue) operands.next();
IClass operandType = this.getType(operand);
IClassLoader icl = this.iClassLoader;
// String concatenation?
if (operator == "+" && (type == icl.STRING || operandType == icl.STRING)) {
return this.compileStringConcatenation(l, type, operand, operands);
}
if (type == null) {
type = this.compileGetValue(operand);
} else {
CodeContext.Inserter convertLhsInserter = this.codeContext.newInserter();
IClass rhsType = this.compileGetValue(operand);
type = this.binaryNumericPromotion(l, type, convertLhsInserter, rhsType);
int opcode;
if (type == IClass.INT) {
opcode = iopcode;
} else
if (type == IClass.LONG) {
opcode = iopcode + 1;
} else
if (type == IClass.FLOAT) {
opcode = iopcode + 2;
} else
if (type == IClass.DOUBLE) {
opcode = iopcode + 3;
} else
{
this.compileError("Unexpected promoted type \"" + type + "\"", l.getLocation());
opcode = iopcode;
}
this.writeOpcode(l, opcode);
}
} while (operands.hasNext());
return type;
}
if (
operator == "<<" ||
operator == ">>" ||
operator == ">>>"
) {
final int iopcode = (
operator == "<<" ? Opcode.ISHL :
operator == ">>" ? Opcode.ISHR :
operator == ">>>" ? Opcode.IUSHR : Integer.MAX_VALUE
);
do {
Java.Rvalue operand = (Java.Rvalue) operands.next();
if (type == null) {
type = this.compileGetValue(operand);
} else {
CodeContext.Inserter convertLhsInserter = this.codeContext.newInserter();
IClass rhsType = this.compileGetValue(operand);
IClass promotedLhsType;
this.codeContext.pushInserter(convertLhsInserter);
try {
promotedLhsType = this.unaryNumericPromotion(l, type);
} finally {
this.codeContext.popInserter();
}
if (promotedLhsType != IClass.INT && promotedLhsType != IClass.LONG) {
this.compileError(
"Shift operation not allowed on operand type \"" + type + "\"",
l.getLocation()
);
}
IClass promotedRhsType = this.unaryNumericPromotion(l, rhsType);
if (promotedRhsType != IClass.INT && promotedRhsType != IClass.LONG) {
this.compileError(
"Shift distance of type \"" + rhsType + "\" is not allowed",
l.getLocation()
);
}
if (promotedRhsType == IClass.LONG) this.writeOpcode(l, Opcode.L2I);
this.writeOpcode(l, promotedLhsType == IClass.LONG ? iopcode + 1 : iopcode);
type = promotedLhsType;
}
} while (operands.hasNext());
return type;
}
throw new JaninoRuntimeException("Unexpected operator \"" + operator + "\"");
}
/**
* @param type If non-null, the first operand with that type is already on the stack
* @param operand The next operand
* @param operands All following operands ({@link Iterator} over {@link Java.Rvalue}s)
*/
private IClass compileStringConcatenation(
final Locatable l,
IClass type,
Java.Rvalue operand,
Iterator operands
) throws CompileException {
boolean operandOnStack;
if (type != null) {
this.stringConversion(l, type);
operandOnStack = true;
} else
{
operandOnStack = false;
}
// Compute list of operands and merge consecutive constant operands.
List tmp = new ArrayList(); // Compilable
do {
Object cv = this.getConstantValue(operand);
if (cv == null) {
// Non-constant operand.
final Java.Rvalue finalOperand = operand;
tmp.add(new Compilable() {
public void compile() throws CompileException {
UnitCompiler.this.stringConversion(l, UnitCompiler.this.compileGetValue(finalOperand));
}
});
operand = operands.hasNext() ? (Java.Rvalue) operands.next() : null;
} else
{
// Constant operand. Check to see whether the next operand is also constant.
if (operands.hasNext()) {
operand = (Java.Rvalue) operands.next();
Object cv2 = this.getConstantValue(operand);
if (cv2 != null) {
StringBuffer sb = new StringBuffer(cv.toString()).append(cv2);
for (;;) {
if (!operands.hasNext()) {
operand = null;
break;
}
operand = (Java.Rvalue) operands.next();
Object cv3 = this.getConstantValue(operand);
if (cv3 == null) break;
sb.append(cv3);
}
cv = sb.toString();
}
} else
{
operand = null;
}
// Break long string constants up into UTF8-able chunks.
final String[] ss = UnitCompiler.makeUTF8Able(cv.toString());
for (int i = 0; i < ss.length; ++i) {
final String s = ss[i];
tmp.add(new Compilable() {
public void compile() throws CompileException {
UnitCompiler.this.pushConstant(l, s);
}
});
}
}
} while (operand != null);
// At this point "tmp" contains an optimized sequence of Strings (representing constant
// portions) and Rvalues (non-constant portions).
if (tmp.size() <= (operandOnStack ? STRING_CONCAT_LIMIT - 1 : STRING_CONCAT_LIMIT)) {
// String concatenation through "a.concat(b).concat(c)".
for (Iterator it = tmp.iterator(); it.hasNext();) {
Compilable c = (Compilable) it.next();
c.compile();
// Concatenate.
if (operandOnStack) {
this.writeOpcode(l, Opcode.INVOKEVIRTUAL);
this.writeConstantMethodrefInfo(
Descriptor.STRING,
"concat", // classFD
"(" + Descriptor.STRING + ")" + Descriptor.STRING // methodMD
);
} else
{
operandOnStack = true;
}
}
return this.iClassLoader.STRING;
}
// String concatenation through "new StringBuffer(a).append(b).append(c).append(d).toString()".
Iterator it = tmp.iterator();
String stringBuilferFD = this.isStringBuilderAvailable ? Descriptor.STRING_BUILDER : Descriptor.STRING_BUFFER;
// "new StringBuffer(a)":
if (operandOnStack) {
this.writeOpcode(l, Opcode.NEW);
this.writeConstantClassInfo(stringBuilferFD);
this.writeOpcode(l, Opcode.DUP_X1);
this.writeOpcode(l, Opcode.SWAP);
} else
{
this.writeOpcode(l, Opcode.NEW);
this.writeConstantClassInfo(stringBuilferFD);
this.writeOpcode(l, Opcode.DUP);
((Compilable) it.next()).compile();
}
this.writeOpcode(l, Opcode.INVOKESPECIAL);
this.writeConstantMethodrefInfo(
stringBuilferFD,
"<init>", // classFD
"(" + Descriptor.STRING + ")" + Descriptor.VOID_ // methodMD
);
while (it.hasNext()) {
((Compilable) it.next()).compile();
// "StringBuffer.append(b)":
this.writeOpcode(l, Opcode.INVOKEVIRTUAL);
this.writeConstantMethodrefInfo(
stringBuilferFD,
"append", // classFD
"(" + Descriptor.STRING + ")" + stringBuilferFD // methodMD
);
}
// "StringBuffer.toString()":
this.writeOpcode(l, Opcode.INVOKEVIRTUAL);
this.writeConstantMethodrefInfo(
stringBuilferFD,
"toString", // classFD
"()" + Descriptor.STRING // methodMD
);
return this.iClassLoader.STRING;
}
interface Compilable { void compile() throws CompileException; }
/**
* Convert object of type "sourceType" to type "String". JLS2 15.18.1.1
*/
private void stringConversion(
Locatable l,
IClass sourceType
) {
this.writeOpcode(l, Opcode.INVOKESTATIC);
this.writeConstantMethodrefInfo(
Descriptor.STRING,
"valueOf", // classFD
"(" + (( // methodMD
sourceType == IClass.BOOLEAN ||
sourceType == IClass.CHAR ||
sourceType == IClass.LONG ||
sourceType == IClass.FLOAT ||
sourceType == IClass.DOUBLE
) ? sourceType.getDescriptor() : (
sourceType == IClass.BYTE ||
sourceType == IClass.SHORT ||
sourceType == IClass.INT
) ? Descriptor.INT_ : Descriptor.OBJECT) + ")" + Descriptor.STRING
);
}
/**
* Expects the object to initialize on the stack.
* <p>
* Notice: This method is used both for explicit constructor invocation (first statement of
* a constructor body) and implicit constructor invocation (right after NEW).
*
* @param optionalEnclosingInstance Used if the target class is an inner class
*/
private void invokeConstructor(
Locatable l,
Java.Scope scope,
Java.Rvalue optionalEnclosingInstance,
IClass targetClass,
Java.Rvalue[] arguments
) throws CompileException {
// Find constructors.
IClass.IConstructor[] iConstructors = targetClass.getDeclaredIConstructors();
if (iConstructors.length == 0) {
throw new JaninoRuntimeException(
"SNO: Target class \"" + targetClass.getDescriptor() + "\" has no constructors"
);
}
IClass.IConstructor iConstructor = (IClass.IConstructor) this.findMostSpecificIInvocable(
l,
iConstructors, // iInvocables
arguments, // arguments
scope // contextScope
);
// Check exceptions that the constructor may throw.
IClass[] thrownExceptions = iConstructor.getThrownExceptions();
for (int i = 0; i < thrownExceptions.length; ++i) {
this.checkThrownException(
l,
thrownExceptions[i],
scope
);
}
// Pass enclosing instance as a synthetic parameter.
if (optionalEnclosingInstance != null) {
IClass outerIClass = targetClass.getOuterIClass();
if (outerIClass != null) {
IClass eiic = this.compileGetValue(optionalEnclosingInstance);
if (!outerIClass.isAssignableFrom(eiic)) {
this.compileError(
"Type of enclosing instance (\"" + eiic + "\") is not assignable to \"" + outerIClass + "\"",
l.getLocation()
);
}
}
}
// Pass local variables to constructor as synthetic parameters.
{
IClass.IField[] syntheticFields = targetClass.getSyntheticIFields();
// Determine enclosing function declarator and type declaration.
Java.TypeBodyDeclaration scopeTBD;
Java.TypeDeclaration scopeTypeDeclaration;
{
Java.Scope s = scope;
for (; !(s instanceof Java.TypeBodyDeclaration); s = s.getEnclosingScope());
scopeTBD = (Java.TypeBodyDeclaration) s;
scopeTypeDeclaration = scopeTBD.getDeclaringType();
}
if (!(scopeTypeDeclaration instanceof Java.ClassDeclaration)) {
if (syntheticFields.length > 0) {
throw new JaninoRuntimeException("SNO: Target class has synthetic fields");
}
} else {
Java.ClassDeclaration scopeClassDeclaration = (Java.ClassDeclaration) scopeTypeDeclaration;
for (int i = 0; i < syntheticFields.length; ++i) {
IClass.IField sf = syntheticFields[i];
if (!sf.getName().startsWith("val$")) continue;
IClass.IField eisf = (IClass.IField) scopeClassDeclaration.syntheticFields.get(sf.getName());
if (eisf != null) {
if (scopeTBD instanceof Java.MethodDeclarator) {
this.load(l, this.resolve(scopeClassDeclaration), 0);
this.writeOpcode(l, Opcode.GETFIELD);
this.writeConstantFieldrefInfo(
this.resolve(scopeClassDeclaration).getDescriptor(),
sf.getName(), // classFD
sf.getDescriptor() // fieldFD
);
} else
if (scopeTBD instanceof Java.ConstructorDeclarator) {
Java.ConstructorDeclarator constructorDeclarator = (Java.ConstructorDeclarator) scopeTBD;
Java.LocalVariable syntheticParameter = (
(Java.LocalVariable) constructorDeclarator.syntheticParameters.get(sf.getName())
);
if (syntheticParameter == null) {
this.compileError((
"Compiler limitation: Constructor cannot access local variable \""
+ sf.getName().substring(4)
+ "\" declared in an enclosing block because none of the methods accesses it. "
+ "As a workaround, declare a dummy method that accesses the local variable."
), l.getLocation());
this.writeOpcode(l, Opcode.ACONST_NULL);
} else {
this.load(l, syntheticParameter);
}
} else {
this.compileError((
"Compiler limitation: "
+ "Initializers cannot access local variables declared in an enclosing block."
), l.getLocation());
this.writeOpcode(l, Opcode.ACONST_NULL);
}
} else {
String localVariableName = sf.getName().substring(4);
Java.LocalVariable lv;
DETERMINE_LV: {
Java.Scope s;
// Does one of the enclosing blocks declare a local variable with that name?
for (s = scope; s instanceof Java.BlockStatement; s = s.getEnclosingScope()) {
Java.BlockStatement bs = (Java.BlockStatement) s;
Java.Scope es = bs.getEnclosingScope();
List statements;
if (es instanceof Java.Block) {
statements = ((Java.Block) es).statements;
} else
if (es instanceof Java.FunctionDeclarator) {
statements = ((Java.FunctionDeclarator) es).optionalStatements;
} else
{
continue;
}
for (Iterator it = statements.iterator();;) {
Java.BlockStatement bs2 = (Java.BlockStatement) it.next();
if (bs2 == bs) break;
if (bs2 instanceof Java.LocalVariableDeclarationStatement) {
Java.LocalVariableDeclarationStatement lvds = (
(Java.LocalVariableDeclarationStatement) bs2
);
Java.VariableDeclarator[] vds = lvds.variableDeclarators;
for (int j = 0; j < vds.length; ++j) {
if (vds[j].name.equals(localVariableName)) {
lv = this.getLocalVariable(lvds, vds[j]);
break DETERMINE_LV;
}
}
}
}
}
// Does the declaring function declare a parameter with that name?
while (!(s instanceof Java.FunctionDeclarator)) s = s.getEnclosingScope();
Java.FunctionDeclarator fd = (Java.FunctionDeclarator) s;
for (int j = 0; j < fd.formalParameters.length; ++j) {
Java.FunctionDeclarator.FormalParameter fp = fd.formalParameters[j];
if (fp.name.equals(localVariableName)) {
lv = this.getLocalVariable(fp);
break DETERMINE_LV;
}
}
throw new JaninoRuntimeException(
"SNO: Synthetic field \""
+ sf.getName()
+ "\" neither maps a synthetic field of an enclosing instance nor a local variable"
);
}
this.load(l, lv);
}
}
}
}
// Evaluate constructor arguments.
IClass[] parameterTypes = iConstructor.getParameterTypes();
for (int i = 0; i < arguments.length; ++i) {
this.assignmentConversion(
l, // l
this.compileGetValue(arguments[i]), // sourceType
parameterTypes[i], // targetType
this.getConstantValue(arguments[i]) // optionalConstantValue
);
}
// Invoke!
// Notice that the method descriptor is "iConstructor.getDescriptor()" prepended with the
// synthetic parameters.
this.writeOpcode(l, Opcode.INVOKESPECIAL);
this.writeConstantMethodrefInfo(
targetClass.getDescriptor(),
"<init>", // classFD
iConstructor.getDescriptor() // methodMD
);
}
/*package*/ IClass.IField[] getIFields(final Java.FieldDeclaration fd) {
IClass.IField[] res = new IClass.IField[fd.variableDeclarators.length];
for (int i = 0; i < res.length; ++i) {
final Java.VariableDeclarator vd = fd.variableDeclarators[i];
res[i] = this.resolve(fd.getDeclaringType()).new IField() {
// Implement IMember.
public Access getAccess() {
switch (fd.modifiers & Mod.PPP) {
case Mod.PRIVATE:
return Access.PRIVATE;
case Mod.PROTECTED:
return Access.PROTECTED;
case Mod.PACKAGE:
return Access.DEFAULT;
case Mod.PUBLIC:
return Access.PUBLIC;
default:
throw new JaninoRuntimeException("Invalid access");
}
}
// Implement "IField".
public boolean isStatic() { return (fd.modifiers & Mod.STATIC) != 0; }
public IClass getType() throws CompileException {
return UnitCompiler.this.getType(fd.type).getArrayIClass(
vd.brackets,
UnitCompiler.this.iClassLoader.OBJECT
);
}
public String getName() { return vd.name; }
public Object getConstantValue() throws CompileException {
if (
(fd.modifiers & Mod.FINAL) != 0 &&
vd.optionalInitializer instanceof Java.Rvalue
) {
Object constantInitializerValue = UnitCompiler.this.getConstantValue(
(Java.Rvalue) vd.optionalInitializer
);
if (constantInitializerValue != null) return UnitCompiler.this.assignmentConversion(
(Locatable) vd.optionalInitializer, // l
constantInitializerValue, // value
this.getType() // targetType
);
}
return null;
}
};
}
return res;
}
/**
* Determine the non-constant-final initializer of the given {@link Java.VariableDeclarator}.
*
* @return <code>null</code> if the variable is declared without an initializer or if the initializer is
* constant-final
*/
Java.ArrayInitializerOrRvalue getNonConstantFinalInitializer(
Java.FieldDeclaration fd,
Java.VariableDeclarator vd
) throws CompileException {
// Check if optional initializer exists.
if (vd.optionalInitializer == null) return null;
// Check if initializer is constant-final.
if (
(fd.modifiers & Mod.STATIC) != 0 &&
(fd.modifiers & Mod.FINAL) != 0 &&
vd.optionalInitializer instanceof Java.Rvalue &&
this.getConstantValue((Java.Rvalue) vd.optionalInitializer) != null
) return null;
return vd.optionalInitializer;
}
private Java.Atom reclassify(Java.AmbiguousName an) throws CompileException {
if (an.reclassified == null) {
an.reclassified = this.reclassifyName(
an.getLocation(),
(Java.Scope) an.getEnclosingBlockStatement(),
an.identifiers, an.n
);
}
return an.reclassified;
}
/**
* JLS 6.5.2.2
* <p>
* Reclassify the ambiguous name consisting of the first <code>n</code> of the <code>identifiers</code>.
*
* @param location
* @param scope
* @param identifiers
* @param n
* @throws CompileException
*/
private Java.Atom reclassifyName(
Location location,
Java.Scope scope,
final String[] identifiers,
int n
) throws CompileException {
if (n == 1) return this.reclassifyName(
location,
scope,
identifiers[0]
);
// 6.5.2.2
Java.Atom lhs = this.reclassifyName(
location,
scope,
identifiers, n - 1
);
String rhs = identifiers[n - 1];
// 6.5.2.2.1
if (UnitCompiler.DEBUG) System.out.println("lhs = " + lhs);
if (lhs instanceof Java.Package) {
String className = ((Java.Package) lhs).name + '.' + rhs;
IClass result = findClassByName(location, className);
if (result != null) return new Java.SimpleType(location, result);
return new Java.Package(location, className);
}
// 6.5.2.2.3.2 EXPRESSION.length
if (rhs.equals("length") && this.getType(lhs).isArray()) {
Java.ArrayLength al = new Java.ArrayLength(location, this.toRvalueOrCE(lhs));
if (!(scope instanceof Java.BlockStatement)) {
this.compileError("\".length\" only allowed in expression context");
return al;
}
al.setEnclosingBlockStatement((Java.BlockStatement) scope);
return al;
}
IClass lhsType = this.getType(lhs);
// Notice: Don't need to check for 6.5.2.2.2.1 TYPE.METHOD and 6.5.2.2.3.1
// EXPRESSION.METHOD here because that has been done before.
{
IClass.IField field = this.findIField(lhsType, rhs, location);
if (field != null) {
// 6.5.2.2.2.2 TYPE.FIELD
// 6.5.2.2.3.2 EXPRESSION.FIELD
Java.FieldAccess fa = new Java.FieldAccess(
location,
lhs,
field
);
fa.setEnclosingBlockStatement((Java.BlockStatement) scope);
return fa;
}
}
IClass[] classes = lhsType.getDeclaredIClasses();
for (int i = 0; i < classes.length; ++i) {
final IClass memberType = classes[i];
String name = Descriptor.toClassName(memberType.getDescriptor());
name = name.substring(name.lastIndexOf('$') + 1);
if (name.equals(rhs)) {
// 6.5.2.2.2.3 TYPE.TYPE
// 6.5.2.2.3.3 EXPRESSION.TYPE
return new Java.SimpleType(location, memberType);
}
}
this.compileError(
"\"" + rhs + "\" is neither a method, a field, nor a member class of \"" + lhsType + "\"",
location
);
return new Java.Atom(location) {
public String toString() { return Java.join(identifiers, "."); }
public final void accept(Visitor.AtomVisitor visitor) {}
};
}
private IClass findClassByName(Location location, String className) throws CompileException {
IClass res = this.findClass(className);
if (res != null) return res;
try {
return this.iClassLoader.loadIClass(Descriptor.fromClassName(className));
} catch (ClassNotFoundException ex) {
if (ex.getException() instanceof CompileException) throw (CompileException) ex.getException();
throw new CompileException(className, location, ex);
}
}
/**
* JLS 6.5.2.1
* @param location
* @param scope
* @param identifier
* @throws CompileException
*/
private Java.Atom reclassifyName(
Location location,
Java.Scope scope,
final String identifier
) throws CompileException {
// Determine scope block statement, type body declaration, type and compilation unit.
Java.BlockStatement scopeBlockStatement = null;
Java.TypeBodyDeclaration scopeTBD = null;
Java.AbstractTypeDeclaration scopeTypeDeclaration = null;
Java.CompilationUnit scopeCompilationUnit;
{
Java.Scope s = scope;
if (s instanceof Java.BlockStatement) scopeBlockStatement = (Java.BlockStatement) s;
while (
(s instanceof Java.BlockStatement || s instanceof Java.CatchClause)
&& !(s instanceof Java.TypeBodyDeclaration)
) s = s.getEnclosingScope();
if (s instanceof Java.TypeBodyDeclaration) {
scopeTBD = (Java.TypeBodyDeclaration) s;
s = s.getEnclosingScope();
}
if (s instanceof Java.TypeDeclaration) {
scopeTypeDeclaration = (Java.AbstractTypeDeclaration) s;
s = s.getEnclosingScope();
}
while (!(s instanceof Java.CompilationUnit)) s = s.getEnclosingScope();
scopeCompilationUnit = (Java.CompilationUnit) s;
}
// 6.5.2.1.BL1
// 6.5.2.BL1.B1.B1.1 (JLS3: 6.5.2.BL1.B1.B1.1) / 6.5.6.1.1 Local variable.
// 6.5.2.BL1.B1.B1.2 (JLS3: 6.5.2.BL1.B1.B1.2) / 6.5.6.1.1 Parameter.
{
Java.Scope s = scope;
if (s instanceof Java.BlockStatement) {
Java.BlockStatement bs = (BlockStatement) s;
Java.LocalVariable lv = bs.findLocalVariable(identifier);
if (lv != null) {
Java.LocalVariableAccess lva = new Java.LocalVariableAccess(location, lv);
lva.setEnclosingBlockStatement(bs);
return lva;
}
s = s.getEnclosingScope();
}
while (s instanceof Java.BlockStatement || s instanceof Java.CatchClause) s = s.getEnclosingScope();
if (s instanceof Java.FunctionDeclarator) {
s = s.getEnclosingScope();
}
if (s instanceof Java.InnerClassDeclaration) {
Java.InnerClassDeclaration icd = (Java.InnerClassDeclaration) s;
s = s.getEnclosingScope();
if (s instanceof Java.AnonymousClassDeclaration) s = s.getEnclosingScope();
while (s instanceof Java.BlockStatement) {
Java.LocalVariable lv = ((Java.BlockStatement) s).findLocalVariable(identifier);
if (lv != null) {
if (!lv.finaL) {
this.compileError(
"Cannot access non-final local variable \""
+ identifier
+ "\" from inner class"
);
}
final IClass lvType = lv.type;
IClass.IField iField = new SimpleIField(
this.resolve(icd),
"val$" + identifier,
lvType
);
icd.defineSyntheticField(iField);
Java.FieldAccess fa = new Java.FieldAccess(
location, // location
new Java.QualifiedThisReference( // lhs
location, // location
new Java.SimpleType(location, this.resolve(icd)) // qualification
),
iField // field
);
fa.setEnclosingBlockStatement((Java.BlockStatement) scope);
return fa;
}
s = s.getEnclosingScope();
while (s instanceof Java.BlockStatement) s = s.getEnclosingScope();
if (!(s instanceof Java.FunctionDeclarator)) break;
s = s.getEnclosingScope();
if (!(s instanceof Java.InnerClassDeclaration)) break;
icd = (Java.InnerClassDeclaration) s;
s = s.getEnclosingScope();
}
}
}
// 6.5.2.BL1.B1.B1.3 (JLS3: 6.5.2.BL1.B1.B1.3) / 6.5.6.1.2.1 Field.
Java.BlockStatement enclosingBlockStatement = null;
for (Java.Scope s = scope; !(s instanceof Java.CompilationUnit); s = s.getEnclosingScope()) {
if (s instanceof Java.BlockStatement && enclosingBlockStatement == null) {
enclosingBlockStatement = (Java.BlockStatement) s;
}
if (s instanceof Java.TypeDeclaration) {
final Java.AbstractTypeDeclaration enclosingTypeDecl = (Java.AbstractTypeDeclaration) s;
final IClass etd = UnitCompiler.this.resolve(enclosingTypeDecl);
final IClass.IField f = this.findIField(etd, identifier, location);
if (f != null) {
if (f.isStatic()) {
this.warning("IASF", (
"Implicit access to static field \""
+ identifier
+ "\" of declaring class (better write \""
+ f.getDeclaringIClass()
+ '.'
+ f.getName()
+ "\")"
), location);
} else
if (f.getDeclaringIClass() == etd) {
this.warning("IANSF", (
"Implicit access to non-static field \""
+ identifier
+ "\" of declaring class (better write \"this."
+ f.getName()
+ "\")"
), location);
} else {
this.warning("IANSFEI", (
"Implicit access to non-static field \""
+ identifier
+ "\" of enclosing instance (better write \""
+ f.getDeclaringIClass()
+ ".this."
+ f.getName()
+ "\")"
), location);
}
Java.SimpleType ct = new Java.SimpleType(scopeTypeDeclaration.getLocation(), (IClass) etd);
Java.Atom lhs;
if (scopeTBD.isStatic()) {
// Field access in static method context.
lhs = ct;
} else
{
// Field access in non-static method context.
if (f.isStatic()) {
// Access to static field.
lhs = ct;
} else {
// Access to non-static field.
lhs = new Java.QualifiedThisReference(location, ct);
}
}
Java.Rvalue res = new Java.FieldAccess(
location,
lhs,
f
);
res.setEnclosingBlockStatement(enclosingBlockStatement);
return res;
}
}
}
// JLS3 6.5.2.BL1.B1.B2.1 Static field imported through single static import.
{
List l = (List) this.singleStaticImports.get(identifier);
if (l != null) {
for (Iterator it = l.iterator(); it.hasNext();) {
Object o = it.next();
if (o instanceof IField) {
FieldAccess fieldAccess = new FieldAccess(
location,
new SimpleType(location, ((IField) o).getDeclaringIClass()),
(IField) o
);
fieldAccess.setEnclosingBlockStatement(enclosingBlockStatement);
return fieldAccess;
}
}
}
}
// JLS3 6.5.2.BL1.B1.B2.2 Static field imported through static-import-on-demand.
{
IField importedField = null;
for (Iterator it = this.staticImportsOnDemand.iterator(); it.hasNext();) {
IClass iClass = (IClass) it.next();
IField f = iClass.getDeclaredIField(identifier);
if (f != null) {
// JLS3 7.5.4 Static-Import-on-Demand Declaration
if (!UnitCompiler.this.isAccessible(f, enclosingBlockStatement)) continue;
if (importedField != null) {
UnitCompiler.this.compileError(
"Ambiguous static field import: \""
+ importedField.toString()
+ "\" vs. \""
+ f.toString()
+ "\""
);
}
importedField = f;
}
}
if (importedField != null) {
if (!importedField.isStatic()) UnitCompiler.this.compileError("Cannot static-import non-static field");
FieldAccess fieldAccess = new FieldAccess(
location,
new SimpleType(location, importedField.getDeclaringIClass()),
importedField
);
fieldAccess.setEnclosingBlockStatement(enclosingBlockStatement);
return fieldAccess;
}
}
// Hack: "java" MUST be a package, not a class.
if (identifier.equals("java")) return new Java.Package(location, identifier);
// 6.5.2.BL1.B1.B2.1 (JLS3: 6.5.2.BL1.B1.B3.2) Local class.
{
Java.LocalClassDeclaration lcd = this.findLocalClassDeclaration(scope, identifier);
if (lcd != null) return new Java.SimpleType(location, this.resolve(lcd));
}
// 6.5.2.BL1.B1.B2.2 (JLS3: 6.5.2.BL1.B1.B3.3) Member type.
if (scopeTypeDeclaration != null) {
IClass memberType = this.findMemberType(
UnitCompiler.this.resolve(scopeTypeDeclaration),
identifier,
location
);
if (memberType != null) return new Java.SimpleType(location, memberType);
}
// 6.5.2.BL1.B1.B3.1 (JLS3: 6.5.2.BL1.B1.B4.1) Single type import.
{
IClass iClass = this.importSingleType(identifier, location);
if (iClass != null) return new Java.SimpleType(location, iClass);
}
// 6.5.2.BL1.B1.B3.2 (JLS3: 6.5.2.BL1.B1.B3.1) Package member class/interface declared in this compilation unit.
// Notice that JLS2 looks this up AFTER local class, member type, single type import, while
// JLS3 looks this up BEFORE local class, member type, single type import.
{
Java.PackageMemberTypeDeclaration pmtd = scopeCompilationUnit.getPackageMemberTypeDeclaration(identifier);
if (pmtd != null) return new Java.SimpleType(location, this.resolve((Java.AbstractTypeDeclaration) pmtd));
}
// 6.5.2.BL1.B1.B4 Class or interface declared in same package.
// Notice: Why is this missing in JLS3?
{
String className = (
scopeCompilationUnit.optionalPackageDeclaration == null ?
identifier :
scopeCompilationUnit.optionalPackageDeclaration.packageName + '.' + identifier
);
IClass result = findClassByName(location, className);
if (result != null) return new Java.SimpleType(location, result);
}
// 6.5.2.BL1.B1.B5 (JLS3: 6.5.2.BL1.B1.B4.2), 6.5.2.BL1.B1.B6 Type-import-on-demand.
{
IClass importedClass = this.importTypeOnDemand(identifier, location);
if (importedClass != null) {
return new Java.SimpleType(location, importedClass);
}
}
// JLS3 6.5.2.BL1.B1.B4.3 Type imported through single static import.
{
List l = (List) this.singleStaticImports.get(identifier);
if (l != null) {
for (Iterator it = l.iterator(); it.hasNext();) {
Object o = it.next();
if (o instanceof IClass) return new SimpleType(null, (IClass) o);
}
}
}
// JLS3 6.5.2.BL1.B1.B4.4 Type imported through static-import-on-demand.
{
IClass importedType = null;
for (Iterator it = this.staticImportsOnDemand.iterator(); it.hasNext();) {
IClass ic = (IClass) it.next();
IClass[] memberTypes = ic.getDeclaredIClasses();
for (int i = 0; i < memberTypes.length; ++i) {
IClass mt = memberTypes[i];
if (!UnitCompiler.this.isAccessible(mt, scopeBlockStatement)) continue;
if (mt.getDescriptor().endsWith('$' + identifier + ';')) {
if (importedType != null) {
UnitCompiler.this.compileError(
"Ambiguous static type import: \""
+ importedType.toString()
+ "\" vs. \""
+ mt.toString()
+ "\""
);
}
importedType = mt;
}
}
}
if (importedType != null) return new Java.SimpleType(null, importedType);
}
// 6.5.2.BL1.B1.B7 Package name
return new Java.Package(location, identifier);
}
private void determineValue(Java.FieldAccessExpression fae) throws CompileException {
if (fae.value != null) return;
IClass lhsType = this.getType(fae.lhs);
if (fae.fieldName.equals("length") && lhsType.isArray()) {
fae.value = new Java.ArrayLength(
fae.getLocation(),
this.toRvalueOrCE(fae.lhs)
);
} else {
IClass.IField iField = this.findIField(lhsType, fae.fieldName, fae.getLocation());
if (iField == null) {
this.compileError(
"\"" + this.getType(fae.lhs).toString() + "\" has no field \"" + fae.fieldName + "\"",
fae.getLocation()
);
fae.value = new Java.Rvalue(fae.getLocation()) {
// public IClass compileGet() throws CompileException { return this.iClassLoader.OBJECT; }
public String toString() { return "???"; }
public final void accept(Visitor.AtomVisitor visitor) {}
public final void accept(Visitor.RvalueVisitor visitor) {}
};
return;
}
fae.value = new Java.FieldAccess(
fae.getLocation(),
fae.lhs,
iField
);
}
fae.value.setEnclosingBlockStatement(fae.getEnclosingBlockStatement());
}
/** "super.fld", "Type.super.fld" */
private void determineValue(Java.SuperclassFieldAccessExpression scfae) throws CompileException {
if (scfae.value != null) return;
Rvalue lhs;
{
Java.ThisReference tr = new Java.ThisReference(scfae.getLocation());
tr.setEnclosingBlockStatement(scfae.getEnclosingBlockStatement());
IClass type;
if (scfae.optionalQualification != null) {
type = UnitCompiler.this.getType(scfae.optionalQualification);
} else
{
type = this.getType(tr);
}
lhs = new Java.Cast(scfae.getLocation(), new SimpleType(scfae.getLocation(), type.getSuperclass()), tr);
}
IClass.IField iField = this.findIField(this.getType(lhs), scfae.fieldName, scfae.getLocation());
if (iField == null) {
this.compileError("Class has no field \"" + scfae.fieldName + "\"", scfae.getLocation());
scfae.value = new Java.Rvalue(scfae.getLocation()) {
public String toString() { return "???"; }
public final void accept(Visitor.AtomVisitor visitor) {}
public final void accept(Visitor.RvalueVisitor visitor) {}
};
return;
}
scfae.value = new Java.FieldAccess(
scfae.getLocation(),
lhs,
iField
);
scfae.value.setEnclosingBlockStatement(scfae.getEnclosingBlockStatement());
}
/**
* Find named methods of "targetType", examine the argument types and choose the
* most specific method. Check that only the allowed exceptions are thrown.
* <p>
* Notice that the returned {@link IClass.IMethod} may be declared in an enclosing type.
*
* @return The selected {@link IClass.IMethod} or <code>null</code>
*/
public IClass.IMethod findIMethod(Java.MethodInvocation mi) throws CompileException {
IClass.IMethod iMethod;
FIND_METHOD: {
if (mi.optionalTarget == null) {
// Method invocation by simple method name... method must be declared by an enclosing type declaration.
for (
Java.Scope s = mi.getEnclosingBlockStatement();
!(s instanceof Java.CompilationUnit);
s = s.getEnclosingScope()
) {
if (s instanceof Java.TypeDeclaration) {
Java.TypeDeclaration td = (Java.TypeDeclaration) s;
// Find methods with specified name.
iMethod = this.findIMethod(
this.resolve(td), // targetType
mi // invocation
);
if (iMethod != null) break FIND_METHOD;
}
}
} else
{
// Method invocation by "target": "expr.meth(arguments)" -- method must be declared by the target's
// type.
iMethod = this.findIMethod(
this.getType(mi.optionalTarget), // targetType
mi // invocable
);
if (iMethod != null) break FIND_METHOD;
}
// Static method declared through single static import?
{
List l = (List) this.singleStaticImports.get(mi.methodName);
if (l != null) {
iMethod = null;
for (Iterator it = l.iterator(); it.hasNext();) {
Object o = it.next();
if (o instanceof IMethod) {
IClass declaringIClass = ((IMethod) o).getDeclaringIClass();
IMethod im = this.findIMethod(
declaringIClass, // targetType
mi // invocable
);
if (im != null) {
if (iMethod != null && iMethod != im) {
UnitCompiler.this.compileError(
"Ambiguous static method import: \""
+ iMethod.toString()
+ "\" vs. \""
+ im.toString()
+ "\""
);
}
iMethod = im;
}
}
}
if (iMethod != null) break FIND_METHOD;
}
}
// Static method declared through static-import-on-demand?
iMethod = null;
for (Iterator it = this.staticImportsOnDemand.iterator(); it.hasNext();) {
IClass iClass = (IClass) it.next();
IMethod im = this.findIMethod(
iClass, // targetType
mi // invocable
);
if (im != null) {
if (iMethod != null) {
UnitCompiler.this.compileError(
"Ambiguous static method import: \""
+ iMethod.toString()
+ "\" vs. \""
+ im.toString()
+ "\""
);
}
iMethod = im;
}
}
if (iMethod != null) break FIND_METHOD;
this.compileError((
"A method named \""
+ mi.methodName
+ "\" is not declared in any enclosing class nor any supertype, nor through a static import"
), mi.getLocation());
return fakeIMethod(this.iClassLoader.OBJECT, mi.methodName, mi.arguments);
}
this.checkThrownExceptions(mi, iMethod);
return iMethod;
}
/**
* Find a {@link IClass.IMethod} in the given <code>targetType</code>, its superclasses or
* superinterfaces with the given <code>name</code> and for the given <code>arguments</code>.
* If more than one such method exists, choose the most specific one (JLS 15.11.2).
*
* @return <code>null</code> if no appropriate method could be found
*/
private IClass.IMethod findIMethod(
IClass targetType,
Invocation invocation
) throws CompileException {
// Get all methods
List ms = new ArrayList();
this.getIMethods(targetType, invocation.methodName, ms);
// JLS2 6.4.3
if (targetType.isInterface()) {
IClass.IMethod[] oms = this.iClassLoader.OBJECT.getDeclaredIMethods(invocation.methodName);
for (int i = 0; i < oms.length; ++i) {
IClass.IMethod om = oms[i];
if (!om.isStatic() && om.getAccess() == Access.PUBLIC) ms.add(om);
}
}
if (ms.size() == 0) return null;
// Determine arguments' types, choose the most specific method
return (IClass.IMethod) this.findMostSpecificIInvocable(
(Locatable) invocation, // loc
(IClass.IMethod[]) ms.toArray(new IClass.IMethod[ms.size()]), // iInvocables
invocation.arguments, // arguments
invocation.getEnclosingBlockStatement() // contextScope
);
}
private IMethod fakeIMethod(IClass targetType, final String name, Rvalue[] arguments) throws CompileException {
final IClass[] pts = new IClass[arguments.length];
for (int i = 0; i < arguments.length; ++i) pts[i] = this.getType(arguments[i]);
return targetType.new IMethod() {
public String getName() { return name; }
public IClass getReturnType() throws CompileException { return IClass.INT; }
public boolean isStatic() { return false; }
public boolean isAbstract() { return false; }
public IClass[] getParameterTypes() throws CompileException { return pts; }
public IClass[] getThrownExceptions() throws CompileException { return new IClass[0]; }
public Access getAccess() { return Access.PUBLIC; }
};
}
/**
* Add all methods with the given <code>methodName</code> that are declared
* by the <code>type</code>, its superclasses and all their superinterfaces
* to the result list <code>v</code>.
* @param type
* @param methodName
* @param v
* @throws CompileException
*/
public void getIMethods(
IClass type,
String methodName,
List v // IMethod
) throws CompileException {
// Check methods declared by this type.
{
IClass.IMethod[] ims = type.getDeclaredIMethods(methodName);
for (int i = 0; i < ims.length; ++i) v.add(ims[i]);
}
// Check superclass.
IClass superclass = type.getSuperclass();
if (superclass != null) this.getIMethods(superclass, methodName, v);
// Check superinterfaces.
IClass[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; ++i) this.getIMethods(interfaces[i], methodName, v);
}
public IClass.IMethod findIMethod(Java.SuperclassMethodInvocation scmi) throws CompileException {
Java.ClassDeclaration declaringClass;
for (Java.Scope s = scmi.getEnclosingBlockStatement();; s = s.getEnclosingScope()) {
if (s instanceof Java.FunctionDeclarator) {
Java.FunctionDeclarator fd = (Java.FunctionDeclarator) s;
if ((fd.modifiers & Mod.STATIC) != 0) {
this.compileError("Superclass method cannot be invoked in static context", scmi.getLocation());
}
}
if (s instanceof Java.ClassDeclaration) {
declaringClass = (Java.ClassDeclaration) s;
break;
}
}
IClass superclass = this.resolve(declaringClass).getSuperclass();
IMethod iMethod = this.findIMethod(
superclass, // targetType
scmi // invocation
);
if (iMethod == null) {
this.compileError(
"Class \"" + superclass + "\" has no method named \"" + scmi.methodName + "\"",
scmi.getLocation()
);
return fakeIMethod(superclass, scmi.methodName, scmi.arguments);
}
this.checkThrownExceptions(scmi, iMethod);
return iMethod;
}
/**
* Determine the arguments' types, determine the applicable invocables and choose the most specific invocable.
*
* @param iInvocables Length must be greater than zero
* @return The selected {@link IClass.IInvocable}
* @throws CompileException
*/
private IClass.IInvocable findMostSpecificIInvocable(
Locatable l,
final IInvocable[] iInvocables,
Rvalue[] arguments,
Java.Scope contextScope
) throws CompileException {
// Determine arguments' types.
final IClass[] argumentTypes = new IClass[arguments.length];
for (int i = 0; i < arguments.length; ++i) {
argumentTypes[i] = this.getType(arguments[i]);
}
// Determine most specific invocable WITHOUT boxing.
IInvocable ii = this.findMostSpecificIInvocable(l, iInvocables, argumentTypes, false, contextScope);
if (ii != null) return ii;
// Determine most specific invocable WITH boxing.
ii = this.findMostSpecificIInvocable(l, iInvocables, argumentTypes, true, contextScope);
if (ii != null) return ii;
// Report a nice compile error.
StringBuffer sb = new StringBuffer("No applicable constructor/method found for ");
if (argumentTypes.length == 0) {
sb.append("zero actual parameters");
} else {
sb.append("actual parameters \"").append(argumentTypes[0]);
for (int i = 1; i < argumentTypes.length; ++i) {
sb.append(", ").append(argumentTypes[i]);
}
sb.append("\"");
}
sb.append("; candidates are: ").append('"' + iInvocables[0].toString() + '"');
for (int i = 1; i < iInvocables.length; ++i) {
sb.append(", ").append('"' + iInvocables[i].toString() + '"');
}
this.compileError(sb.toString(), l.getLocation());
// Well, returning a "fake" IInvocable is a bit tricky, because the iInvocables
// can be of different types.
if (iInvocables[0] instanceof IClass.IConstructor) {
return iInvocables[0].getDeclaringIClass().new IConstructor() {
public IClass[] getParameterTypes() { return argumentTypes; }
public Access getAccess() { return Access.PUBLIC; }
public IClass[] getThrownExceptions() { return new IClass[0]; }
};
} else
if (iInvocables[0] instanceof IClass.IMethod) {
return iInvocables[0].getDeclaringIClass().new IMethod() {
public boolean isStatic() { return true; }
public boolean isAbstract() { return false; }
public IClass getReturnType() { return IClass.INT; }
public String getName() { return ((IClass.IMethod) iInvocables[0]).getName(); }
public Access getAccess() { return Access.PUBLIC; }
public IClass[] getParameterTypes() { return argumentTypes; }
public IClass[] getThrownExceptions() { return new IClass[0]; }
};
} else
{
return iInvocables[0];
}
}
/**
* Determine the applicable invocables and choose the most specific invocable.
*
* @return The maximally specific {@link IClass.IInvocable} or <code>null</code> if no {@link
* IClass.IInvocable} is applicable
* @throws CompileException
*/
public IClass.IInvocable findMostSpecificIInvocable(
Locatable l,
final IInvocable[] iInvocables,
final IClass[] argumentTypes,
boolean boxingPermitted,
Java.Scope contextScope
) throws CompileException {
if (UnitCompiler.DEBUG) {
System.out.println("Argument types:");
for (int i = 0; i < argumentTypes.length; ++i) {
System.out.println(argumentTypes[i]);
}
}
// Select applicable methods (15.12.2.1).
List applicableIInvocables = new ArrayList();
NEXT_METHOD:
for (int i = 0; i < iInvocables.length; ++i) {
IClass.IInvocable ii = iInvocables[i];
// Check parameter count.
IClass[] parameterTypes = ii.getParameterTypes();
if (parameterTypes.length != argumentTypes.length) continue;
if (!this.isAccessible(ii, contextScope)) continue;
// Check argument types vs. parameter types.
if (UnitCompiler.DEBUG) System.out.println("Parameter / argument type check:");
for (int j = 0; j < argumentTypes.length; ++j) {
// Is method invocation conversion possible (5.3)?
if (UnitCompiler.DEBUG) System.out.println(parameterTypes[j] + " <=> " + argumentTypes[j]);
if (!this.isMethodInvocationConvertible(argumentTypes[j], parameterTypes[j], boxingPermitted)) {
continue NEXT_METHOD;
}
}
// Applicable!
if (UnitCompiler.DEBUG) System.out.println("Applicable!");
applicableIInvocables.add(ii);
}
if (applicableIInvocables.size() == 0) return null;
// Choose the most specific invocable (15.12.2.2).
if (applicableIInvocables.size() == 1) {
return (IClass.IInvocable) applicableIInvocables.get(0);
}
// Determine the "maximally specific invocables".
List maximallySpecificIInvocables = new ArrayList();
for (int i = 0; i < applicableIInvocables.size(); ++i) {
IClass.IInvocable applicableIInvocable = (IClass.IInvocable) applicableIInvocables.get(i);
int moreSpecific = 0, lessSpecific = 0;
for (int j = 0; j < maximallySpecificIInvocables.size(); ++j) {
IClass.IInvocable mostSpecificIInvocable = (IClass.IInvocable) maximallySpecificIInvocables.get(j);
if (applicableIInvocable.isMoreSpecificThan(mostSpecificIInvocable)) {
++moreSpecific;
} else
if (applicableIInvocable.isLessSpecificThan(mostSpecificIInvocable)) {
++lessSpecific;
}
}
if (moreSpecific == maximallySpecificIInvocables.size()) {
maximallySpecificIInvocables.clear();
maximallySpecificIInvocables.add(applicableIInvocable);
} else
if (lessSpecific < maximallySpecificIInvocables.size()) {
maximallySpecificIInvocables.add(applicableIInvocable);
} else
{
;
}
if (UnitCompiler.DEBUG) System.out.println("maximallySpecificIInvocables=" + maximallySpecificIInvocables);
}
if (maximallySpecificIInvocables.size() == 1) return (IClass.IInvocable) maximallySpecificIInvocables.get(0);
ONE_NON_ABSTRACT_INVOCABLE:
if (maximallySpecificIInvocables.size() > 1 && iInvocables[0] instanceof IClass.IMethod) {
// Check if all methods have the same signature (i.e. the types of all their parameters are identical) and
// exactly one of the methods is non-abstract (JLS 15.12.2.2.BL2.B1).
IClass.IMethod theNonAbstractMethod = null;
{
Iterator it = maximallySpecificIInvocables.iterator();
IClass.IMethod m = (IClass.IMethod) it.next();
IClass[] parameterTypesOfFirstMethod = m.getParameterTypes();
for (;;) {
if (!m.isAbstract()) {
if (theNonAbstractMethod == null) {
theNonAbstractMethod = m;
} else {
IClass declaringIClass = m.getDeclaringIClass();
IClass theNonAbstractMethodDeclaringIClass = theNonAbstractMethod.getDeclaringIClass();
if (declaringIClass == theNonAbstractMethodDeclaringIClass) {
if (m.getReturnType() == theNonAbstractMethod.getReturnType()) {
throw new JaninoRuntimeException(
"Two non-abstract methods '" + m + "' have the same parameter types, "
+ "declaring type and return type"
);
} else
if (m.getReturnType().isAssignableFrom(theNonAbstractMethod.getReturnType())) {
;
} else
if (theNonAbstractMethod.getReturnType().isAssignableFrom(m.getReturnType())) {
theNonAbstractMethod = m;
} else
{
throw new JaninoRuntimeException("Incompatible return types");
}
} else
if (declaringIClass.isAssignableFrom(theNonAbstractMethodDeclaringIClass)) {
;
} else
if (theNonAbstractMethodDeclaringIClass.isAssignableFrom(declaringIClass)) {
theNonAbstractMethod = m;
} else
{
throw new JaninoRuntimeException(
"SNO: Types declaring '"
+ theNonAbstractMethod
+ "' are not assignable"
);
}
}
}
if (!it.hasNext()) break;
m = (IClass.IMethod) it.next();
IClass[] pts = m.getParameterTypes();
for (int i = 0; i < pts.length; ++i) {
if (pts[i] != parameterTypesOfFirstMethod[i]) break ONE_NON_ABSTRACT_INVOCABLE;
}
}
}
// JLS 15.12.2.2.BL2.B1.B1
if (theNonAbstractMethod != null) return theNonAbstractMethod;
// JLS 15.12.2.2.BL2.B1.B2
// Check "that exception [te1] is declared in the THROWS clause of each of the maximally specific methods".
Set s = new HashSet();
{
IClass[][] tes = new IClass[maximallySpecificIInvocables.size()][];
Iterator it = maximallySpecificIInvocables.iterator();
for (int i = 0; i < tes.length; ++i) {
tes[i] = ((IClass.IMethod) it.next()).getThrownExceptions();
}
for (int i = 0; i < tes.length; ++i) {
EACH_EXCEPTION:
for (int j = 0; j < tes[i].length; ++j) {
IClass te1 = tes[i][j];
EACH_METHOD:
for (int k = 0; k < tes.length; ++k) {
if (k == i) continue;
for (int m = 0; m < tes[k].length; ++m) {
IClass te2 = tes[k][m];
if (te2.isAssignableFrom(te1)) continue EACH_METHOD;
}
continue EACH_EXCEPTION;
}
s.add(te1);
}
}
}
// Return a "dummy" method.
final IClass.IMethod im = (IClass.IMethod) maximallySpecificIInvocables.get(0);
final IClass[] tes = (IClass[]) s.toArray(new IClass[s.size()]);
return im.getDeclaringIClass().new IMethod() {
public String getName() { return im.getName(); }
public IClass getReturnType() throws CompileException { return im.getReturnType(); }
public boolean isAbstract() { return im.isAbstract(); }
public boolean isStatic() { return im.isStatic(); }
public Access getAccess() { return im.getAccess(); }
public IClass[] getParameterTypes() throws CompileException { return im.getParameterTypes(); }
public IClass[] getThrownExceptions() { return tes; }
};
}
// JLS 15.12.2.2.BL2.B2
{
StringBuffer sb = new StringBuffer("Invocation of constructor/method with actual parameter type(s) \"");
for (int i = 0; i < argumentTypes.length; ++i) {
if (i > 0) sb.append(", ");
sb.append(Descriptor.toString(argumentTypes[i].getDescriptor()));
}
sb.append("\" is ambiguous: ");
for (int i = 0; i < maximallySpecificIInvocables.size(); ++i) {
if (i > 0) sb.append(" vs. ");
sb.append("\"" + maximallySpecificIInvocables.get(i) + "\"");
}
this.compileError(sb.toString(), l.getLocation());
}
return (IClass.IMethod) iInvocables[0];
}
/**
* Check if "method invocation conversion" (5.3) is possible.
*/
private boolean isMethodInvocationConvertible(
IClass sourceType,
IClass targetType,
boolean boxingPermitted
) throws CompileException {
// 5.3 Identity conversion.
if (sourceType == targetType) return true;
// 5.3 Widening primitive conversion.
if (this.isWideningPrimitiveConvertible(sourceType, targetType)) return true;
// 5.3 Widening reference conversion.
if (this.isWideningReferenceConvertible(sourceType, targetType)) return true;
// JLS3 5.3 A boxing conversion (JLS3 5.1.7) optionally followed by widening reference conversion.
if (boxingPermitted) {
IClass boxedType = this.isBoxingConvertible(sourceType);
if (boxedType != null) {
return (
this.isIdentityConvertible(boxedType, targetType) ||
this.isWideningReferenceConvertible(boxedType, targetType)
);
}
}
// JLS3 5.3 An unboxing conversion (JLS3 5.1.8) optionally followed by a widening primitive conversion.
if (boxingPermitted) {
IClass unboxedType = this.isUnboxingConvertible(sourceType);
if (unboxedType != null) {
return (
this.isIdentityConvertible(unboxedType, targetType) ||
this.isWideningPrimitiveConvertible(unboxedType, targetType)
);
}
}
// 5.3 TODO: FLOAT or DOUBLE value set conversion
return false;
}
/**
* @throws CompileException if the {@link Invocation} throws exceptions that are disallowed in the given scope
*/
private void checkThrownExceptions(Invocation in, IMethod iMethod) throws CompileException {
IClass[] thrownExceptions = iMethod.getThrownExceptions();
for (int i = 0; i < thrownExceptions.length; ++i) {
this.checkThrownException(
(Locatable) in, // l
thrownExceptions[i], // type
(Java.Scope) in.getEnclosingBlockStatement() // scope
);
}
}
/**
* @throws CompileException if the exception with the given type must not be thrown in the given scope
*/
private void checkThrownException(
Locatable l,
IClass type,
Java.Scope scope
) throws CompileException {
// Thrown object must be assignable to "Throwable".
if (!this.iClassLoader.THROWABLE.isAssignableFrom(type)) {
this.compileError(
"Thrown object of type \"" + type + "\" is not assignable to \"Throwable\"",
l.getLocation()
);
}
// "RuntimeException" and "Error" are never checked.
if (
this.iClassLoader.RUNTIME_EXCEPTION.isAssignableFrom(type) ||
this.iClassLoader.ERROR.isAssignableFrom(type)
) return;
for (;; scope = scope.getEnclosingScope()) {
// Match against enclosing "try...catch" blocks.
if (scope instanceof Java.TryStatement) {
Java.TryStatement ts = (Java.TryStatement) scope;
for (int i = 0; i < ts.catchClauses.size(); ++i) {
Java.CatchClause cc = (Java.CatchClause) ts.catchClauses.get(i);
IClass caughtType = this.getType(cc.caughtException.type);
if (caughtType.isAssignableFrom(type)) return;
}
} else
// Match against "throws" clause of declaring function.
if (scope instanceof Java.FunctionDeclarator) {
Java.FunctionDeclarator fd = (Java.FunctionDeclarator) scope;
for (int i = 0; i < fd.thrownExceptions.length; ++i) {
IClass te = this.getType(fd.thrownExceptions[i]);
if (te.isAssignableFrom(type)) return;
}
break;
} else
if (scope instanceof Java.TypeBodyDeclaration) {
break;
}
}
this.compileError((
"Thrown exception of type \""
+ type
+ "\" is neither caught by a \"try...catch\" block "
+ "nor declared in the \"throws\" clause of the declaring function"
), l.getLocation());
}
private IClass getTargetIClass(Java.QualifiedThisReference qtr) throws CompileException {
// Determine target type.
if (qtr.targetIClass == null) {
qtr.targetIClass = this.getType(qtr.qualification);
}
return qtr.targetIClass;
}
/**
* Checks whether the operand is an integer-like local variable.
*/
Java.LocalVariable isIntLV(Java.Crement c) throws CompileException {
if (!(c.operand instanceof Java.AmbiguousName)) return null;
Java.AmbiguousName an = (Java.AmbiguousName) c.operand;
Java.Atom rec = this.reclassify(an);
if (!(rec instanceof Java.LocalVariableAccess)) return null;
Java.LocalVariableAccess lva = (Java.LocalVariableAccess) rec;
Java.LocalVariable lv = lva.localVariable;
if (lv.finaL) this.compileError("Must not increment or decrement \"final\" local variable", lva.getLocation());
if (
lv.type == IClass.BYTE ||
lv.type == IClass.SHORT ||
lv.type == IClass.INT ||
lv.type == IClass.CHAR
) return lv;
return null;
}
private IClass resolve(final Java.TypeDeclaration td) {
final Java.AbstractTypeDeclaration atd = (Java.AbstractTypeDeclaration) td;
if (atd.resolvedType == null) atd.resolvedType = new IClass() {
protected IClass.IMethod[] getDeclaredIMethods2() {
IClass.IMethod[] res = new IClass.IMethod[atd.getMethodDeclarations().size()];
int i = 0;
for (Iterator it = atd.getMethodDeclarations().iterator(); it.hasNext();) {
res[i++] = UnitCompiler.this.toIMethod((Java.MethodDeclarator) it.next());
}
return res;
}
private IClass[] declaredClasses = null;
protected IClass[] getDeclaredIClasses2() {
if (this.declaredClasses == null) {
Collection/*<MemberTypeDeclaration>*/ mtds = td.getMemberTypeDeclarations();
IClass[] mts = new IClass[mtds.size()];
int i = 0;
for (Iterator it = mtds.iterator(); it.hasNext();) {
mts[i++] = UnitCompiler.this.resolve((Java.AbstractTypeDeclaration) it.next());
}
this.declaredClasses = mts;
}
return this.declaredClasses;
}
protected IClass getDeclaringIClass2() {
Java.Scope s = atd;
for (; !(s instanceof Java.TypeBodyDeclaration); s = s.getEnclosingScope()) {
if (s instanceof Java.CompilationUnit) return null;
}
return UnitCompiler.this.resolve((Java.AbstractTypeDeclaration) s.getEnclosingScope());
}
protected IClass getOuterIClass2() throws CompileException {
Java.AbstractTypeDeclaration oc = (Java.AbstractTypeDeclaration) UnitCompiler.getOuterClass(atd);
if (oc == null) return null;
return UnitCompiler.this.resolve(oc);
}
protected final String getDescriptor2() {
return Descriptor.fromClassName(atd.getClassName());
}
public boolean isArray() { return false; }
protected IClass getComponentType2() {
throw new JaninoRuntimeException("SNO: Non-array type has no component type");
}
public boolean isPrimitive() { return false; }
public boolean isPrimitiveNumeric() { return false; }
protected IConstructor[] getDeclaredIConstructors2() {
if (atd instanceof Java.ClassDeclaration) {
Java.ConstructorDeclarator[] cs = ((Java.ClassDeclaration) atd).getConstructors();
IClass.IConstructor[] res = new IClass.IConstructor[cs.length];
for (int i = 0; i < cs.length; ++i) res[i] = UnitCompiler.this.toIConstructor(cs[i]);
return res;
}
return new IClass.IConstructor[0];
}
protected IField[] getDeclaredIFields2() {
if (atd instanceof Java.ClassDeclaration) {
Java.ClassDeclaration cd = (Java.ClassDeclaration) atd;
List l = new ArrayList(); // IClass.IField
// Determine variable declarators of type declaration.
for (int i = 0; i < cd.variableDeclaratorsAndInitializers.size(); ++i) {
Java.BlockStatement vdoi = (Java.BlockStatement) cd.variableDeclaratorsAndInitializers.get(i);
if (vdoi instanceof Java.FieldDeclaration) {
Java.FieldDeclaration fd = (Java.FieldDeclaration) vdoi;
IClass.IField[] flds = UnitCompiler.this.getIFields(fd);
for (int j = 0; j < flds.length; ++j) l.add(flds[j]);
}
}
return (IClass.IField[]) l.toArray(new IClass.IField[l.size()]);
} else
if (atd instanceof Java.InterfaceDeclaration) {
Java.InterfaceDeclaration id = (Java.InterfaceDeclaration) atd;
List l = new ArrayList();
// Determine static fields.
for (int i = 0; i < id.constantDeclarations.size(); ++i) {
Java.BlockStatement bs = (Java.BlockStatement) id.constantDeclarations.get(i);
if (bs instanceof Java.FieldDeclaration) {
Java.FieldDeclaration fd = (Java.FieldDeclaration) bs;
IClass.IField[] flds = UnitCompiler.this.getIFields(fd);
for (int j = 0; j < flds.length; ++j) l.add(flds[j]);
}
}
return (IClass.IField[]) l.toArray(new IClass.IField[l.size()]);
} else {
throw new JaninoRuntimeException(
"SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration"
);
}
}
public IField[] getSyntheticIFields() {
if (atd instanceof Java.ClassDeclaration) {
Collection c = ((Java.ClassDeclaration) atd).syntheticFields.values();
return (IField[]) c.toArray(new IField[c.size()]);
}
return new IField[0];
}
protected IClass getSuperclass2() throws CompileException {
if (atd instanceof Java.AnonymousClassDeclaration) {
IClass bt = UnitCompiler.this.getType(((Java.AnonymousClassDeclaration) atd).baseType);
return bt.isInterface() ? UnitCompiler.this.iClassLoader.OBJECT : bt;
}
if (atd instanceof Java.NamedClassDeclaration) {
Java.NamedClassDeclaration ncd = (Java.NamedClassDeclaration) atd;
if (ncd.optionalExtendedType == null) return UnitCompiler.this.iClassLoader.OBJECT;
IClass superclass = UnitCompiler.this.getType(ncd.optionalExtendedType);
if (superclass.isInterface()) {
UnitCompiler.this.compileError(
"\"" + superclass.toString() + "\" is an interface; classes can only extend a class",
td.getLocation()
);
}
return superclass;
}
return null;
}
public Access getAccess() { return UnitCompiler.modifiers2Access(atd.getModifiers()); }
public boolean isFinal() { return (atd.getModifiers() & Mod.FINAL) != 0; }
protected IClass[] getInterfaces2() throws CompileException {
if (atd instanceof Java.AnonymousClassDeclaration) {
IClass bt = UnitCompiler.this.getType(((Java.AnonymousClassDeclaration) atd).baseType);
return bt.isInterface() ? new IClass[] { bt } : new IClass[0];
} else
if (atd instanceof Java.NamedClassDeclaration) {
Java.NamedClassDeclaration ncd = (Java.NamedClassDeclaration) atd;
IClass[] res = new IClass[ncd.implementedTypes.length];
for (int i = 0; i < res.length; ++i) {
res[i] = UnitCompiler.this.getType(ncd.implementedTypes[i]);
if (!res[i].isInterface()) {
UnitCompiler.this.compileError((
"\""
+ res[i].toString()
+ "\" is not an interface; classes can only implement interfaces"
), td.getLocation());
}
}
return res;
} else
if (atd instanceof Java.InterfaceDeclaration) {
Java.InterfaceDeclaration id = (Java.InterfaceDeclaration) atd;
IClass[] res = new IClass[id.extendedTypes.length];
for (int i = 0; i < res.length; ++i) {
res[i] = UnitCompiler.this.getType(id.extendedTypes[i]);
if (!res[i].isInterface()) {
UnitCompiler.this.compileError((
"\""
+ res[i].toString()
+ "\" is not an interface; interfaces can only extend interfaces"
), td.getLocation());
}
}
return res;
} else {
throw new JaninoRuntimeException(
"SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration"
);
}
}
public boolean isAbstract() {
return (
(atd instanceof Java.InterfaceDeclaration)
|| (atd.getModifiers() & Mod.ABSTRACT) != 0
);
}
public boolean isInterface() { return atd instanceof Java.InterfaceDeclaration; }
};
return atd.resolvedType;
}
private void referenceThis(
Locatable l,
Java.ClassDeclaration declaringClass,
Java.TypeBodyDeclaration declaringTypeBodyDeclaration,
IClass targetIClass
) throws CompileException {
List path = UnitCompiler.getOuterClasses(declaringClass);
if (declaringTypeBodyDeclaration.isStatic()) {
this.compileError("No current instance available in static context", l.getLocation());
}
int j;
TARGET_FOUND: {
for (j = 0; j < path.size(); ++j) {
// Notice: JLS 15.9.2.BL1.B3.B1.B2 seems to be wrong: Obviously, JAVAC does not
// only allow
//
// O is the nth lexically enclosing class
//
// , but also
//
// O is assignable from the nth lexically enclosing class
//
// However, this strategy bears the risk of ambiguities, because "O" may be
// assignable from more than one enclosing class.
if (targetIClass.isAssignableFrom(this.resolve((Java.AbstractTypeDeclaration) path.get(j)))) {
break TARGET_FOUND;
}
}
this.compileError(
"\"" + declaringClass + "\" is not enclosed by \"" + targetIClass + "\"",
l.getLocation()
);
}
int i;
if (declaringTypeBodyDeclaration instanceof Java.ConstructorDeclarator) {
if (j == 0) {
this.writeOpcode(l, Opcode.ALOAD_0);
return;
}
Java.ConstructorDeclarator constructorDeclarator = (
(Java.ConstructorDeclarator) declaringTypeBodyDeclaration
);
String spn = "this$" + (path.size() - 2);
Java.LocalVariable syntheticParameter = (
(Java.LocalVariable) constructorDeclarator.syntheticParameters.get(spn)
);
if (syntheticParameter == null) {
throw new JaninoRuntimeException("SNO: Synthetic parameter \"" + spn + "\" not found");
}
this.load(l, syntheticParameter);
i = 1;
} else {
this.writeOpcode(l, Opcode.ALOAD_0);
i = 0;
}
for (; i < j; ++i) {
final String fieldName = "this$" + (path.size() - i - 2);
final Java.InnerClassDeclaration inner = (Java.InnerClassDeclaration) path.get(i);
IClass iic = this.resolve((Java.AbstractTypeDeclaration) inner);
final Java.TypeDeclaration outer = (Java.TypeDeclaration) path.get(i + 1);
final IClass oic = this.resolve((Java.AbstractTypeDeclaration) outer);
inner.defineSyntheticField(new SimpleIField(
iic,
fieldName,
oic
));
this.writeOpcode(l, Opcode.GETFIELD);
this.writeConstantFieldrefInfo(
iic.getDescriptor(),
fieldName, // classFD
oic.getDescriptor() // fieldFD
);
}
}
/**
* Return a list consisting of the given <code>inner</code> class and all its outer classes.
* @return {@link List} of {@link Java.TypeDeclaration}
*/
private static List getOuterClasses(Java.TypeDeclaration inner) {
List path = new ArrayList();
for (Java.TypeDeclaration ic = inner; ic != null; ic = UnitCompiler.getOuterClass(ic)) path.add(ic);
return path;
}
/*package*/ static Java.TypeDeclaration getOuterClass(Java.TypeDeclaration atd) {
// Package member class declaration.
if (atd instanceof Java.PackageMemberClassDeclaration) return null;
// Local class declaration.
if (atd instanceof Java.LocalClassDeclaration) {
Java.Scope s = atd.getEnclosingScope();
for (; !(s instanceof Java.FunctionDeclarator); s = s.getEnclosingScope());
if (
(s instanceof Java.MethodDeclarator)
&& (((Java.FunctionDeclarator) s).modifiers & Mod.STATIC) != 0
) return null;
for (; !(s instanceof Java.TypeDeclaration); s = s.getEnclosingScope());
Java.TypeDeclaration immediatelyEnclosingTypeDeclaration = (Java.TypeDeclaration) s;
return (
immediatelyEnclosingTypeDeclaration instanceof Java.ClassDeclaration
) ? immediatelyEnclosingTypeDeclaration : null;
}
// Member class declaration.
if (
atd instanceof Java.MemberClassDeclaration &&
(((Java.MemberClassDeclaration) atd).getModifiers() & Mod.STATIC) != 0
) return null;
// Anonymous class declaration, interface declaration
Java.Scope s = atd;
for (; !(s instanceof Java.TypeBodyDeclaration); s = s.getEnclosingScope()) {
if (s instanceof Java.ConstructorInvocation) return null;
if (s instanceof Java.CompilationUnit) return null;
}
//if (!(s instanceof Java.ClassDeclaration)) return null;
if (((Java.TypeBodyDeclaration) s).isStatic()) return null;
return (Java.AbstractTypeDeclaration) s.getEnclosingScope();
}
private IClass getIClass(Java.ThisReference tr) throws CompileException {
if (tr.iClass == null) {
// Compile error if in static function context.
Java.Scope s;
for (
s = tr.getEnclosingBlockStatement();
s instanceof Java.Statement || s instanceof Java.CatchClause;
s = s.getEnclosingScope()
);
if (s instanceof Java.FunctionDeclarator) {
Java.FunctionDeclarator function = (Java.FunctionDeclarator) s;
if ((function.modifiers & Mod.STATIC) != 0) {
this.compileError("No current instance available in static method", tr.getLocation());
}
}
// Determine declaring type.
while (!(s instanceof Java.TypeDeclaration)) {
s = s.getEnclosingScope();
}
if (!(s instanceof Java.ClassDeclaration)) {
this.compileError("Only methods of classes can have a current instance", tr.getLocation());
}
tr.iClass = this.resolve((Java.ClassDeclaration) s);
}
return tr.iClass;
}
private IClass getReturnType(Java.FunctionDeclarator fd) throws CompileException {
if (fd.returnType == null) {
fd.returnType = this.getType(fd.type);
}
return fd.returnType;
}
/*package*/ IClass.IConstructor toIConstructor(final Java.ConstructorDeclarator cd) {
if (cd.iConstructor != null) return cd.iConstructor;
cd.iConstructor = this.resolve((Java.AbstractTypeDeclaration) cd.getDeclaringType()).new IConstructor() {
// Implement IMember.
public Access getAccess() {
switch (cd.modifiers & Mod.PPP) {
case Mod.PRIVATE:
return Access.PRIVATE;
case Mod.PROTECTED:
return Access.PROTECTED;
case Mod.PACKAGE:
return Access.DEFAULT;
case Mod.PUBLIC:
return Access.PUBLIC;
default:
throw new JaninoRuntimeException("Invalid access");
}
}
// Implement IInvocable.
public String getDescriptor() throws CompileException {
if (!(cd.getDeclaringClass() instanceof Java.InnerClassDeclaration)) return super.getDescriptor();
List l = new ArrayList();
// Convert enclosing instance reference into prepended constructor parameters.
IClass outerClass = UnitCompiler.this.resolve(cd.getDeclaringClass()).getOuterIClass();
if (outerClass != null) l.add(outerClass.getDescriptor());
// Convert synthetic fields into prepended constructor parameters.
for (Iterator it = cd.getDeclaringClass().syntheticFields.values().iterator(); it.hasNext();) {
IClass.IField sf = (IClass.IField) it.next();
if (sf.getName().startsWith("val$")) l.add(sf.getType().getDescriptor());
}
Java.FunctionDeclarator.FormalParameter[] fps = cd.formalParameters;
for (int i = 0; i < fps.length; ++i) {
l.add(UnitCompiler.this.getType(fps[i].type).getDescriptor());
}
String[] apd = (String[]) l.toArray(new String[l.size()]);
return new MethodDescriptor(apd, Descriptor.VOID_).toString();
}
public IClass[] getParameterTypes() throws CompileException {
Java.FunctionDeclarator.FormalParameter[] fps = cd.formalParameters;
IClass[] res = new IClass[fps.length];
for (int i = 0; i < fps.length; ++i) {
res[i] = UnitCompiler.this.getType(fps[i].type);
}
return res;
}
public IClass[] getThrownExceptions() throws CompileException {
IClass[] res = new IClass[cd.thrownExceptions.length];
for (int i = 0; i < res.length; ++i) {
res[i] = UnitCompiler.this.getType(cd.thrownExceptions[i]);
}
return res;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(cd.getDeclaringType().getClassName());
sb.append('(');
Java.FunctionDeclarator.FormalParameter[] fps = cd.formalParameters;
for (int i = 0; i < fps.length; ++i) {
if (i != 0) sb.append(", ");
try {
sb.append(UnitCompiler.this.getType(fps[i].type).toString());
} catch (CompileException ex) {
sb.append("???");
}
}
return sb.append(')').toString();
}
};
return cd.iConstructor;
}
public IClass.IMethod toIMethod(final Java.MethodDeclarator md) {
if (md.iMethod != null) return md.iMethod;
md.iMethod = this.resolve((Java.AbstractTypeDeclaration) md.getDeclaringType()).new IMethod() {
// Implement IMember.
public Access getAccess() {
switch (md.modifiers & Mod.PPP) {
case Mod.PRIVATE:
return Access.PRIVATE;
case Mod.PROTECTED:
return Access.PROTECTED;
case Mod.PACKAGE:
return Access.DEFAULT;
case Mod.PUBLIC:
return Access.PUBLIC;
default:
throw new JaninoRuntimeException("Invalid access");
}
}
// Implement IInvocable.
public IClass[] getParameterTypes() throws CompileException {
Java.FunctionDeclarator.FormalParameter[] fps = md.formalParameters;
IClass[] res = new IClass[fps.length];
for (int i = 0; i < fps.length; ++i) {
res[i] = UnitCompiler.this.getType(fps[i].type);
}
return res;
}
public IClass[] getThrownExceptions() throws CompileException {
IClass[] res = new IClass[md.thrownExceptions.length];
for (int i = 0; i < res.length; ++i) {
res[i] = UnitCompiler.this.getType(md.thrownExceptions[i]);
}
return res;
}
// Implement IMethod.
public boolean isStatic() { return (md.modifiers & Mod.STATIC) != 0; }
public boolean isAbstract() {
return (
(md.getDeclaringType() instanceof Java.InterfaceDeclaration)
|| (md.modifiers & Mod.ABSTRACT) != 0
);
}
public IClass getReturnType() throws CompileException {
return UnitCompiler.this.getReturnType(md);
}
public String getName() { return md.name; }
};
return md.iMethod;
}
private IClass.IInvocable toIInvocable(Java.FunctionDeclarator fd) {
if (fd instanceof Java.ConstructorDeclarator) {
return this.toIConstructor((Java.ConstructorDeclarator) fd);
} else
if (fd instanceof Java.MethodDeclarator) {
return this.toIMethod((Java.MethodDeclarator) fd);
} else
{
throw new JaninoRuntimeException(
"FunctionDeclarator is neither ConstructorDeclarator nor MethodDeclarator"
);
}
}
/**
* If the given name was declared in a simple type import, load that class.
*/
private IClass importSingleType(String simpleTypeName, Location location) throws CompileException {
String[] ss = this.getSingleTypeImport(simpleTypeName);
if (ss == null) return null;
IClass iClass = this.loadFullyQualifiedClass(ss);
if (iClass == null) {
this.compileError("Imported class \"" + Java.join(ss, ".") + "\" could not be loaded", location);
return this.iClassLoader.OBJECT;
}
return iClass;
}
/**
* Check if the given name was imported through a "single type import", e.g.<pre>
* import java.util.Map</pre>
*
* @return the fully qualified name or <code>null</code>
*/
public String[] getSingleTypeImport(String name) {
return (String[]) this.singleTypeImports.get(name);
}
/**
* 6.5.2.BL1.B1.B5, 6.5.2.BL1.B1.B6 Type-import-on-demand.<br>
* 6.5.5.1.6 Type-import-on-demand declaration.
*
* @return <code>null</code> if the given <code>simpleTypeName</code> cannot be resolved through any of the
* import-on-demand directives
*/
public IClass importTypeOnDemand(String simpleTypeName, Location location) throws CompileException {
// Check cache. (A cache for unimportable types is not required, because
// the class is importable 99.9%.)
IClass importedClass = (IClass) this.onDemandImportableTypes.get(simpleTypeName);
if (importedClass != null) return importedClass;
// Cache miss...
for (Iterator i = this.typeImportsOnDemand.iterator(); i.hasNext();) {
String[] ss = (String[]) i.next();
String[] ss2 = concat(ss, simpleTypeName);
IClass iClass = this.loadFullyQualifiedClass(ss2);
if (iClass != null) {
if (importedClass != null && importedClass != iClass) {
this.compileError(
"Ambiguous class name: \"" + importedClass + "\" vs. \"" + iClass + "\"",
location
);
}
importedClass = iClass;
}
}
if (importedClass == null) return null;
// Put in cache and return.
this.onDemandImportableTypes.put(simpleTypeName, importedClass);
return importedClass;
}
private final Map onDemandImportableTypes = new HashMap(); // String simpleTypeName => IClass
private void declareClassDollarMethod(Java.ClassLiteral cl) {
// Method "class$" is not yet declared; declare it like
//
// static java.lang.Class class$(java.lang.String className) {
// try {
// return java.lang.Class.forName(className);
// } catch (java.lang.ClassNotFoundException e) {
// throw new java.lang.NoClassDefFoundError(e.getMessage());
// }
// }
//
Location loc = cl.getLocation();
Java.AbstractTypeDeclaration declaringType;
for (Java.Scope s = cl.getEnclosingBlockStatement();; s = s.getEnclosingScope()) {
if (s instanceof Java.AbstractTypeDeclaration) {
declaringType = (Java.AbstractTypeDeclaration) s;
break;
}
}
// try {
// return Class.forName(className);
Java.MethodInvocation mi = new Java.MethodInvocation(
loc, // location
new Java.SimpleType(loc, this.iClassLoader.CLASS), // optionalTarget
"forName", // methodName
new Java.Rvalue[] { // arguments
new Java.AmbiguousName(loc, new String[] { "className" })
}
);
IClass classNotFoundExceptionIClass;
try {
classNotFoundExceptionIClass = this.iClassLoader.loadIClass("Ljava/lang/ClassNotFoundException;");
} catch (ClassNotFoundException ex) {
throw new JaninoRuntimeException("Loading class \"ClassNotFoundException\": " + ex.getMessage());
}
if (classNotFoundExceptionIClass == null) {
throw new JaninoRuntimeException("SNO: Cannot load \"ClassNotFoundException\"");
}
IClass noClassDefFoundErrorIClass;
try {
noClassDefFoundErrorIClass = this.iClassLoader.loadIClass("Ljava/lang/NoClassDefFoundError;");
} catch (ClassNotFoundException ex) {
throw new JaninoRuntimeException("Loading class \"NoClassDefFoundError\": " + ex.getMessage());
}
if (noClassDefFoundErrorIClass == null) {
throw new JaninoRuntimeException("SNO: Cannot load \"NoClassFoundError\"");
}
// catch (ClassNotFoundException e) {
Java.Block b = new Java.Block(loc);
// throw new NoClassDefFoundError(e.getMessage());
b.addStatement(new Java.ThrowStatement(loc, new Java.NewClassInstance(
loc, // location
(Java.Rvalue) null, // optionalQualification
new Java.SimpleType(loc, noClassDefFoundErrorIClass), // type
new Java.Rvalue[] { // arguments
new Java.MethodInvocation(
loc, // location
new Java.AmbiguousName(loc, new String[] { "ex"}), // optionalTarget
"getMessage", // methodName
new Java.Rvalue[0] // arguments
)
}
)));
List l = new ArrayList();
l.add(new Java.CatchClause(
loc, // location
new Java.FunctionDeclarator.FormalParameter( // caughtException
loc, // location
true, // finaL
new Java.SimpleType(loc, classNotFoundExceptionIClass), // type
"ex" // name
),
b // body
));
Java.TryStatement ts = new Java.TryStatement(
loc, // location
new Java.ReturnStatement(loc, mi), // body
l, // catchClauses
null // optionalFinally
);
List statements = new ArrayList(); // BlockStatement
statements.add(ts);
// Class class$(String className)
Java.FunctionDeclarator.FormalParameter fp = new Java.FunctionDeclarator.FormalParameter(
loc, // location
false, // finaL
new Java.SimpleType(loc, this.iClassLoader.STRING), // type
"className" // name
);
Java.MethodDeclarator cdmd = new Java.MethodDeclarator(
loc, // location
null, // optionalDocComment
Mod.STATIC, // modifiers
new Java.SimpleType(loc, this.iClassLoader.CLASS), // type
"class$", // name
new Java.FunctionDeclarator.FormalParameter[] { fp }, // formalParameters
new Java.Type[0], // thrownExceptions
statements // optionalStatements
);
declaringType.addDeclaredMethod(cdmd);
declaringType.invalidateMethodCaches();
}
private IClass pushConstant(Locatable l, Object value) {
if (
value instanceof Integer ||
value instanceof Short ||
value instanceof Character ||
value instanceof Byte
) {
int i = (
value instanceof Character ?
((Character) value).charValue() :
((Number) value).intValue()
);
if (i >= -1 && i <= 5) {
this.writeOpcode(l, Opcode.ICONST_0 + i);
} else
if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
this.writeOpcode(l, Opcode.BIPUSH);
this.writeByte((byte) i);
} else {
this.writeLDC(l, this.addConstantIntegerInfo(i));
}
return IClass.INT;
}
if (value instanceof Long) {
long lv = ((Long) value).longValue();
if (lv >= 0L && lv <= 1L) {
this.writeOpcode(l, Opcode.LCONST_0 + (int) lv);
} else {
this.writeOpcode(l, Opcode.LDC2_W);
this.writeConstantLongInfo(lv);
}
return IClass.LONG;
}
if (value instanceof Float) {
float fv = ((Float) value).floatValue();
if (
Float.floatToIntBits(fv) == Float.floatToIntBits(0.0F) // POSITIVE zero!
|| fv == 1.0F
|| fv == 2.0F
) {
this.writeOpcode(l, Opcode.FCONST_0 + (int) fv);
} else
{
this.writeLDC(l, this.addConstantFloatInfo(fv));
}
return IClass.FLOAT;
}
if (value instanceof Double) {
double dv = ((Double) value).doubleValue();
if (
Double.doubleToLongBits(dv) == Double.doubleToLongBits(0.0D) // POSITIVE zero!
|| dv == 1.0D
) {
this.writeOpcode(l, Opcode.DCONST_0 + (int) dv);
} else
{
this.writeOpcode(l, Opcode.LDC2_W);
this.writeConstantDoubleInfo(dv);
}
return IClass.DOUBLE;
}
if (value instanceof String) {
String s = (String) value;
String[] ss = UnitCompiler.makeUTF8Able(s);
this.writeLDC(l, this.addConstantStringInfo(ss[0]));
for (int i = 1; i < ss.length; ++i) {
this.writeLDC(l, this.addConstantStringInfo(ss[i]));
this.writeOpcode(l, Opcode.INVOKEVIRTUAL);
this.writeConstantMethodrefInfo(
Descriptor.STRING,
"concat", // classFD
"(" + Descriptor.STRING + ")" + Descriptor.STRING // methodMD
);
}
return this.iClassLoader.STRING;
}
if (value instanceof Boolean) {
this.writeOpcode(l, ((Boolean) value).booleanValue() ? Opcode.ICONST_1 : Opcode.ICONST_0);
return IClass.BOOLEAN;
}
if (value == Java.Rvalue.CONSTANT_VALUE_NULL) {
this.writeOpcode(l, Opcode.ACONST_NULL);
return IClass.VOID;
}
throw new JaninoRuntimeException("Unknown literal type \"" + value.getClass().getName() + "\"");
}
/**
* Only strings that can be UTF8-encoded into 65535 bytes can be stored as a constant string info.
*
* @param s The string to split into suitable chunks
* @return The chunks that can be UTF8-encoded into 65535 bytes
*/
private static String[] makeUTF8Able(String s) {
if (s.length() < (65536 / 3)) return new String[] { s };
int sLength = s.length(), uTFLength = 0;
int from = 0;
List l = new ArrayList();
for (int i = 0;; i++) {
if (i == sLength) {
l.add(s.substring(from));
break;
}
if (uTFLength >= 65532) {
l.add(s.substring(from, i));
if (i + (65536 / 3) > sLength) {
l.add(s.substring(i));
break;
}
from = i;
uTFLength = 0;
}
int c = s.charAt(i);
if (c >= 0x0001 && c <= 0x007F) {
++uTFLength;
} else
if (c > 0x07FF) {
uTFLength += 3;
} else
{
uTFLength += 2;
}
}
return (String[]) l.toArray(new String[l.size()]);
}
private void writeLDC(Locatable l, short index) {
if (0 <= index && index <= 255) {
this.writeOpcode(l, Opcode.LDC);
this.writeByte((byte) index);
} else {
this.writeOpcode(l, Opcode.LDC_W);
this.writeShort(index);
}
}
/**
* Implements "assignment conversion" (JLS2 5.2).
*/
private void assignmentConversion(
Locatable l,
IClass sourceType,
IClass targetType,
Object optionalConstantValue
) throws CompileException {
if (UnitCompiler.DEBUG) {
System.out.println(
"assignmentConversion("
+ sourceType
+ ", "
+ targetType
+ ", "
+ optionalConstantValue
+ ")"
);
}
// JLS2 5.1.1 Identity conversion.
if (this.tryIdentityConversion(sourceType, targetType)) return;
// JLS2 5.1.2 Widening primitive conversion.
if (this.tryWideningPrimitiveConversion(l, sourceType, targetType)) return;
// JLS2 5.1.4 Widening reference conversion.
if (this.isWideningReferenceConvertible(sourceType, targetType)) return;
// A boxing conversion (JLS3 5.1.7) optionally followed by a widening reference conversion.
{
IClass boxedType = this.isBoxingConvertible(sourceType);
if (boxedType != null) {
if (this.tryIdentityConversion(boxedType, targetType)) {
this.boxingConversion(l, sourceType, boxedType);
return;
} else
if (this.isWideningReferenceConvertible(boxedType, targetType)) {
this.boxingConversion(l, sourceType, boxedType);
return;
}
}
}
// An unboxing conversion (JLS3 5.1.8) optionally followed by a widening primitive conversion.
{
IClass unboxedType = this.isUnboxingConvertible(sourceType);
if (unboxedType != null) {
if (this.tryIdentityConversion(unboxedType, targetType)) {
this.unboxingConversion(l, sourceType, unboxedType);
return;
} else
if (this.isWideningPrimitiveConvertible(unboxedType, targetType)) {
this.unboxingConversion(l, sourceType, unboxedType);
this.tryWideningPrimitiveConversion(l, unboxedType, targetType);
return;
}
}
}
// 5.2 Special narrowing primitive conversion.
if (optionalConstantValue != null) {
if (this.tryConstantAssignmentConversion(
l,
optionalConstantValue, // constantValue
targetType // targetType
)) return;
}
this.compileError(
"Assignment conversion not possible from type \"" + sourceType + "\" to type \"" + targetType + "\"",
l.getLocation()
);
}
/**
* Implements "assignment conversion" (JLS2 5.2) on a constant value.
*/
private Object assignmentConversion(
Locatable l,
Object value,
IClass targetType
) throws CompileException {
Object result = null;
if (targetType == IClass.BOOLEAN) {
if (value instanceof Boolean) result = value;
} else
if (targetType == this.iClassLoader.STRING) {
if (value instanceof String) result = value;
} else
if (targetType == IClass.BYTE) {
if (value instanceof Byte) {
result = value;
} else
if (value instanceof Short || value instanceof Integer) {
int x = ((Number) value).intValue();
if (x >= Byte.MIN_VALUE && x <= Byte.MAX_VALUE) result = new Byte((byte) x);
} else
if (value instanceof Character) {
int x = ((Character) value).charValue();
if (x >= Byte.MIN_VALUE && x <= Byte.MAX_VALUE) result = new Byte((byte) x);
}
} else
if (targetType == IClass.SHORT) {
if (value instanceof Byte) {
result = new Short(((Number) value).shortValue());
} else
if (value instanceof Short) {
result = value;
} else
if (value instanceof Character) {
int x = ((Character) value).charValue();
if (x >= Short.MIN_VALUE && x <= Short.MAX_VALUE) result = new Short((short) x);
} else
if (value instanceof Integer) {
int x = ((Integer) value).intValue();
if (x >= Short.MIN_VALUE && x <= Short.MAX_VALUE) result = new Short((short) x);
}
} else
if (targetType == IClass.CHAR) {
if (value instanceof Short) {
result = value;
} else
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
int x = ((Number) value).intValue();
if (x >= Character.MIN_VALUE && x <= Character.MAX_VALUE) result = new Character((char) x);
}
} else
if (targetType == IClass.INT) {
if (value instanceof Integer) {
result = value;
} else
if (value instanceof Byte || value instanceof Short) {
result = new Integer(((Number) value).intValue());
} else
if (value instanceof Character) {
result = new Integer(((Character) value).charValue());
}
} else
if (targetType == IClass.LONG) {
if (value instanceof Long) {
result = value;
} else
if (value instanceof Byte || value instanceof Short || value instanceof Integer) {
result = new Long(((Number) value).longValue());
} else
if (value instanceof Character) {
result = new Long(((Character) value).charValue());
}
} else
if (targetType == IClass.FLOAT) {
if (value instanceof Float) {
result = value;
} else
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) {
result = new Float(((Number) value).floatValue());
} else
if (value instanceof Character) {
result = new Float(((Character) value).charValue());
}
} else
if (targetType == IClass.DOUBLE) {
if (value instanceof Double) {
result = value;
} else
if (
value instanceof Byte
|| value instanceof Short
|| value instanceof Integer
|| value instanceof Long
|| value instanceof Float
) {
result = new Double(((Number) value).doubleValue());
} else
if (value instanceof Character) {
result = new Double(((Character) value).charValue());
}
} else
if (value == Java.Rvalue.CONSTANT_VALUE_NULL && !targetType.isPrimitive()) {
result = value;
}
if (result == null) {
this.compileError((
"Cannot convert constant of type \""
+ value.getClass().getName()
+ "\" to type \""
+ targetType.toString()
+ "\""
), l.getLocation());
}
return result;
}
/**
* Implements "unary numeric promotion" (JLS3 5.6.1)
*
* @return The promoted type.
*/
private IClass unaryNumericPromotion(Locatable l, IClass type) throws CompileException {
type = this.convertToPrimitiveNumericType(l, type);
IClass promotedType = this.unaryNumericPromotionType(l, type);
this.numericPromotion(l, type, promotedType);
return promotedType;
}
private void reverseUnaryNumericPromotion(
Locatable l,
IClass sourceType,
IClass targetType
) throws CompileException {
IClass unboxedType = this.isUnboxingConvertible(targetType);
IClass pt = unboxedType != null ? unboxedType : targetType;
if (
!this.tryIdentityConversion(sourceType, pt) &&
!this.tryNarrowingPrimitiveConversion(
l, // locatable
sourceType, // sourceType
pt // targetType
)
) throw new JaninoRuntimeException("SNO: reverse unary numeric promotion failed");
if (unboxedType != null) this.boxingConversion(l, unboxedType, targetType);
}
/**
* If the given type is a primitive type, return that type.
* If the given type is a primitive wrapper class, unbox the operand on top of the operand
* stack and return the primitive type.
* Otherwise, issue a compile error.
*/
private IClass convertToPrimitiveNumericType(Locatable l, IClass type) throws CompileException {
if (type.isPrimitiveNumeric()) return type;
IClass unboxedType = this.isUnboxingConvertible(type);
if (unboxedType != null) {
this.unboxingConversion(l, type, unboxedType);
return unboxedType;
}
this.compileError(
"Object of type \"" + type.toString() + "\" cannot be converted to a numeric type",
l.getLocation()
);
return type;
}
private void numericPromotion(Locatable l, IClass sourceType, IClass targetType) {
if (
!this.tryIdentityConversion(sourceType, targetType) &&
!this.tryWideningPrimitiveConversion(
l, // locatable
sourceType, // sourceType
targetType // targetType
)
) throw new JaninoRuntimeException("SNO: Conversion failed");
}
private IClass unaryNumericPromotionType(Locatable l, IClass type) throws CompileException {
if (!type.isPrimitiveNumeric()) {
this.compileError(
"Unary numeric promotion not possible on non-numeric-primitive type \"" + type + "\"",
l.getLocation()
);
}
return (
type == IClass.DOUBLE ? IClass.DOUBLE :
type == IClass.FLOAT ? IClass.FLOAT :
type == IClass.LONG ? IClass.LONG :
IClass.INT
);
}
/**
* Implements "binary numeric promotion" (5.6.2)
*
* @return The promoted type.
*/
private IClass binaryNumericPromotion(
Locatable locatable,
IClass type1,
CodeContext.Inserter convertInserter1,
IClass type2
) throws CompileException {
return this.binaryNumericPromotion(
locatable,
type1,
convertInserter1,
type2,
this.codeContext.currentInserter()
);
}
/**
* Implements "binary numeric promotion" (5.6.2)
*
* @return The promoted type.
*/
private IClass binaryNumericPromotion(
Locatable l,
IClass type1,
CodeContext.Inserter convertInserter1,
IClass type2,
CodeContext.Inserter convertInserter2
) throws CompileException {
IClass promotedType;
{
IClass c1 = this.isUnboxingConvertible(type1);
IClass c2 = this.isUnboxingConvertible(type2);
promotedType = this.binaryNumericPromotionType(
l,
c1 != null ? c1 : type1,
c2 != null ? c2 : type2
);
}
if (convertInserter1 != null) {
this.codeContext.pushInserter(convertInserter1);
try {
this.numericPromotion(l, this.convertToPrimitiveNumericType(l, type1), promotedType);
} finally {
this.codeContext.popInserter();
}
}
if (convertInserter2 != null) {
this.codeContext.pushInserter(convertInserter2);
try {
this.numericPromotion(l, this.convertToPrimitiveNumericType(l, type2), promotedType);
} finally {
this.codeContext.popInserter();
}
}
return promotedType;
}
private IClass binaryNumericPromotionType(
Java.Locatable locatable,
IClass type1,
IClass type2
) throws CompileException {
if (
!type1.isPrimitiveNumeric() ||
!type2.isPrimitiveNumeric()
) this.compileError(
"Binary numeric promotion not possible on types \"" + type1 + "\" and \"" + type2 + "\"",
locatable.getLocation()
);
return (
type1 == IClass.DOUBLE || type2 == IClass.DOUBLE ? IClass.DOUBLE :
type1 == IClass.FLOAT || type2 == IClass.FLOAT ? IClass.FLOAT :
type1 == IClass.LONG || type2 == IClass.LONG ? IClass.LONG :
IClass.INT
);
}
/**
* Checks whether "identity conversion" (5.1.1) is possible.
*
* @return Whether the conversion is possible
*/
private boolean isIdentityConvertible(
IClass sourceType,
IClass targetType
) {
return sourceType == targetType;
}
/**
* Implements "identity conversion" (5.1.1).
*
* @return Whether the conversion was possible
*/
private boolean tryIdentityConversion(
IClass sourceType,
IClass targetType
) {
return sourceType == targetType;
}
private boolean isWideningPrimitiveConvertible(
IClass sourceType,
IClass targetType
) {
return UnitCompiler.PRIMITIVE_WIDENING_CONVERSIONS.get(
sourceType.getDescriptor() + targetType.getDescriptor()
) != null;
}
/**
* Implements "widening primitive conversion" (5.1.2).
*
* @return Whether the conversion succeeded
*/
private boolean tryWideningPrimitiveConversion(
Locatable l,
IClass sourceType,
IClass targetType
) {
byte[] opcodes = (byte[]) UnitCompiler.PRIMITIVE_WIDENING_CONVERSIONS.get(
sourceType.getDescriptor() + targetType.getDescriptor()
);
if (opcodes != null) {
this.writeOpcodes(l, opcodes);
return true;
}
return false;
}
private static final HashMap PRIMITIVE_WIDENING_CONVERSIONS = new HashMap();
static { UnitCompiler.fillConversionMap(new Object[] {
new byte[0],
Descriptor.BYTE_ + Descriptor.SHORT_,
Descriptor.BYTE_ + Descriptor.INT_,
Descriptor.SHORT_ + Descriptor.INT_,
Descriptor.CHAR_ + Descriptor.INT_,
new byte[] { Opcode.I2L },
Descriptor.BYTE_ + Descriptor.LONG_,
Descriptor.SHORT_ + Descriptor.LONG_,
Descriptor.CHAR_ + Descriptor.LONG_,
Descriptor.INT_ + Descriptor.LONG_,
new byte[] { Opcode.I2F },
Descriptor.BYTE_ + Descriptor.FLOAT_,
Descriptor.SHORT_ + Descriptor.FLOAT_,
Descriptor.CHAR_ + Descriptor.FLOAT_,
Descriptor.INT_ + Descriptor.FLOAT_,
new byte[] { Opcode.L2F },
Descriptor.LONG_ + Descriptor.FLOAT_,
new byte[] { Opcode.I2D },
Descriptor.BYTE_ + Descriptor.DOUBLE_,
Descriptor.SHORT_ + Descriptor.DOUBLE_,
Descriptor.CHAR_ + Descriptor.DOUBLE_,
Descriptor.INT_ + Descriptor.DOUBLE_,
new byte[] { Opcode.L2D },
Descriptor.LONG_ + Descriptor.DOUBLE_,
new byte[] { Opcode.F2D },
Descriptor.FLOAT_ + Descriptor.DOUBLE_,
}, UnitCompiler.PRIMITIVE_WIDENING_CONVERSIONS); }
private static void fillConversionMap(Object[] array, HashMap map) {
byte[] opcodes = null;
for (int i = 0; i < array.length; ++i) {
Object o = array[i];
if (o instanceof byte[]) {
opcodes = (byte[]) o;
} else {
map.put(o, opcodes);
}
}
}
/**
* Checks if "widening reference conversion" (5.1.4) is possible.
*
* @return Whether the conversion is possible
*/
private boolean isWideningReferenceConvertible(
IClass sourceType,
IClass targetType
) throws CompileException {
if (
targetType.isPrimitive() ||
sourceType == targetType
) return false;
return targetType.isAssignableFrom(sourceType);
}
/**
* Performs "widening reference conversion" (5.1.4) if possible.
*
* @return Whether the conversion was possible
*/
private boolean tryWideningReferenceConversion(
IClass sourceType,
IClass targetType
) throws CompileException {
if (
targetType.isPrimitive() ||
sourceType == targetType
) return false;
return targetType.isAssignableFrom(sourceType);
}
/**
* Check whether "narrowing primitive conversion" (JLS 5.1.3) is possible.
*/
private boolean isNarrowingPrimitiveConvertible(
IClass sourceType,
IClass targetType
) {
return UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS.containsKey(
sourceType.getDescriptor() + targetType.getDescriptor()
);
}
/**
* Implements "narrowing primitive conversion" (JLS 5.1.3).
*
* @return Whether the conversion succeeded
*/
private boolean tryNarrowingPrimitiveConversion(
Locatable l,
IClass sourceType,
IClass targetType
) {
byte[] opcodes = (byte[]) UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS.get(
sourceType.getDescriptor() + targetType.getDescriptor()
);
if (opcodes != null) {
this.writeOpcodes(l, opcodes);
return true;
}
return false;
}
private static final HashMap PRIMITIVE_NARROWING_CONVERSIONS = new HashMap();
static { UnitCompiler.fillConversionMap(new Object[] {
new byte[0],
Descriptor.BYTE_ + Descriptor.CHAR_,
Descriptor.SHORT_ + Descriptor.CHAR_,
Descriptor.CHAR_ + Descriptor.SHORT_,
new byte[] { Opcode.I2B },
Descriptor.SHORT_ + Descriptor.BYTE_,
Descriptor.CHAR_ + Descriptor.BYTE_,
Descriptor.INT_ + Descriptor.BYTE_,
new byte[] { Opcode.I2S },
Descriptor.INT_ + Descriptor.SHORT_,
Descriptor.INT_ + Descriptor.CHAR_,
new byte[] { Opcode.L2I, Opcode.I2B },
Descriptor.LONG_ + Descriptor.BYTE_,
new byte[] { Opcode.L2I, Opcode.I2S },
Descriptor.LONG_ + Descriptor.SHORT_,
Descriptor.LONG_ + Descriptor.CHAR_,
new byte[] { Opcode.L2I },
Descriptor.LONG_ + Descriptor.INT_,
new byte[] { Opcode.F2I, Opcode.I2B },
Descriptor.FLOAT_ + Descriptor.BYTE_,
new byte[] { Opcode.F2I, Opcode.I2S },
Descriptor.FLOAT_ + Descriptor.SHORT_,
Descriptor.FLOAT_ + Descriptor.CHAR_,
new byte[] { Opcode.F2I },
Descriptor.FLOAT_ + Descriptor.INT_,
new byte[] { Opcode.F2L },
Descriptor.FLOAT_ + Descriptor.LONG_,
new byte[] { Opcode.D2I, Opcode.I2B },
Descriptor.DOUBLE_ + Descriptor.BYTE_,
new byte[] { Opcode.D2I, Opcode.I2S },
Descriptor.DOUBLE_ + Descriptor.SHORT_,
Descriptor.DOUBLE_ + Descriptor.CHAR_,
new byte[] { Opcode.D2I },
Descriptor.DOUBLE_ + Descriptor.INT_,
new byte[] { Opcode.D2L },
Descriptor.DOUBLE_ + Descriptor.LONG_,
new byte[] { Opcode.D2F },
Descriptor.DOUBLE_ + Descriptor.FLOAT_,
}, UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS); }
/**
* Check if "constant assignment conversion" (JLS 5.2, paragraph 1) is possible.
* @param constantValue The constant value that is to be converted
* @param targetType The type to convert to
*/
private boolean tryConstantAssignmentConversion(
Locatable l,
Object constantValue,
IClass targetType
) throws CompileException {
if (UnitCompiler.DEBUG) {
System.out.println("isConstantPrimitiveAssignmentConvertible(" + constantValue + ", " + targetType + ")");
}
int cv;
if (constantValue instanceof Byte) {
cv = ((Byte) constantValue).byteValue();
} else
if (constantValue instanceof Short) {
cv = ((Short) constantValue).shortValue();
} else
if (constantValue instanceof Integer) {
cv = ((Integer) constantValue).intValue();
} else
if (constantValue instanceof Character) {
cv = ((Character) constantValue).charValue();
} else
{
return false;
}
if (targetType == IClass.BYTE) return cv >= Byte.MIN_VALUE && cv <= Byte.MAX_VALUE;
if (targetType == IClass.SHORT) return cv >= Short.MIN_VALUE && cv <= Short.MAX_VALUE;
if (targetType == IClass.CHAR) return cv >= Character.MIN_VALUE && cv <= Character.MAX_VALUE;
IClassLoader icl = this.iClassLoader;
if (targetType == icl.BYTE && cv >= Byte.MIN_VALUE && cv <= Byte.MAX_VALUE) {
this.boxingConversion(l, IClass.BYTE, targetType);
return true;
}
if (targetType == icl.SHORT && cv >= Short.MIN_VALUE && cv <= Short.MAX_VALUE) {
this.boxingConversion(l, IClass.SHORT, targetType);
return true;
}
if (targetType == icl.CHARACTER && cv >= Character.MIN_VALUE && cv <= Character.MAX_VALUE) {
this.boxingConversion(l, IClass.CHAR, targetType);
return true;
}
return false;
}
/**
* Check whether "narrowing reference conversion" (JLS 5.1.5) is possible.
*/
private boolean isNarrowingReferenceConvertible(
IClass sourceType,
IClass targetType
) throws CompileException {
if (sourceType.isPrimitive()) return false;
if (sourceType == targetType) return false;
// 5.1.5.1
if (sourceType.isAssignableFrom(targetType)) return true;
// 5.1.5.2
if (
targetType.isInterface() &&
!sourceType.isFinal() &&
!targetType.isAssignableFrom(sourceType)
) return true;
// 5.1.5.3
if (
sourceType == this.iClassLoader.OBJECT &&
targetType.isArray()
) return true;
// 5.1.5.4
if (
sourceType == this.iClassLoader.OBJECT &&
targetType.isInterface()
) return true;
// 5.1.5.5
if (
sourceType.isInterface() &&
!targetType.isFinal()
) return true;
// 5.1.5.6
if (
sourceType.isInterface() &&
targetType.isFinal() &&
sourceType.isAssignableFrom(targetType)
) return true;
// 5.1.5.7
// TODO: Check for redefinition of methods with same signature but different return type.
if (
sourceType.isInterface() &&
targetType.isInterface() &&
!targetType.isAssignableFrom(sourceType)
) return true;
// 5.1.5.8
if (sourceType.isArray() && targetType.isArray()) {
IClass st = sourceType.getComponentType();
IClass tt = targetType.getComponentType();
if (
this.isNarrowingPrimitiveConvertible(st, tt) ||
this.isNarrowingReferenceConvertible(st, tt)
) return true;
}
return false;
}
/**
* Implements "narrowing reference conversion" (5.1.5).
*
* @return Whether the conversion succeeded
*/
private boolean tryNarrowingReferenceConversion(
Locatable l,
IClass sourceType,
IClass targetType
) throws CompileException {
if (!this.isNarrowingReferenceConvertible(sourceType, targetType)) return false;
this.writeOpcode(l, Opcode.CHECKCAST);
this.writeConstantClassInfo(targetType.getDescriptor());
return true;
}
/*
* @return the boxed type or <code>null</code>
*/
private IClass isBoxingConvertible(IClass sourceType) {
IClassLoader icl = this.iClassLoader;
if (sourceType == IClass.BOOLEAN) return icl.BOOLEAN;
if (sourceType == IClass.BYTE) return icl.BYTE;
if (sourceType == IClass.CHAR) return icl.CHARACTER;
if (sourceType == IClass.SHORT) return icl.SHORT;
if (sourceType == IClass.INT) return icl.INTEGER;
if (sourceType == IClass.LONG) return icl.LONG;
if (sourceType == IClass.FLOAT) return icl.FLOAT;
if (sourceType == IClass.DOUBLE) return icl.DOUBLE;
return null;
}
private boolean tryBoxingConversion(
Locatable l,
IClass sourceType,
IClass targetType
) throws CompileException {
if (this.isBoxingConvertible(sourceType) == targetType) {
this.boxingConversion(l, sourceType, targetType);
return true;
}
return false;
}
/**
* @param sourceType a primitive type (except VOID)
* @param targetType the corresponding wrapper type
*/
private void boxingConversion(Locatable l, IClass sourceType, IClass targetType) throws CompileException {
// In some pre-1.5 JDKs, only some wrapper classes have the static "Target.valueOf(source)" method.
if (targetType.hasIMethod("valueOf", new IClass[] { sourceType })) {
this.writeOpcode(l, Opcode.INVOKESTATIC);
this.writeConstantMethodrefInfo(
targetType.getDescriptor(),
"valueOf", // classFD
'(' + sourceType.getDescriptor() + ')' + targetType.getDescriptor() // methodFD
);
return;
}
// new Target(source)
this.writeOpcode(l, Opcode.NEW);
this.writeConstantClassInfo(targetType.getDescriptor());
if (Descriptor.hasSize2(sourceType.getDescriptor())) {
this.writeOpcode(l, Opcode.DUP_X2);
this.writeOpcode(l, Opcode.DUP_X2);
this.writeOpcode(l, Opcode.POP);
} else
{
this.writeOpcode(l, Opcode.DUP_X1);
this.writeOpcode(l, Opcode.SWAP);
}
this.writeOpcode(l, Opcode.INVOKESPECIAL);
this.writeConstantMethodrefInfo(
targetType.getDescriptor(),
"<init>", // classFD
'(' + sourceType.getDescriptor() + ')' + Descriptor.VOID_ // methodMD
);
}
/*
* @return the unboxed type or <code>null</code>
*/
private IClass isUnboxingConvertible(IClass sourceType) {
IClassLoader icl = this.iClassLoader;
if (sourceType == icl.BOOLEAN) return IClass.BOOLEAN;
if (sourceType == icl.BYTE) return IClass.BYTE;
if (sourceType == icl.CHARACTER) return IClass.CHAR;
if (sourceType == icl.SHORT) return IClass.SHORT;
if (sourceType == icl.INTEGER) return IClass.INT;
if (sourceType == icl.LONG) return IClass.LONG;
if (sourceType == icl.FLOAT) return IClass.FLOAT;
if (sourceType == icl.DOUBLE) return IClass.DOUBLE;
return null;
}
private boolean tryUnboxingConversion(
Locatable l,
IClass sourceType,
IClass targetType
) {
if (this.isUnboxingConvertible(sourceType) == targetType) {
this.unboxingConversion(l, sourceType, targetType);
return true;
}
return false;
}
/**
* @param targetType a primitive type (except VOID)
* @param sourceType the corresponding wrapper type
*/
private void unboxingConversion(Locatable l, IClass sourceType, IClass targetType) {
// "source.targetValue()"
this.writeOpcode(l, Opcode.INVOKEVIRTUAL);
this.writeConstantMethodrefInfo(
sourceType.getDescriptor(),
targetType.toString() + "Value", // classFD
"()" + targetType.getDescriptor() // methodFD
);
}
/**
* Attempt to load an {@link IClass} by fully-qualified name
* @param identifiers
* @return <code>null</code> if a class with the given name could not be loaded
*/
private IClass loadFullyQualifiedClass(String[] identifiers) throws CompileException {
// Compose the descriptor (like "La/b/c;") and remember the positions of the slashes
// (2 and 4).
int[] slashes = new int[identifiers.length - 1];
StringBuffer sb = new StringBuffer("L");
for (int i = 0;; ++i) {
sb.append(identifiers[i]);
if (i == identifiers.length - 1) break;
slashes[i] = sb.length();
sb.append('/');
}
sb.append(';');
// Attempt to load the IClass and replace dots with dollar signs, i.e.:
// La/b/c; La/b$c; La$b$c;
for (int j = slashes.length - 1;; --j) {
IClass result;
try {
result = this.iClassLoader.loadIClass(sb.toString());
} catch (ClassNotFoundException ex) {
if (ex.getException() instanceof CompileException) throw (CompileException) ex.getException();
throw new CompileException(sb.toString(), null, ex);
}
if (result != null) return result;
if (j < 0) break;
sb.setCharAt(slashes[j], '$');
}
return null;
}
// Load the value of a local variable onto the stack and return its type.
private IClass load(
Locatable l,
Java.LocalVariable localVariable
) {
this.load(
l,
localVariable.type,
localVariable.getSlotIndex()
);
return localVariable.type;
}
private void load(
Locatable l,
IClass type,
int index
) {
if (index <= 3) {
this.writeOpcode(l, Opcode.ILOAD_0 + 4 * this.ilfda(type) + index);
} else
if (index <= 255) {
this.writeOpcode(l, Opcode.ILOAD + this.ilfda(type));
this.writeByte(index);
} else
{
this.writeOpcode(l, Opcode.WIDE);
this.writeOpcode(l, Opcode.ILOAD + this.ilfda(type));
this.writeShort(index);
}
}
/**
* Assign stack top value to the given local variable. (Assignment conversion takes effect.)
* If <code>optionalConstantValue</code> is not <code>null</code>, then the top stack value
* is a constant value with that type and value, and a narrowing primitive conversion as
* described in JLS 5.2 is applied.
*/
private void store(
Locatable l,
IClass valueType,
Java.LocalVariable localVariable
) {
this.store(
l, // l
localVariable.type, // lvType
localVariable.getSlotIndex() // lvIndex
);
}
private void store(
Locatable l,
IClass lvType,
short lvIndex
) {
if (lvIndex <= 3) {
this.writeOpcode(l, Opcode.ISTORE_0 + 4 * this.ilfda(lvType) + lvIndex);
} else
if (lvIndex <= 255) {
this.writeOpcode(l, Opcode.ISTORE + this.ilfda(lvType));
this.writeByte(lvIndex);
} else
{
this.writeOpcode(l, Opcode.WIDE);
this.writeOpcode(l, Opcode.ISTORE + this.ilfda(lvType));
this.writeShort(lvIndex);
}
}
private void dup(Locatable l, int n) {
switch (n) {
case 0:
;
break;
case 1:
this.writeOpcode(l, Opcode.DUP);
break;
case 2:
this.writeOpcode(l, Opcode.DUP2);
break;
default:
throw new JaninoRuntimeException("dup(" + n + ")");
}
}
private void dupx(
Locatable l,
IClass type,
int x
) {
if (x < 0 || x > 2) throw new JaninoRuntimeException("SNO: x has value " + x);
int dup = Opcode.DUP + x;
int dup2 = Opcode.DUP2 + x;
this.writeOpcode(l, (
type == IClass.LONG || type == IClass.DOUBLE ?
dup2 :
dup
));
}
private void pop(Locatable l, IClass type) {
if (type == IClass.VOID) return;
this.writeOpcode(l, (
type == IClass.LONG || type == IClass.DOUBLE ?
Opcode.POP2 :
Opcode.POP
));
}
static int ilfd(IClass t) {
if (
t == IClass.BYTE ||
t == IClass.CHAR ||
t == IClass.INT ||
t == IClass.SHORT ||
t == IClass.BOOLEAN
) return 0;
if (t == IClass.LONG) return 1;
if (t == IClass.FLOAT) return 2;
if (t == IClass.DOUBLE) return 3;
throw new JaninoRuntimeException("Unexpected type \"" + t + "\"");
}
static int ilfd(
IClass t,
int opcodeInt,
int opcodeLong,
int opcodeFloat,
int opcodeDouble
) {
if (
t == IClass.BYTE ||
t == IClass.CHAR ||
t == IClass.INT ||
t == IClass.SHORT ||
t == IClass.BOOLEAN
) return opcodeInt;
if (t == IClass.LONG) return opcodeLong;
if (t == IClass.FLOAT) return opcodeFloat;
if (t == IClass.DOUBLE) return opcodeDouble;
throw new JaninoRuntimeException("Unexpected type \"" + t + "\"");
}
private int ilfda(IClass t) {
return !t.isPrimitive() ? 4 : UnitCompiler.ilfd(t);
}
static int ilfdabcs(IClass t) {
if (t == IClass.INT) return 0;
if (t == IClass.LONG) return 1;
if (t == IClass.FLOAT) return 2;
if (t == IClass.DOUBLE) return 3;
if (!t.isPrimitive()) return 4;
if (t == IClass.BOOLEAN) return 5;
if (t == IClass.BYTE) return 5;
if (t == IClass.CHAR) return 6;
if (t == IClass.SHORT) return 7;
throw new JaninoRuntimeException("Unexpected type \"" + t + "\"");
}
/**
* Find a named field in the given {@link IClass}.
* Honor superclasses and interfaces. See JLS 8.3.
* @return <code>null</code> if no field is found
*/
private IClass.IField findIField(
IClass iClass,
String name,
Location location
) throws CompileException {
// Search for a field with the given name in the current class.
IClass.IField f = iClass.getDeclaredIField(name);
if (f != null) return f;
// Examine superclass.
{
IClass superclass = iClass.getSuperclass();
if (superclass != null) f = this.findIField(superclass, name, location);
}
// Examine interfaces.
IClass[] ifs = iClass.getInterfaces();
for (int i = 0; i < ifs.length; ++i) {
IClass.IField f2 = this.findIField(ifs[i], name, location);
if (f2 != null) {
if (f != null) {
throw new CompileException((
"Access to field \""
+ name
+ "\" is ambiguous - both \""
+ f.getDeclaringIClass()
+ "\" and \""
+ f2.getDeclaringIClass()
+ "\" declare it"
), location);
}
f = f2;
}
}
return f;
}
/**
* Find a named type in the given {@link IClass}.
* Honor superclasses, interfaces and enclosing type declarations.
* @return <code>null</code> if no type with the given name is found
*/
private IClass findMemberType(
IClass iClass,
String name,
Location location
) throws CompileException {
IClass[] types = iClass.findMemberType(name);
if (types.length == 0) return null;
if (types.length == 1) return types[0];
StringBuffer sb = new StringBuffer("Type \"" + name + "\" is ambiguous: " + types[0].toString());
for (int i = 1; i < types.length; ++i) sb.append(" vs. ").append(types[i].toString());
this.compileError(sb.toString(), location);
return types[0];
}
/**
* Find one class or interface declared in this compilation unit by name.
*
* @param className Fully qualified class name, e.g. "pkg1.pkg2.Outer$Inner".
* @return <code>null</code> if a class with that name is not declared in this compilation unit
*/
public IClass findClass(String className) {
// Examine package name.
String packageName = (
this.compilationUnit.optionalPackageDeclaration == null ? null :
this.compilationUnit.optionalPackageDeclaration.packageName
);
if (packageName != null) {
if (!className.startsWith(packageName + '.')) return null;
className = className.substring(packageName.length() + 1);
}
StringTokenizer st = new StringTokenizer(className, "$");
Java.TypeDeclaration td = this.compilationUnit.getPackageMemberTypeDeclaration(st.nextToken());
if (td == null) return null;
while (st.hasMoreTokens()) {
td = td.getMemberTypeDeclaration(st.nextToken());
if (td == null) return null;
}
return this.resolve((Java.AbstractTypeDeclaration) td);
}
/**
* Equivalent to {@link #compileError(String, Location)} with a
* <code>null</code> location argument.
*/
private void compileError(String message) throws CompileException {
this.compileError(message, null);
}
/**
* Issue a compile error with the given message. This is done through the
* {@link ErrorHandler} that was installed through
* {@link #setCompileErrorHandler(ErrorHandler)}. Such a handler typically throws
* a {@link CompileException}, but it may as well decide to return normally. Consequently,
* the calling code must be prepared that {@link #compileError(String, Location)}
* returns normally, and must attempt to continue compiling.
*
* @param message The message to report
* @param optionalLocation The location to report
*/
private void compileError(String message, Location optionalLocation) throws CompileException {
++this.compileErrorCount;
if (this.optionalCompileErrorHandler != null) {
this.optionalCompileErrorHandler.handleError(message, optionalLocation);
} else {
throw new CompileException(message, optionalLocation);
}
}
/**
* Issues a warning with the given message an location an returns. This is done through a {@link WarningHandler}
* that was installed through {@link #setWarningHandler(WarningHandler)}.
* <p>
* The <code>handle</code> argument qualifies the warning and is typically used by the {@link WarningHandler} to
* suppress individual warnings.
*/
private void warning(String handle, String message, Location optionalLocation) {
if (this.optionalWarningHandler != null) {
this.optionalWarningHandler.handleWarning(handle, message, optionalLocation);
}
}
/**
* Interface type for {@link UnitCompiler#setCompileErrorHandler}.
*/
public interface ErrorHandler {
void handleError(String message, Location optionalLocation) throws CompileException;
}
/**
* By default, {@link CompileException}s are thrown on compile errors, but an application my install its own
* (thread-local) {@link ErrorHandler}.
* <p>
* Be aware that a single problem during compilation often causes a bunch of compile errors, so a good {@link
* ErrorHandler} counts errors and throws a {@link CompileException} when a limit is reached.
* <p>
* If the given {@link ErrorHandler} does not throw {@link CompileException}s, then {@link
* #compileUnit(boolean, boolean, boolean)} will throw one when the compilation of the unit is finished, and errors had
* occurred. In other words: The {@link ErrorHandler} may throw a {@link CompileException} or not, but {@link
* #compileUnit(boolean, boolean, boolean)} will definitely throw a {@link CompileException} if one or more compile errors have
* occurred.
*
* @param optionalCompileErrorHandler <code>null</code> to restore the default behavior (throwing a {@link
* CompileException}
*/
public void setCompileErrorHandler(ErrorHandler optionalCompileErrorHandler) {
this.optionalCompileErrorHandler = optionalCompileErrorHandler;
}
/**
* By default, warnings are discarded, but an application my install a custom
* {@link WarningHandler}.
*
* @param optionalWarningHandler <code>null</code> to indicate that no warnings be issued
*/
public void setWarningHandler(WarningHandler optionalWarningHandler) {
this.optionalWarningHandler = optionalWarningHandler;
}
private CodeContext getCodeContext() {
CodeContext res = this.codeContext;
if (res == null) throw new JaninoRuntimeException("S.N.O.: Null CodeContext");
return res;
}
private CodeContext replaceCodeContext(CodeContext newCodeContext) {
CodeContext oldCodeContext = this.codeContext;
this.codeContext = newCodeContext;
return oldCodeContext;
}
private CodeContext createDummyCodeContext() {
return new CodeContext(this.getCodeContext().getClassFile());
}
private void writeByte(int v) {
if (v > Byte.MAX_VALUE - Byte.MIN_VALUE) {
throw new JaninoRuntimeException("Byte value out of legal range");
}
this.codeContext.write((short) -1, (byte) v);
}
private void writeShort(int v) {
if (v > Short.MAX_VALUE - Short.MIN_VALUE) {
throw new JaninoRuntimeException("Short value out of legal range");
}
this.codeContext.write((short) -1, (byte) (v >> 8), (byte) v);
}
private void writeInt(int v) {
this.codeContext.write((short) -1, (byte) (v >> 24), (byte) (v >> 16), (byte) (v >> 8), (byte) v);
}
private void writeOpcode(Java.Locatable l, int opcode) {
this.codeContext.write(l.getLocation().getLineNumber(), (byte) opcode);
}
private void writeOpcodes(Java.Locatable l, byte[] opcodes) {
this.codeContext.write(l.getLocation().getLineNumber(), opcodes);
}
private void writeBranch(Java.Locatable l, int opcode, final CodeContext.Offset dst) {
this.codeContext.writeBranch(l.getLocation().getLineNumber(), opcode, dst);
}
private void writeOffset(CodeContext.Offset src, final CodeContext.Offset dst) {
this.codeContext.writeOffset((short) -1, src, dst);
}
// Wrappers for "ClassFile.addConstant...Info()". Saves us some coding overhead.
private void writeConstantClassInfo(String descriptor) {
CodeContext ca = this.codeContext;
ca.writeShort((short) -1, ca.getClassFile().addConstantClassInfo(descriptor));
}
private void writeConstantFieldrefInfo(String classFD, String fieldName, String fieldFD) {
CodeContext ca = this.codeContext;
ca.writeShort((short) -1, ca.getClassFile().addConstantFieldrefInfo(classFD, fieldName, fieldFD));
}
private void writeConstantMethodrefInfo(String classFD, String methodName, String methodMD) {
CodeContext ca = this.codeContext;
ca.writeShort((short) -1, ca.getClassFile().addConstantMethodrefInfo(classFD, methodName, methodMD));
}
private void writeConstantInterfaceMethodrefInfo(String classFD, String methodName, String methodMD) {
CodeContext ca = this.codeContext;
ca.writeShort((short) -1, ca.getClassFile().addConstantInterfaceMethodrefInfo(classFD, methodName, methodMD));
}
/* UNUSED
private void writeConstantStringInfo(String value) {
this.codeContext.writeShort((short) -1, this.addConstantStringInfo(value));
}
*/
private short addConstantStringInfo(String value) {
return this.codeContext.getClassFile().addConstantStringInfo(value);
}
/* UNUSED
private void writeConstantIntegerInfo(int value) {
this.codeContext.writeShort((short) -1, this.addConstantIntegerInfo(value));
}
*/
private short addConstantIntegerInfo(int value) {
return this.codeContext.getClassFile().addConstantIntegerInfo(value);
}
/* UNUSED
private void writeConstantFloatInfo(float value) {
this.codeContext.writeShort((short) -1, this.addConstantFloatInfo(value));
}
*/
private short addConstantFloatInfo(float value) {
return this.codeContext.getClassFile().addConstantFloatInfo(value);
}
private void writeConstantLongInfo(long value) {
CodeContext ca = this.codeContext;
ca.writeShort((short) -1, ca.getClassFile().addConstantLongInfo(value));
}
private void writeConstantDoubleInfo(double value) {
CodeContext ca = this.codeContext;
ca.writeShort((short) -1, ca.getClassFile().addConstantDoubleInfo(value));
}
public CodeContext.Offset getWhereToBreak(Java.BreakableStatement bs) {
if (bs.whereToBreak == null) {
bs.whereToBreak = this.codeContext.new Offset();
}
return bs.whereToBreak;
}
private Java.TypeBodyDeclaration getDeclaringTypeBodyDeclaration(
Java.QualifiedThisReference qtr
) throws CompileException {
if (qtr.declaringTypeBodyDeclaration == null) {
// Compile error if in static function context.
Java.Scope s;
for (
s = qtr.getEnclosingBlockStatement();
!(s instanceof Java.TypeBodyDeclaration);
s = s.getEnclosingScope()
);
qtr.declaringTypeBodyDeclaration = (Java.TypeBodyDeclaration) s;
if (qtr.declaringTypeBodyDeclaration.isStatic()) {
this.compileError("No current instance available in static method", qtr.getLocation());
}
// Determine declaring type.
qtr.declaringClass = (Java.ClassDeclaration) qtr.declaringTypeBodyDeclaration.getDeclaringType();
}
return qtr.declaringTypeBodyDeclaration;
}
private Java.ClassDeclaration getDeclaringClass(Java.QualifiedThisReference qtr) throws CompileException {
if (qtr.declaringClass == null) {
this.getDeclaringTypeBodyDeclaration(qtr);
}
return qtr.declaringClass;
}
private void referenceThis(Locatable l) {
this.writeOpcode(l, Opcode.ALOAD_0);
}
/**
* Expects "dimExprCount" values of type "integer" on the operand stack.
* Creates an array of "dimExprCount" + "dims" dimensions of
* "componentType".
*
* @return The type of the created array
*/
private IClass newArray(
Locatable l,
int dimExprCount,
int dims,
IClass componentType
) {
if (dimExprCount == 1 && dims == 0 && componentType.isPrimitive()) {
// "new <primitive>[<size>]"
this.writeOpcode(l, Opcode.NEWARRAY);
this.writeByte((
componentType == IClass.BOOLEAN ? 4 :
componentType == IClass.CHAR ? 5 :
componentType == IClass.FLOAT ? 6 :
componentType == IClass.DOUBLE ? 7 :
componentType == IClass.BYTE ? 8 :
componentType == IClass.SHORT ? 9 :
componentType == IClass.INT ? 10 :
componentType == IClass.LONG ? 11 : -1
));
return componentType.getArrayIClass(this.iClassLoader.OBJECT);
}
if (dimExprCount == 1) {
IClass at = componentType.getArrayIClass(dims, this.iClassLoader.OBJECT);
// "new <class-or-interface>[<size>]"
// "new <anything>[<size>][]..."
this.writeOpcode(l, Opcode.ANEWARRAY);
this.writeConstantClassInfo(at.getDescriptor());
return at.getArrayIClass(this.iClassLoader.OBJECT);
} else {
IClass at = componentType.getArrayIClass(dimExprCount + dims, this.iClassLoader.OBJECT);
// "new <anything>[]..."
// "new <anything>[<size1>][<size2>]..."
// "new <anything>[<size1>][<size2>]...[]..."
this.writeOpcode(l, Opcode.MULTIANEWARRAY);
this.writeConstantClassInfo(at.getDescriptor());
this.writeByte(dimExprCount);
return at;
}
}
/**
* Short-hand implementation of {@link IClass.IField} that implements a
* non-constant, non-static, package-accessible field.
*/
public static class SimpleIField extends IClass.IField {
private final String name;
private final IClass type;
public SimpleIField(
IClass declaringIClass,
String name,
IClass type
) {
declaringIClass.super();
this.name = name;
this.type = type;
}
public Object getConstantValue() { return null; }
public String getName() { return this.name; }
public IClass getType() { return this.type; }
public boolean isStatic() { return false; }
public Access getAccess() { return Access.DEFAULT; }
};
private static Access modifiers2Access(short modifiers) {
return (
(modifiers & Mod.PUBLIC) != 0 ? Access.PUBLIC :
(modifiers & Mod.PROTECTED) != 0 ? Access.PROTECTED :
(modifiers & Mod.PRIVATE) != 0 ? Access.PRIVATE :
Access.DEFAULT
);
}
private static String last(String[] sa) {
if (sa.length == 0) throw new IllegalArgumentException("SNO: Empty string array");
return sa[sa.length - 1];
}
private static String[] allButLast(String[] sa) {
if (sa.length == 0) throw new IllegalArgumentException("SNO: Empty string array");
String[] tmp = new String[sa.length - 1];
System.arraycopy(sa, 0, tmp, 0, tmp.length);
return tmp;
}
private static String[] concat(String[] sa, String s) {
String[] tmp = new String[sa.length + 1];
System.arraycopy(sa, 0, tmp, 0, sa.length);
tmp[sa.length] = s;
return tmp;
}
// Used to write byte code while compiling one constructor/method.
private CodeContext codeContext = null;
// Used for elaborate compile error handling.
private ErrorHandler optionalCompileErrorHandler = null;
private int compileErrorCount = 0;
// Used for elaborate warning handling.
private WarningHandler optionalWarningHandler = null;
public final Java.CompilationUnit compilationUnit;
private final IClassLoader iClassLoader;
private final boolean isStringBuilderAvailable;
private List generatedClassFiles;
private boolean debugSource;
private boolean debugLines;
private boolean debugVars;
/** String simpleTypeName => String[] fullyQualifiedTypeName */
private final Map singleTypeImports = new HashMap();
/** String[] package */
private final Collection typeImportsOnDemand;
/** String staticMemberName => List of(IField, IMethod and IClass) */
private final Map singleStaticImports = new HashMap();
private final Collection staticImportsOnDemand = new ArrayList(); // IClass
}