/* * Janino - An embedded Java[TM] compiler * * Copyright (c) 2001-2013, Arno Unkrig * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the * following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package 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.ErrorHandler; import org.codehaus.commons.compiler.Location; import org.codehaus.commons.compiler.UncheckedCompileException; import org.codehaus.commons.compiler.WarningHandler; import org.codehaus.janino.CodeContext.Inserter; import org.codehaus.janino.CodeContext.Offset; import org.codehaus.janino.IClass.IConstructor; import org.codehaus.janino.IClass.IField; import org.codehaus.janino.IClass.IInvocable; import org.codehaus.janino.IClass.IMethod; import org.codehaus.janino.Java.AbstractTypeDeclaration; import org.codehaus.janino.Java.AlternateConstructorInvocation; import org.codehaus.janino.Java.AmbiguousName; import org.codehaus.janino.Java.Annotation; import org.codehaus.janino.Java.AnonymousClassDeclaration; import org.codehaus.janino.Java.ArrayAccessExpression; import org.codehaus.janino.Java.ArrayInitializer; import org.codehaus.janino.Java.ArrayInitializerOrRvalue; import org.codehaus.janino.Java.ArrayLength; import org.codehaus.janino.Java.ArrayType; import org.codehaus.janino.Java.AssertStatement; import org.codehaus.janino.Java.Assignment; import org.codehaus.janino.Java.Atom; import org.codehaus.janino.Java.BasicType; import org.codehaus.janino.Java.BinaryOperation; import org.codehaus.janino.Java.Block; import org.codehaus.janino.Java.BlockStatement; import org.codehaus.janino.Java.BooleanLiteral; import org.codehaus.janino.Java.BooleanRvalue; import org.codehaus.janino.Java.BreakStatement; import org.codehaus.janino.Java.BreakableStatement; import org.codehaus.janino.Java.Cast; import org.codehaus.janino.Java.CatchClause; import org.codehaus.janino.Java.CharacterLiteral; import org.codehaus.janino.Java.ClassDeclaration; import org.codehaus.janino.Java.ClassLiteral; import org.codehaus.janino.Java.CompilationUnit; 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.Java.ConditionalExpression; import org.codehaus.janino.Java.ConstructorDeclarator; import org.codehaus.janino.Java.ConstructorInvocation; import org.codehaus.janino.Java.ContinuableStatement; import org.codehaus.janino.Java.ContinueStatement; import org.codehaus.janino.Java.Crement; import org.codehaus.janino.Java.DoStatement; import org.codehaus.janino.Java.DocCommentable; import org.codehaus.janino.Java.EmptyStatement; import org.codehaus.janino.Java.EnclosingScopeOfTypeDeclaration; import org.codehaus.janino.Java.ExpressionStatement; import org.codehaus.janino.Java.FieldAccess; import org.codehaus.janino.Java.FieldAccessExpression; import org.codehaus.janino.Java.FieldDeclaration; import org.codehaus.janino.Java.FloatingPointLiteral; import org.codehaus.janino.Java.ForEachStatement; import org.codehaus.janino.Java.ForStatement; import org.codehaus.janino.Java.FunctionDeclarator; import org.codehaus.janino.Java.FunctionDeclarator.FormalParameter; import org.codehaus.janino.Java.FunctionDeclarator.FormalParameters; import org.codehaus.janino.Java.IfStatement; import org.codehaus.janino.Java.Initializer; import org.codehaus.janino.Java.InnerClassDeclaration; import org.codehaus.janino.Java.Instanceof; import org.codehaus.janino.Java.IntegerLiteral; import org.codehaus.janino.Java.InterfaceDeclaration; import org.codehaus.janino.Java.Invocation; import org.codehaus.janino.Java.LabeledStatement; import org.codehaus.janino.Java.Literal; import org.codehaus.janino.Java.LocalClassDeclaration; import org.codehaus.janino.Java.LocalClassDeclarationStatement; import org.codehaus.janino.Java.LocalVariable; import org.codehaus.janino.Java.LocalVariableAccess; 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.Lvalue; import org.codehaus.janino.Java.MemberClassDeclaration; import org.codehaus.janino.Java.MemberInterfaceDeclaration; import org.codehaus.janino.Java.MemberTypeDeclaration; import org.codehaus.janino.Java.MethodDeclarator; import org.codehaus.janino.Java.MethodInvocation; import org.codehaus.janino.Java.Modifiers; import org.codehaus.janino.Java.NamedClassDeclaration; import org.codehaus.janino.Java.NamedTypeDeclaration; import org.codehaus.janino.Java.NewAnonymousClassInstance; import org.codehaus.janino.Java.NewArray; import org.codehaus.janino.Java.NewClassInstance; import org.codehaus.janino.Java.NewInitializedArray; import org.codehaus.janino.Java.NullLiteral; import org.codehaus.janino.Java.Package; import org.codehaus.janino.Java.PackageMemberClassDeclaration; import org.codehaus.janino.Java.PackageMemberInterfaceDeclaration; import org.codehaus.janino.Java.PackageMemberTypeDeclaration; import org.codehaus.janino.Java.Padder; import org.codehaus.janino.Java.ParameterAccess; import org.codehaus.janino.Java.ParenthesizedExpression; import org.codehaus.janino.Java.QualifiedThisReference; import org.codehaus.janino.Java.ReferenceType; import org.codehaus.janino.Java.ReturnStatement; import org.codehaus.janino.Java.Rvalue; import org.codehaus.janino.Java.RvalueMemberType; import org.codehaus.janino.Java.Scope; import org.codehaus.janino.Java.SimpleConstant; import org.codehaus.janino.Java.SimpleType; import org.codehaus.janino.Java.Statement; import org.codehaus.janino.Java.StringLiteral; import org.codehaus.janino.Java.SuperConstructorInvocation; import org.codehaus.janino.Java.SuperclassFieldAccessExpression; import org.codehaus.janino.Java.SuperclassMethodInvocation; import org.codehaus.janino.Java.SwitchStatement; import org.codehaus.janino.Java.SynchronizedStatement; import org.codehaus.janino.Java.ThisReference; import org.codehaus.janino.Java.ThrowStatement; import org.codehaus.janino.Java.TryStatement; import org.codehaus.janino.Java.Type; import org.codehaus.janino.Java.TypeBodyDeclaration; import org.codehaus.janino.Java.TypeDeclaration; import org.codehaus.janino.Java.TypeParameter; import org.codehaus.janino.Java.UnaryOperation; import org.codehaus.janino.Java.VariableDeclarator; import org.codehaus.janino.Java.WhileStatement; import org.codehaus.janino.Visitor.AtomVisitor; import org.codehaus.janino.Visitor.BlockStatementVisitor; import org.codehaus.janino.Visitor.ElementValueVisitor; import org.codehaus.janino.Visitor.ImportVisitor; import org.codehaus.janino.Visitor.LvalueVisitor; import org.codehaus.janino.Visitor.RvalueVisitor; import org.codehaus.janino.Visitor.TypeDeclarationVisitor; import org.codehaus.janino.util.ClassFile; /** * This class actually implements the Java™ compiler. It is associated with exactly one compilation unit which it * compiles. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class UnitCompiler { private static final boolean DEBUG = false; /** * This constant determines the number of operands up to which the *
     *      a.concat(b).concat(c)
     * 
* strategy is used to implement string concatenation. For more operands, the *
     *      new StringBuilder(a).append(b).append(c).append(d).toString()
     * 
* strategy is chosen. *

* A very good article from Tom * Gibara analyzes the impact of this decision and recommends a value of three. */ private static final int STRING_CONCAT_LIMIT = 3; /** * Special value for the {@code orientation} parameter of the {@link #compileBoolean(Java.Rvalue, * CodeContext.Offset, boolean)} methods, indicating that the code should be generated such that execution branches * if the value on top of the operand stack is TRUE. */ public static final boolean JUMP_IF_TRUE = true; /** * Special value for the {@code orientation} parameter of the {@link #compileBoolean(Java.Rvalue, * CodeContext.Offset, boolean)} methods, indicating that the code should be generated such that execution branches * if the value on top of the operand stack is FALSE. */ public static final boolean JUMP_IF_FALSE = false; public UnitCompiler(CompilationUnit compilationUnit, IClassLoader iClassLoader) { this.compilationUnit = compilationUnit; this.iClassLoader = iClassLoader; } /** @return The {@link CompilationUnit} that this {@link UnitCompiler} compiles */ public CompilationUnit getCompilationUnit() { return this.compilationUnit; } private void import2(SingleStaticImportDeclaration ssid) throws CompileException { String name = UnitCompiler.last(ssid.identifiers); List importedObjects = (List) this.singleStaticImports.get(name); if (importedObjects == null) { importedObjects = new ArrayList(); this.singleStaticImports.put(name, importedObjects); } // Type? { IClass iClass = this.findTypeByFullyQualifiedName(ssid.getLocation(), ssid.identifiers); if (iClass != null) { importedObjects.add(iClass); return; } } String[] typeName = UnitCompiler.allButLast(ssid.identifiers); IClass iClass = this.findTypeByFullyQualifiedName(ssid.getLocation(), typeName); if (iClass == null) { this.compileError("Could not load \"" + Java.join(typeName, ".") + "\"", ssid.getLocation()); return; } // Static field? IField iField = iClass.getDeclaredIField(name); if (iField != null) { if (!iField.isStatic()) { this.compileError( "Field \"" + name + "\" of \"" + Java.join(typeName, ".") + "\" must be static", ssid.getLocation() ); } importedObjects.add(iField); return; } // Static method? IMethod[] ms = iClass.getDeclaredIMethods(name); if (ms.length > 0) { importedObjects.addAll(Arrays.asList(ms)); return; } // Give up. this.compileError( "\"" + Java.join(typeName, ".") + "\" has no static member \"" + name + "\"", ssid.getLocation() ); } private void import2(StaticImportOnDemandDeclaration siodd) throws CompileException { IClass iClass = this.findTypeByFullyQualifiedName(siodd.getLocation(), siodd.identifiers); if (iClass == null) { this.compileError("Could not load \"" + Java.join(siodd.identifiers, ".") + "\"", siodd.getLocation()); return; } this.staticImportsOnDemand.add(iClass); } /** * Generates an array of {@link ClassFile} objects which represent the classes and interfaces declared in the * compilation unit. */ public ClassFile[] compileUnit(boolean debugSource, boolean debugLines, boolean debugVars) throws CompileException { this.debugSource = debugSource; this.debugLines = debugLines; this.debugVars = debugVars; // Compile static import declarations. // Notice: The single-type and on-demand imports are needed BEFORE the unit is compiled, thus they are // processed in 'getSingleTypeImport()' and 'importOnDemand()'. for (ImportDeclaration id : this.compilationUnit.importDeclarations) { try { id.accept(new ImportVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitSingleTypeImportDeclaration(SingleTypeImportDeclaration stid) {} @Override public void visitTypeImportOnDemandDeclaration(TypeImportOnDemandDeclaration tiodd) {} @Override public void visitSingleStaticImportDeclaration(SingleStaticImportDeclaration ssid) { try { UnitCompiler.this.import2(ssid); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitStaticImportOnDemandDeclaration(StaticImportOnDemandDeclaration siodd) { try { UnitCompiler.this.import2(siodd); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }); } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } this.generatedClassFiles = new ArrayList(); for (PackageMemberTypeDeclaration pmtd : this.compilationUnit.packageMemberTypeDeclarations) { this.compile(pmtd); } if (this.compileErrorCount > 0) { throw new CompileException(( this.compileErrorCount + " error(s) while compiling unit \"" + this.compilationUnit.optionalFileName + "\"" ), null); } List l = this.generatedClassFiles; return (ClassFile[]) l.toArray(new ClassFile[l.size()]); } // ------------ TypeDeclaration.compile() ------------- private void compile(TypeDeclaration td) throws CompileException { TypeDeclarationVisitor tdv = new TypeDeclarationVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitAnonymousClassDeclaration(AnonymousClassDeclaration acd) { try { UnitCompiler.this.compile2(acd); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalClassDeclaration(LocalClassDeclaration lcd) { try { UnitCompiler.this.compile2(lcd); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitPackageMemberClassDeclaration(PackageMemberClassDeclaration pmcd) { try { UnitCompiler.this.compile2((PackageMemberTypeDeclaration) pmcd); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitMemberInterfaceDeclaration(MemberInterfaceDeclaration mid) { try { UnitCompiler.this.compile2(mid); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitPackageMemberInterfaceDeclaration(PackageMemberInterfaceDeclaration pmid) { try { UnitCompiler.this.compile2((PackageMemberTypeDeclaration) pmid); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitMemberClassDeclaration(MemberClassDeclaration mcd) { try { UnitCompiler.this.compile2(mcd); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { td.accept(tdv); } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } private void compile2(PackageMemberTypeDeclaration pmtd) throws CompileException { CompilationUnit declaringCompilationUnit = pmtd.getDeclaringCompilationUnit(); // Check for conflict with single-type-import (7.6). { String[] ss = this.getSingleTypeImport(pmtd.getName(), pmtd.getLocation()); if (ss != null) { this.compileError(( "Package member type declaration \"" + pmtd.getName() + "\" conflicts with single-type-import \"" + Java.join(ss, ".") + "\"" ), pmtd.getLocation()); } } // Check for redefinition within compilation unit (7.6). { PackageMemberTypeDeclaration otherPmtd = declaringCompilationUnit.getPackageMemberTypeDeclaration( pmtd.getName() ); if (otherPmtd != pmtd) { this.compileError(( "Redeclaration of type \"" + pmtd.getName() + "\", previously declared in " + otherPmtd.getLocation() ), pmtd.getLocation()); } } if (pmtd instanceof NamedClassDeclaration) { this.compile2((NamedClassDeclaration) pmtd); } else if (pmtd instanceof InterfaceDeclaration) { this.compile2((InterfaceDeclaration) pmtd); } else { throw new JaninoRuntimeException("PMTD of unexpected type " + pmtd.getClass().getName()); } } private void compile2(ClassDeclaration cd) throws CompileException { IClass iClass = this.resolve(cd); // Check that all methods of the non-abstract class are implemented. if (!Mod.isAbstract(cd.getModifierFlags())) { IMethod[] ms = iClass.getIMethods(); for (IMethod base : ms) { if (base.isAbstract()) { IMethod override = iClass.findIMethod(base.getName(), base.getParameterTypes()); if ( override == null // It wasn't overridden || override.isAbstract() // It was overridden with an abstract method // The override does not provide a covariant return type || !base.getReturnType().isAssignableFrom(override.getReturnType()) ) { this.compileError( "Non-abstract class \"" + iClass + "\" must implement method \"" + base + "\"", cd.getLocation() ); } } } } // Create "ClassFile" object. ClassFile cf = new ClassFile( (short) (cd.getModifierFlags() | Mod.SUPER), // accessFlags iClass.getDescriptor(), // thisClassFD iClass.getSuperclass().getDescriptor(), // superclassFD IClass.getDescriptors(iClass.getInterfaces()) // interfaceFDs ); // TODO: Add annotations with retention != SOURCE. // for (Annotation a : cd.getAnnotations()) { // assert false : "Class '" + iClass + "' has annotation '" + a + "'"; // } // Add InnerClasses attribute entry for this class declaration. if (cd.getEnclosingScope() instanceof CompilationUnit) { ; } else if (cd.getEnclosingScope() instanceof Block) { short innerClassInfoIndex = cf.addConstantClassInfo(iClass.getDescriptor()); short innerNameIndex = ( this instanceof NamedTypeDeclaration ? cf.addConstantUtf8Info(((NamedTypeDeclaration) this).getName()) : (short) 0 ); assert cd.getAnnotations().length == 0 : "NYI"; cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry( innerClassInfoIndex, // innerClassInfoIndex (short) 0, // outerClassInfoIndex innerNameIndex, // innerNameIndex cd.getModifierFlags() // innerClassAccessFlags )); } else if (cd.getEnclosingScope() instanceof TypeDeclaration) { short innerClassInfoIndex = cf.addConstantClassInfo(iClass.getDescriptor()); short outerClassInfoIndex = cf.addConstantClassInfo( this.resolve(((TypeDeclaration) cd.getEnclosingScope())).getDescriptor() ); short innerNameIndex = cf.addConstantUtf8Info(((MemberTypeDeclaration) cd).getName()); assert cd.getAnnotations().length == 0 : "NYI"; cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry( innerClassInfoIndex, // innerClassInfoIndex outerClassInfoIndex, // outerClassInfoIndex innerNameIndex, // innerNameIndex cd.getModifierFlags() // innerClassAccessFlags )); } // Set "SourceFile" attribute. if (this.debugSource) { String sourceFileName; { String s = cd.getLocation().getFileName(); if (s != null) { sourceFileName = new File(s).getName(); } else if (cd instanceof NamedTypeDeclaration) { sourceFileName = ((NamedTypeDeclaration) cd).getName() + ".java"; } else { sourceFileName = "ANONYMOUS.java"; } } cf.addSourceFileAttribute(sourceFileName); } // Add "Deprecated" attribute (JVMS 4.7.10). if (cd instanceof DocCommentable) { if (((DocCommentable) cd).hasDeprecatedDocTag()) cf.addDeprecatedAttribute(); } // Optional: Generate and compile class initialization method. { List statements = new ArrayList(); for (BlockStatement vdoi : cd.variableDeclaratorsAndInitializers) { if (((TypeBodyDeclaration) vdoi).isStatic()) statements.add(vdoi); } this.maybeCreateInitMethod(cd, cf, statements); } this.compileDeclaredMethods(cd, cf); // Compile declared constructors. // As a side effect of compiling methods and constructors, synthetic "class-dollar" methods (which implement // class literals) are generated on-the fly. We need to note how many we have here so we can compile the // extras. final int declaredMethodCount = cd.getMethodDeclarations().size(); { int syntheticFieldCount = cd.syntheticFields.size(); ConstructorDeclarator[] ctords = cd.getConstructors(); for (ConstructorDeclarator ctord : ctords) { this.compile(ctord, cf); if (syntheticFieldCount != cd.syntheticFields.size()) { throw new JaninoRuntimeException( "SNO: Compilation of constructor \"" + ctord + "\" (" + ctord.getLocation() + ") added synthetic fields!?" ); } } } // A side effect of this call may create synthetic functions to access protected parent variables. this.compileDeclaredMemberTypes(cd, cf); // Compile the aforementioned extras. this.compileDeclaredMethods(cd, cf, declaredMethodCount); { // for every method look for bridge methods that need to be supplied this is used to correctly dispatch // into covariant return types from existing code. IMethod[] ms = iClass.getIMethods(); for (IMethod base : ms) { if (!base.isStatic()) { IMethod override = iClass.findIMethod(base.getName(), base.getParameterTypes()); // If we overrode the method but with a DIFFERENT return type. if ( override != null && !base.getReturnType().equals(override.getReturnType()) ) { this.compileBridgeMethod(cf, base, override); } } } } // Class and instance variables. for (BlockStatement vdoi : cd.variableDeclaratorsAndInitializers) { if (vdoi instanceof FieldDeclaration) this.addFields((FieldDeclaration) vdoi, cf); } // Synthetic fields. for (IField f : cd.syntheticFields.values()) { cf.addFieldInfo( new Modifiers(Mod.PACKAGE), // modifiers f.getName(), // fieldName f.getType().getDescriptor(), // fieldTypeFD null // optionalConstantValue ); } // Add the generated class file to a thread-local store. this.generatedClassFiles.add(cf); } /** Creates {@link ClassFile.FieldInfo}s for all fields declared by the given {@link FieldDeclaration}. */ private void addFields(FieldDeclaration fd, ClassFile cf) throws CompileException { for (VariableDeclarator vd : fd.variableDeclarators) { Type type = fd.type; for (int i = 0; i < vd.brackets; ++i) type = new ArrayType(type); Object ocv = UnitCompiler.NOT_CONSTANT; if (Mod.isFinal(fd.modifiers.flags) && vd.optionalInitializer instanceof Rvalue) { ocv = this.getConstantValue((Rvalue) vd.optionalInitializer); } ClassFile.FieldInfo fi; if (Mod.isPrivateAccess(fd.modifiers.flags)) { // To make the private field accessible for enclosing types, enclosed types and types enclosed by the // same type, it is modified as follows: // + Access is changed from PRIVATE to PACKAGE assert fd.modifiers.annotations.length == 0 : "NYI"; fi = cf.addFieldInfo( fd.modifiers.changeAccess(Mod.PACKAGE), // modifiers vd.name, // fieldName this.getType(type).getDescriptor(), // fieldTypeFD ocv == UnitCompiler.NOT_CONSTANT ? null : ocv // optionalConstantValue ); } else { assert fd.modifiers.annotations.length == 0 : "NYI"; fi = cf.addFieldInfo( fd.modifiers, // modifiers vd.name, // fieldName this.getType(type).getDescriptor(), // fieldTypeFD ocv == UnitCompiler.NOT_CONSTANT ? null : ocv // optionalConstantValue ); } // Add "Deprecated" attribute (JVMS 4.7.10). if (fd.hasDeprecatedDocTag()) { fi.addAttribute(new ClassFile.DeprecatedAttribute(cf.addConstantUtf8Info("Deprecated"))); } } } private void compile2(AnonymousClassDeclaration acd) throws CompileException { this.compile2((InnerClassDeclaration) acd); } private void compile2(LocalClassDeclaration lcd) throws CompileException { this.compile2((InnerClassDeclaration) lcd); } private void compile2(InnerClassDeclaration icd) throws CompileException { // Define a synthetic "this$..." field if there is an enclosing instance. { List ocs = UnitCompiler.getOuterClasses(icd); final int nesting = ocs.size(); if (nesting >= 2) { icd.defineSyntheticField(new SimpleIField( this.resolve(icd), "this$" + (nesting - 2), this.resolve((TypeDeclaration) ocs.get(1)) )); } } // For classes that enclose surrounding scopes, trawl their field initializers looking for synthetic fields. if (icd instanceof AnonymousClassDeclaration || icd instanceof LocalClassDeclaration) { ClassDeclaration cd = (ClassDeclaration) icd; // Compilation of field declarations can create synthetic variables, so we must not use an iterator. List vdais = cd.variableDeclaratorsAndInitializers; for (int i = 0; i < vdais.size(); i++) { BlockStatement vdoi = (BlockStatement) vdais.get(i); this.fakeCompile(vdoi); } } this.compile2((ClassDeclaration) icd); } private void compile2(final MemberClassDeclaration mcd) throws CompileException { this.compile2((InnerClassDeclaration) mcd); } private void compile2(InterfaceDeclaration id) throws CompileException { final IClass iClass = this.resolve(id); // Determine extended interfaces. id.interfaces = new IClass[id.extendedTypes.length]; String[] interfaceDescriptors = new String[id.interfaces.length]; for (int i = 0; i < id.extendedTypes.length; ++i) { id.interfaces[i] = this.getType(id.extendedTypes[i]); interfaceDescriptors[i] = id.interfaces[i].getDescriptor(); } // Create "ClassFile" object. ClassFile cf = new ClassFile( (short) (id.getModifierFlags() | Mod.SUPER | Mod.INTERFACE | Mod.ABSTRACT), // accessFlags iClass.getDescriptor(), // thisClassFD Descriptor.JAVA_LANG_OBJECT, // superclassFD interfaceDescriptors // interfaceFDs ); // TODO: Add annotations with retention != SOURCE. // for (Annotation a : id.getAnnotations()) { // assert false : "Interface '" + iClass + "' has annotation '" + a + "'"; // } // Set "SourceFile" attribute. if (this.debugSource) { String sourceFileName; { String s = id.getLocation().getFileName(); if (s != null) { sourceFileName = new File(s).getName(); } else { sourceFileName = id.getName() + ".java"; } } cf.addSourceFileAttribute(sourceFileName); } // Add "Deprecated" attribute (JVMS 4.7.10). if (id.hasDeprecatedDocTag()) cf.addDeprecatedAttribute(); // Interface initialization method. if (!id.constantDeclarations.isEmpty()) { List statements = new ArrayList(); statements.addAll(id.constantDeclarations); this.maybeCreateInitMethod(id, cf, statements); } this.compileDeclaredMethods(id, cf); // Class variables. for (FieldDeclaration constantDeclaration : id.constantDeclarations) this.addFields(constantDeclaration, cf); this.compileDeclaredMemberTypes(id, cf); // Add the generated class file to a thread-local store. this.generatedClassFiles.add(cf); } /** * Create class initialization method iff there is any initialization code. * * @param decl The type declaration * @param cf The class file into which to put the method * @param b The block for the method (possibly empty) * @throws CompileException */ private void maybeCreateInitMethod( AbstractTypeDeclaration decl, ClassFile cf, List statements ) throws CompileException { // Create interface initialization method iff there is any initialization code. if (this.generatesCode2(statements)) { MethodDeclarator md = new MethodDeclarator( decl.getLocation(), // location null, // optionalDocComment new Modifiers((short) (Mod.STATIC | Mod.PUBLIC)), // modifiers new BasicType( // type decl.getLocation(), BasicType.VOID ), "", // name new FormalParameters(), // formalParameters new ReferenceType[0], // thrownExceptions statements // optionalStatements ); md.setDeclaringType(decl); this.compile(md, cf); } } /** * Compile all of the types for this declaration *

* NB: as a side effect this will fill in the synthetic field map */ private void compileDeclaredMemberTypes(TypeDeclaration decl, ClassFile cf) throws CompileException { for (MemberTypeDeclaration mtd : decl.getMemberTypeDeclarations()) { this.compile(mtd); // Add InnerClasses attribute entry for member type declaration. short innerClassInfoIndex = cf.addConstantClassInfo(this.resolve(mtd).getDescriptor()); short outerClassInfoIndex = cf.addConstantClassInfo(this.resolve(decl).getDescriptor()); short innerNameIndex = cf.addConstantUtf8Info(mtd.getName()); assert mtd.getAnnotations().length == 0; cf.addInnerClassesAttributeEntry(new ClassFile.InnerClassesAttribute.Entry( innerClassInfoIndex, // innerClassInfoIndex outerClassInfoIndex, // outerClassInfoIndex innerNameIndex, // innerNameIndex mtd.getModifierFlags() // innerClassAccessFlags )); } } /** * Compile all of the methods for this declaration *

* NB: as a side effect this will fill in the synthetic field map * * @throws CompileException */ private void compileDeclaredMethods(AbstractTypeDeclaration typeDeclaration, ClassFile cf) throws CompileException { this.compileDeclaredMethods(typeDeclaration, cf, 0); } /** * Compile methods for this declaration starting at {@code startPos}. * * @param startPos Starting parameter to fill in * @throws CompileException */ private void compileDeclaredMethods(TypeDeclaration typeDeclaration, ClassFile cf, int startPos) throws CompileException { // Notice that as a side effect of compiling methods, synthetic "class-dollar" methods (which implement class // literals) are generated on-the fly. Hence, we must not use an Iterator here. for (int i = startPos; i < typeDeclaration.getMethodDeclarations().size(); ++i) { MethodDeclarator md = (MethodDeclarator) typeDeclaration.getMethodDeclarations().get(i); IMethod m = this.toIMethod(md); boolean overrides = this.overridesMethodFromSupertype(m, this.resolve(md.getDeclaringType())); boolean hasOverrideAnnotation = this.hasAnnotation(md, this.iClassLoader.ANNO_java_lang_Override); if (overrides && !hasOverrideAnnotation && !(typeDeclaration instanceof InterfaceDeclaration)) { this.warning("MO", "Missing @Override", md.getLocation()); } else if (!overrides && hasOverrideAnnotation) { this.compileError("Method does not override a method declared in a supertype", md.getLocation()); } this.compile(md, cf); } } private boolean hasAnnotation(FunctionDeclarator fd, IClass methodAnnotation) throws CompileException { Annotation[] methodAnnotations = fd.modifiers.annotations; for (Annotation ma : methodAnnotations) { if (this.getType(ma.getType()) == methodAnnotation) return true; } return false; } private boolean overridesMethodFromSupertype(IMethod m, IClass type) throws CompileException { // Check whether it overrides a method declared in the superclass (or any of its supertypes). { IClass superclass = type.getSuperclass(); if (superclass != null && this.overridesMethod(m, superclass)) return true; } // Check whether it overrides a method declared in an interface (or any of its superinterfaces). IClass[] ifs = type.getInterfaces(); for (IClass i : ifs) { if (this.overridesMethod(m, i)) return true; } // Special handling for interfaces that don't extend other interfaces: JLS7 dictates that these stem from // 'Object', but 'getSuperclass()' returns NULL for interfaces. if (ifs.length == 0 && type.isInterface()) { return this.overridesMethod(m, this.iClassLoader.TYPE_java_lang_Object); } return false; } /** @return Whether {@code method} overrides a method of {@code type} or any of its supertypes */ private boolean overridesMethod(IMethod method, IClass type) throws CompileException { // Check whether it overrides a method declared in THIS type. IMethod[] ms = type.getDeclaredIMethods(method.getName()); for (IMethod m : ms) { if (Arrays.equals(method.getParameterTypes(), m.getParameterTypes())) return true; } // Check whether it overrides a method declared in a supertype. return this.overridesMethodFromSupertype(method, type); } /** Compiles a bridge method which will add a method of the signature of base that delegates to override. */ private void compileBridgeMethod(ClassFile cf, IMethod base, IMethod override) throws CompileException { ClassFile.MethodInfo mi = cf.addMethodInfo( new Modifiers((short) (Mod.PUBLIC | Mod.SYNTHETIC)), base.getName(), base.getDescriptor() ); // Add "Exceptions" attribute (JVMS 4.7.4). IClass[] thrownExceptions = base.getThrownExceptions(); if (thrownExceptions.length > 0) { final short eani = cf.addConstantUtf8Info("Exceptions"); short[] tecciis = new short[thrownExceptions.length]; for (int i = 0; i < thrownExceptions.length; ++i) { tecciis[i] = cf.addConstantClassInfo(thrownExceptions[i].getDescriptor()); } mi.addAttribute(new ClassFile.ExceptionsAttribute(eani, tecciis)); } final CodeContext codeContext = new CodeContext(mi.getClassFile(), base.toString()); final CodeContext savedCodeContext = this.replaceCodeContext(codeContext); // Allocate all our local variables. codeContext.saveLocalVariables(); codeContext.allocateLocalVariable((short) 1, "this", override.getDeclaringIClass()); IClass[] paramTypes = override.getParameterTypes(); LocalVariableSlot[] locals = new LocalVariableSlot[paramTypes.length]; for (int i = 0; i < paramTypes.length; ++i) { locals[i] = codeContext.allocateLocalVariable( Descriptor.size(paramTypes[i].getDescriptor()), "param" + i, paramTypes[i] ); } this.writeOpcode(Located.NOWHERE, Opcode.ALOAD_0); for (LocalVariableSlot l : locals) this.load(Located.NOWHERE, l.getType(), l.getSlotIndex()); this.invoke(Located.NOWHERE, override); this.writeOpcode(Located.NOWHERE, Opcode.ARETURN); this.replaceCodeContext(savedCodeContext); codeContext.flowAnalysis(override.getName()); // Add the code context as a code attribute to the MethodInfo. mi.addAttribute(new ClassFile.AttributeInfo(cf.addConstantUtf8Info("Code")) { @Override protected void storeBody(DataOutputStream dos) throws IOException { codeContext.storeCodeAttributeBody(dos, (short) 0, (short) 0); } }); } /** @return Whether this statement can complete normally (JLS7 14.1) */ private boolean compile(BlockStatement bs) throws CompileException { final boolean[] res = new boolean[1]; BlockStatementVisitor bsv = new BlockStatementVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitInitializer(Initializer i) { try { res[0] = UnitCompiler.this.compile2(i); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldDeclaration(FieldDeclaration fd) { try { res[0] = UnitCompiler.this.compile2(fd); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLabeledStatement(LabeledStatement ls) { try { res[0] = UnitCompiler.this.compile2(ls); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBlock(Block b) { try { res[0] = UnitCompiler.this.compile2(b); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitExpressionStatement(ExpressionStatement es) { try { res[0] = UnitCompiler.this.compile2(es); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitIfStatement(IfStatement is) { try { res[0] = UnitCompiler.this.compile2(is); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitForStatement(ForStatement fs) { try { res[0] = UnitCompiler.this.compile2(fs); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitForEachStatement(ForEachStatement fes) { try { res[0] = UnitCompiler.this.compile2(fes); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitWhileStatement(WhileStatement ws) { try { res[0] = UnitCompiler.this.compile2(ws); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitTryStatement(TryStatement ts) { try { res[0] = UnitCompiler.this.compile2(ts); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSwitchStatement(SwitchStatement ss) { try { res[0] = UnitCompiler.this.compile2(ss); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSynchronizedStatement(SynchronizedStatement ss) { try { res[0] = UnitCompiler.this.compile2(ss); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitDoStatement(DoStatement ds) { try { res[0] = UnitCompiler.this.compile2(ds); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { try { res[0] = UnitCompiler.this.compile2(lvds); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitReturnStatement(ReturnStatement rs) { try { res[0] = UnitCompiler.this.compile2(rs); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitThrowStatement(ThrowStatement ts) { try { res[0] = UnitCompiler.this.compile2(ts); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBreakStatement(BreakStatement bs) { try { res[0] = UnitCompiler.this.compile2(bs); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitContinueStatement(ContinueStatement cs) { try { res[0] = UnitCompiler.this.compile2(cs); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAssertStatement(AssertStatement as) { try { res[0] = UnitCompiler.this.compile2(as); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitEmptyStatement(EmptyStatement es) { res[0] = UnitCompiler.this.compile2(es); } @Override public void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { try { res[0] = UnitCompiler.this.compile2(lcds); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { try { res[0] = UnitCompiler.this.compile2(aci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { try { res[0] = UnitCompiler.this.compile2(sci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { bs.accept(bsv); return res[0]; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } /** * Called to check whether the given {@link Rvalue} compiles or not. * * @return Whether the block statement can complete normally */ private boolean fakeCompile(BlockStatement bs) throws CompileException { Offset from = this.codeContext.newOffset(); boolean ccn = this.compile(bs); Offset to = this.codeContext.newOffset(); this.codeContext.removeCode(from, to); return ccn; } private boolean compile2(Initializer i) throws CompileException { return this.compile(i.block); } private boolean compile2(Block b) throws CompileException { this.codeContext.saveLocalVariables(); try { return this.compileStatements(b.statements); } finally { this.codeContext.restoreLocalVariables(); } } private boolean compileStatements(List statements) throws CompileException { boolean previousStatementCanCompleteNormally = true; for (BlockStatement bs : statements) { if (!previousStatementCanCompleteNormally && this.generatesCode(bs)) { this.compileError("Statement is unreachable", bs.getLocation()); break; } previousStatementCanCompleteNormally = this.compile(bs); } return previousStatementCanCompleteNormally; } private boolean compile2(DoStatement ds) throws CompileException { Object cvc = this.getConstantValue(ds.condition); if (cvc != UnitCompiler.NOT_CONSTANT) { if (Boolean.TRUE.equals(cvc)) { this.warning("DSTC", ( "Condition of DO statement is always TRUE; " + "the proper way of declaring an unconditional loop is \"for (;;)\"" ), ds.getLocation()); return this.compileUnconditionalLoop(ds, ds.body, null); } else { this.warning("DSNR", "DO statement never repeats", ds.getLocation()); } } final CodeContext.Offset bodyOffset = this.codeContext.newOffset(); // Compile body. ds.whereToContinue = null; if (!this.compile(ds.body) && ds.whereToContinue == null) { this.warning("DSNTC", "\"do\" statement never tests its condition", ds.getLocation()); if (ds.whereToBreak == null) return false; ds.whereToBreak.set(); ds.whereToBreak = null; return true; } if (ds.whereToContinue != null) { ds.whereToContinue.set(); ds.whereToContinue = null; } // Compile condition. this.compileBoolean(ds.condition, bodyOffset, UnitCompiler.JUMP_IF_TRUE); if (ds.whereToBreak != null) { ds.whereToBreak.set(); ds.whereToBreak = null; } return true; } private boolean compile2(ForStatement fs) throws CompileException { this.codeContext.saveLocalVariables(); try { // Compile initializer. if (fs.optionalInit != null) this.compile(fs.optionalInit); if (fs.optionalCondition == null) { return this.compileUnconditionalLoop(fs, fs.body, fs.optionalUpdate); } else { Object cvc = this.getConstantValue(fs.optionalCondition); if (cvc != UnitCompiler.NOT_CONSTANT) { if (Boolean.TRUE.equals(cvc)) { this.warning("FSTC", ( "Condition of FOR statement is always TRUE; " + "the proper way of declaring an unconditional loop is \"for (;;)\"" ), fs.getLocation()); return this.compileUnconditionalLoop(fs, fs.body, fs.optionalUpdate); } else { this.warning("FSNR", "FOR statement never repeats", fs.getLocation()); } } } CodeContext.Offset toCondition = this.codeContext.new Offset(); this.writeBranch(fs, Opcode.GOTO, toCondition); // Compile body. fs.whereToContinue = null; final CodeContext.Offset bodyOffset = this.codeContext.newOffset(); boolean bodyCcn = this.compile(fs.body); if (fs.whereToContinue != null) fs.whereToContinue.set(); // Compile update. if (fs.optionalUpdate != null) { if (!bodyCcn && fs.whereToContinue == null) { this.warning("FUUR", "For update is unreachable", fs.getLocation()); } else { for (Rvalue rv : fs.optionalUpdate) this.compile(rv); } } fs.whereToContinue = null; // Compile condition. toCondition.set(); this.compileBoolean(fs.optionalCondition, bodyOffset, UnitCompiler.JUMP_IF_TRUE); } finally { this.codeContext.restoreLocalVariables(); } if (fs.whereToBreak != null) { fs.whereToBreak.set(); fs.whereToBreak = null; } return true; } private boolean compile2(ForEachStatement fes) throws CompileException { IClass expressionType = this.getType(fes.expression); if (expressionType.isArray()) { this.codeContext.saveLocalVariables(); try { // Allocate the local variable for the current element. LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false); elementLv.setSlot(this.codeContext.allocateLocalVariable( Descriptor.size(elementLv.type.getDescriptor()), fes.currentElement.name, elementLv.type )); // Compile initializer. this.compileGetValue(fes.expression); short expressionLv = this.codeContext.allocateLocalVariable((short) 1); this.store(fes.expression, expressionType, expressionLv); this.pushConstant(fes, 0); LocalVariable indexLv = new LocalVariable(false, IClass.INT); indexLv.setSlot(this.codeContext.allocateLocalVariable((short) 1, null, indexLv.type)); this.store(fes, indexLv); CodeContext.Offset toCondition = this.codeContext.new Offset(); this.writeBranch(fes, Opcode.GOTO, toCondition); // Compile the body. fes.whereToContinue = null; final CodeContext.Offset bodyOffset = this.codeContext.newOffset(); this.load(fes, expressionType, expressionLv); this.load(fes, indexLv); this.writeOpcode(fes, Opcode.IALOAD + UnitCompiler.ilfdabcs(expressionType.getComponentType())); this.assignmentConversion(fes.currentElement, expressionType.getComponentType(), elementLv.type, null); this.store(fes, elementLv); boolean bodyCcn = this.compile(fes.body); if (fes.whereToContinue != null) fes.whereToContinue.set(); // Compile update. if (!bodyCcn && fes.whereToContinue == null) { this.warning("FUUR", "For update is unreachable", fes.getLocation()); } else { this.crement(fes, indexLv, "++"); } fes.whereToContinue = null; // Compile condition. toCondition.set(); this.load(fes, indexLv); this.load(fes, expressionType, expressionLv); this.writeOpcode(fes, Opcode.ARRAYLENGTH); this.writeBranch(fes, Opcode.IF_ICMPLT, bodyOffset); } finally { this.codeContext.restoreLocalVariables(); } if (fes.whereToBreak != null) { fes.whereToBreak.set(); fes.whereToBreak = null; } } else if (this.iClassLoader.TYPE_java_lang_Iterable.isAssignableFrom(expressionType)) { this.codeContext.saveLocalVariables(); try { // Allocate the local variable for the current element. LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false); elementLv.setSlot(this.codeContext.allocateLocalVariable( (short) 1, fes.currentElement.name, elementLv.type )); // Compile initializer. this.compileGetValue(fes.expression); this.invoke(fes.expression, this.iClassLoader.METH_java_lang_Iterable__iterator); LocalVariable iteratorLv = new LocalVariable(false, this.iClassLoader.TYPE_java_util_Iterator); iteratorLv.setSlot(this.codeContext.allocateLocalVariable((short) 1, null, iteratorLv.type)); this.store(fes, iteratorLv); CodeContext.Offset toCondition = this.codeContext.new Offset(); this.writeBranch(fes, Opcode.GOTO, toCondition); // Compile the body. fes.whereToContinue = null; final CodeContext.Offset bodyOffset = this.codeContext.newOffset(); this.load(fes, iteratorLv); this.invoke(fes.expression, this.iClassLoader.METH_java_util_Iterator__next); if ( !this.tryAssignmentConversion( fes.currentElement, this.iClassLoader.TYPE_java_lang_Object, elementLv.type, null ) && !this.tryNarrowingReferenceConversion( fes.currentElement, this.iClassLoader.TYPE_java_lang_Object, elementLv.type ) ) throw new AssertionError(); this.store(fes, elementLv); boolean bodyCcn = this.compile(fes.body); if (fes.whereToContinue != null) fes.whereToContinue.set(); // Compile update. if (!bodyCcn && fes.whereToContinue == null) { this.warning("FUUR", "For update is unreachable", fes.getLocation()); } fes.whereToContinue = null; // Compile condition. toCondition.set(); this.load(fes, iteratorLv); this.invoke(fes.expression, this.iClassLoader.METH_java_util_Iterator__hasNext); this.writeBranch(fes, Opcode.IFNE, bodyOffset); } finally { this.codeContext.restoreLocalVariables(); } if (fes.whereToBreak != null) { fes.whereToBreak.set(); fes.whereToBreak = null; } } else { this.compileError("Cannot iterate over '" + expressionType + "'"); } return true; } private boolean compile2(WhileStatement ws) throws CompileException { Object cvc = this.getConstantValue(ws.condition); if (cvc != UnitCompiler.NOT_CONSTANT) { if (Boolean.TRUE.equals(cvc)) { this.warning("WSTC", ( "Condition of WHILE statement is always TRUE; " + "the proper way of declaring an unconditional loop is \"for (;;)\"" ), ws.getLocation()); return this.compileUnconditionalLoop(ws, ws.body, null); } else { this.warning("WSNR", "WHILE statement never repeats", ws.getLocation()); } } // Compile body. ws.whereToContinue = this.codeContext.new Offset(); this.writeBranch(ws, Opcode.GOTO, ws.whereToContinue); final CodeContext.Offset bodyOffset = this.codeContext.newOffset(); this.compile(ws.body); // Return value (CCN) is ignored. ws.whereToContinue.set(); ws.whereToContinue = null; // Compile condition. this.compileBoolean(ws.condition, bodyOffset, UnitCompiler.JUMP_IF_TRUE); if (ws.whereToBreak != null) { ws.whereToBreak.set(); ws.whereToBreak = null; } return true; } private boolean compileUnconditionalLoop(ContinuableStatement cs, BlockStatement body, Rvalue[] optionalUpdate) throws CompileException { if (optionalUpdate != null) return this.compileUnconditionalLoopWithUpdate(cs, body, optionalUpdate); // Compile body. cs.whereToContinue = this.codeContext.newOffset(); if (this.compile(body)) this.writeBranch(cs, Opcode.GOTO, cs.whereToContinue); cs.whereToContinue = null; if (cs.whereToBreak == null) return false; cs.whereToBreak.set(); cs.whereToBreak = null; return true; } private boolean compileUnconditionalLoopWithUpdate(ContinuableStatement cs, BlockStatement body, Rvalue[] update) throws CompileException { // Compile body. cs.whereToContinue = null; final CodeContext.Offset bodyOffset = this.codeContext.newOffset(); boolean bodyCcn = this.compile(body); // Compile the "update". if (cs.whereToContinue != null) cs.whereToContinue.set(); if (!bodyCcn && cs.whereToContinue == null) { this.warning("LUUR", "Loop update is unreachable", update[0].getLocation()); } else { for (Rvalue rv : update) this.compile(rv); this.writeBranch(cs, Opcode.GOTO, bodyOffset); } cs.whereToContinue = null; if (cs.whereToBreak == null) return false; cs.whereToBreak.set(); cs.whereToBreak = null; return true; } private boolean compile2(LabeledStatement ls) throws CompileException { boolean canCompleteNormally = this.compile(ls.body); if (ls.whereToBreak == null) return canCompleteNormally; ls.whereToBreak.set(); ls.whereToBreak = null; return true; } private boolean compile2(SwitchStatement ss) throws CompileException { // Compute condition. IClass switchExpressionType = this.compileGetValue(ss.condition); this.assignmentConversion( ss, // locatable switchExpressionType, // sourceType IClass.INT, // targetType null // optionalConstantValue ); // Prepare the map of case labels to code offsets. TreeMap caseLabelMap = new TreeMap(); CodeContext.Offset defaultLabelOffset = null; CodeContext.Offset[] sbsgOffsets = new CodeContext.Offset[ss.sbsgs.size()]; for (int i = 0; i < ss.sbsgs.size(); ++i) { SwitchStatement.SwitchBlockStatementGroup sbsg = (SwitchStatement.SwitchBlockStatementGroup) ( ss.sbsgs.get(i) ); sbsgOffsets[i] = this.codeContext.new Offset(); for (Rvalue caseLabel : sbsg.caseLabels) { // Verify that case label value is a constant. Object cv = this.getConstantValue(caseLabel); if (cv == UnitCompiler.NOT_CONSTANT) { this.compileError("Value of 'case' label does not pose a constant value", caseLabel.getLocation()); cv = new Integer(99); } // Verify that case label is assignable to the type of the switch expression. IClass rvType = this.getType(caseLabel); this.assignmentConversion( ss, // locatable rvType, // sourceType switchExpressionType, // targetType cv // optionalConstantValue ); // Convert char, byte, short, int to "Integer". Integer civ; if (cv instanceof Integer) { civ = (Integer) cv; } else if (cv instanceof Number) { civ = new Integer(((Number) cv).intValue()); } else if (cv instanceof Character) { civ = new Integer(((Character) cv).charValue()); } else { this.compileError( "Value of case label must be a char, byte, short or int constant", caseLabel.getLocation() ); civ = new Integer(99); } // Store in case label map. if (caseLabelMap.containsKey(civ)) { this.compileError("Duplicate \"case\" switch label value", caseLabel.getLocation()); } caseLabelMap.put(civ, sbsgOffsets[i]); } if (sbsg.hasDefaultLabel) { if (defaultLabelOffset != null) { this.compileError("Duplicate \"default\" switch label", sbsg.getLocation()); } defaultLabelOffset = sbsgOffsets[i]; } } if (defaultLabelOffset == null) defaultLabelOffset = this.getWhereToBreak(ss); // Generate TABLESWITCH or LOOKUPSWITCH instruction. CodeContext.Offset switchOffset = this.codeContext.newOffset(); if (caseLabelMap.isEmpty()) { // Special case: SWITCH statement without CASE labels (but maybe a DEFAULT label). ; } else if ( (Integer) caseLabelMap.firstKey() + caseLabelMap.size() // Beware of INT overflow! >= (Integer) caseLabelMap.lastKey() - caseLabelMap.size() ) { // The case label values are strictly consecutity or almost consecutive (at most 50% 'gaps'), so // let's use a TABLESWITCH. final int low = (Integer) caseLabelMap.firstKey(); final int high = (Integer) caseLabelMap.lastKey(); this.writeOpcode(ss, Opcode.TABLESWITCH); new Padder(this.codeContext).set(); this.writeOffset(switchOffset, defaultLabelOffset); this.writeInt(low); this.writeInt(high); int cur = low; for (Map.Entry me : caseLabelMap.entrySet()) { int caseLabelValue = (Integer) me.getKey(); CodeContext.Offset caseLabelOffset = (CodeContext.Offset) me.getValue(); while (cur < caseLabelValue) { this.writeOffset(switchOffset, defaultLabelOffset); ++cur; } this.writeOffset(switchOffset, caseLabelOffset); ++cur; } } else { // The case label values are not 'consecutive enough', so use a LOOKUPSWOTCH. this.writeOpcode(ss, Opcode.LOOKUPSWITCH); new Padder(this.codeContext).set(); this.writeOffset(switchOffset, defaultLabelOffset); this.writeInt(caseLabelMap.size()); for (Map.Entry me : caseLabelMap.entrySet()) { this.writeInt((Integer) me.getKey()); this.writeOffset(switchOffset, (CodeContext.Offset) me.getValue()); } } // Compile statement groups. boolean canCompleteNormally = true; for (int i = 0; i < ss.sbsgs.size(); ++i) { SwitchStatement.SwitchBlockStatementGroup sbsg = ( (SwitchStatement.SwitchBlockStatementGroup) ss.sbsgs.get(i) ); sbsgOffsets[i].set(); canCompleteNormally = true; for (BlockStatement bs : sbsg.blockStatements) { if (!canCompleteNormally) { this.compileError("Statement is unreachable", bs.getLocation()); break; } canCompleteNormally = this.compile(bs); } } if (ss.whereToBreak == null) return canCompleteNormally; ss.whereToBreak.set(); ss.whereToBreak = null; return true; } private boolean compile2(BreakStatement bs) throws CompileException { // Find the broken statement. BreakableStatement brokenStatement = null; if (bs.optionalLabel == null) { for ( Scope s = bs.getEnclosingScope(); s instanceof Statement || s instanceof CatchClause; s = s.getEnclosingScope() ) { if (s instanceof BreakableStatement) { brokenStatement = (BreakableStatement) s; break; } } if (brokenStatement == null) { this.compileError("\"break\" statement is not enclosed by a breakable statement", bs.getLocation()); return false; } } else { for ( Scope s = bs.getEnclosingScope(); s instanceof Statement || s instanceof CatchClause; s = s.getEnclosingScope() ) { if (s instanceof LabeledStatement) { LabeledStatement ls = (LabeledStatement) s; if (ls.label.equals(bs.optionalLabel)) { brokenStatement = ls; break; } } } if (brokenStatement == null) { this.compileError(( "Statement \"break " + bs.optionalLabel + "\" is not enclosed by a breakable statement with label \"" + bs.optionalLabel + "\"" ), bs.getLocation()); return false; } } this.leaveStatements( bs.getEnclosingScope(), // from brokenStatement.getEnclosingScope(), // to null // optionalStackValueType ); this.writeBranch(bs, Opcode.GOTO, this.getWhereToBreak(brokenStatement)); return false; } private boolean compile2(ContinueStatement cs) throws CompileException { // Find the continued statement. ContinuableStatement continuedStatement = null; if (cs.optionalLabel == null) { for ( Scope s = cs.getEnclosingScope(); s instanceof Statement || s instanceof CatchClause; s = s.getEnclosingScope() ) { if (s instanceof ContinuableStatement) { continuedStatement = (ContinuableStatement) s; break; } } if (continuedStatement == null) { this.compileError( "\"continue\" statement is not enclosed by a continuable statement", cs.getLocation() ); return false; } } else { for ( Scope s = cs.getEnclosingScope(); s instanceof Statement || s instanceof CatchClause; s = s.getEnclosingScope() ) { if (s instanceof LabeledStatement) { LabeledStatement ls = (LabeledStatement) s; if (ls.label.equals(cs.optionalLabel)) { Statement st = ls.body; while (st instanceof LabeledStatement) st = ((LabeledStatement) st).body; if (!(st instanceof ContinuableStatement)) { this.compileError("Labeled statement is not continuable", st.getLocation()); return false; } continuedStatement = (ContinuableStatement) st; break; } } } if (continuedStatement == null) { this.compileError(( "Statement \"continue " + cs.optionalLabel + "\" is not enclosed by a continuable statement with label \"" + cs.optionalLabel + "\"" ), cs.getLocation()); return false; } } if (continuedStatement.whereToContinue == null) { continuedStatement.whereToContinue = this.codeContext.new Offset(); } this.leaveStatements( cs.getEnclosingScope(), // from continuedStatement.getEnclosingScope(), // to null // optionalStackValueType ); this.writeBranch(cs, Opcode.GOTO, continuedStatement.whereToContinue); return false; } private boolean compile2(AssertStatement as) throws CompileException { // assert expression1; // if (!expression1) throw new AssertionError(); // assert expression1 : expression2; // if (!expression1) throw new AssertionError(expression2); CodeContext.Offset end = this.codeContext.new Offset(); try { this.compileBoolean(as.expression1, end, UnitCompiler.JUMP_IF_TRUE); this.writeOpcode(as, Opcode.NEW); this.writeConstantClassInfo(Descriptor.JAVA_LANG_ASSERTIONERROR); this.writeOpcode(as, Opcode.DUP); Rvalue[] arguments = ( as.optionalExpression2 == null ? new Rvalue[0] : new Rvalue[] { as.optionalExpression2 } ); this.invokeConstructor( as, // locatable as, // scope null, // optionalEnclosingInstance this.iClassLoader.TYPE_java_lang_AssertionError, // targetClass arguments // arguments ); this.writeOpcode(as, Opcode.ATHROW); } finally { end.set(); } return true; } @SuppressWarnings("static-method") private boolean compile2(EmptyStatement es) { return true; } private boolean compile2(ExpressionStatement ee) throws CompileException { this.compile(ee.rvalue); return true; } private boolean compile2(FieldDeclaration fd) throws CompileException { for (VariableDeclarator vd : fd.variableDeclarators) { ArrayInitializerOrRvalue initializer = this.getNonConstantFinalInitializer(fd, vd); if (initializer == null) continue; assert fd.modifiers.annotations.length == 0; if (!Mod.isStatic(fd.modifiers.flags)) this.writeOpcode(fd, Opcode.ALOAD_0); IClass fieldType = this.getType(fd.type); if (initializer instanceof Rvalue) { Rvalue rvalue = (Rvalue) initializer; IClass initializerType = this.compileGetValue(rvalue); fieldType = fieldType.getArrayIClass(vd.brackets, this.iClassLoader.TYPE_java_lang_Object); this.assignmentConversion( fd, // locatable initializerType, // sourceType fieldType, // targetType this.getConstantValue(rvalue) // optionalConstantValue ); } else if (initializer instanceof ArrayInitializer) { this.compileGetValue((ArrayInitializer) initializer, fieldType); } else { throw new JaninoRuntimeException( "Unexpected array initializer or rvalue class " + initializer.getClass().getName() ); } // No need to check accessibility here. ; assert fd.modifiers.annotations.length == 0; this.putfield(fd, this.resolve(fd.getDeclaringType()).getDeclaredIField(vd.name)); } return true; } private boolean compile2(IfStatement is) throws CompileException { Object cv = this.getConstantValue(is.condition); BlockStatement es = ( is.optionalElseStatement != null ? is.optionalElseStatement : new EmptyStatement(is.thenStatement.getLocation()) ); if (cv instanceof Boolean) { // Constant condition. this.fakeCompile(is.condition); BlockStatement seeingStatement, blindStatement; if (((Boolean) cv).booleanValue()) { seeingStatement = is.thenStatement; blindStatement = es; } else { seeingStatement = es; blindStatement = is.thenStatement; } // Compile the seeing statement. final CodeContext.Inserter ins = this.codeContext.newInserter(); boolean ssccn = this.compile(seeingStatement); boolean bsccn = this.fakeCompile(blindStatement); if (ssccn) return true; if (!bsccn) return false; // Hm... the "seeing statement" cannot complete normally, but the "blind statement" can. Things are getting // complicated here! The robust solution is to compile the constant-condition-IF statement as a // non-constant-condition-IF statement. As an optimization, iff the IF-statement is enclosed ONLY by blocks, // then the remaining bytecode can be written to a "fake" code context, i.e. be thrown away. // Compile constant-condition-IF statement as non-constant-condition-IF statement. CodeContext.Offset off = this.codeContext.newOffset(); this.codeContext.pushInserter(ins); try { this.pushConstant(is, Boolean.FALSE); this.writeBranch(is, Opcode.IFNE, off); } finally { this.codeContext.popInserter(); } return true; } // Non-constant condition. if (this.generatesCode(is.thenStatement)) { if (this.generatesCode(es)) { // if (expression) statement else statement CodeContext.Offset eso = this.codeContext.new Offset(); CodeContext.Offset end = this.codeContext.new Offset(); this.compileBoolean(is.condition, eso, UnitCompiler.JUMP_IF_FALSE); boolean tsccn = this.compile(is.thenStatement); if (tsccn) this.writeBranch(is, Opcode.GOTO, end); eso.set(); boolean esccn = this.compile(es); end.set(); return tsccn || esccn; } else { // if (expression) statement else ; CodeContext.Offset end = this.codeContext.new Offset(); this.compileBoolean(is.condition, end, UnitCompiler.JUMP_IF_FALSE); this.compile(is.thenStatement); end.set(); return true; } } else { if (this.generatesCode(es)) { // if (expression) ; else statement CodeContext.Offset end = this.codeContext.new Offset(); this.compileBoolean(is.condition, end, UnitCompiler.JUMP_IF_TRUE); this.compile(es); end.set(); return true; } else { // if (expression) ; else ; IClass conditionType = this.compileGetValue(is.condition); if (conditionType != IClass.BOOLEAN) this.compileError("Not a boolean expression", is.getLocation()); this.pop(is, conditionType); return true; } } } private boolean compile2(LocalClassDeclarationStatement lcds) throws CompileException { // Check for redefinition. LocalClassDeclaration otherLcd = UnitCompiler.findLocalClassDeclaration(lcds, lcds.lcd.name); if (otherLcd != lcds.lcd) { this.compileError( "Redeclaration of local class \"" + lcds.lcd.name + "\"; previously declared in " + otherLcd.getLocation() ); } this.compile(lcds.lcd); return true; } /** Finds a local class declared in any block enclosing the given block statement. */ private static LocalClassDeclaration findLocalClassDeclaration(Scope s, String name) { if (s instanceof CompilationUnit) return null; for (;;) { Scope es = s.getEnclosingScope(); if (es instanceof CompilationUnit) break; if ( s instanceof BlockStatement && (es instanceof Block || es instanceof FunctionDeclarator) ) { BlockStatement bs = (BlockStatement) s; List statements = ( es instanceof BlockStatement ? ((Block) es).statements : ((FunctionDeclarator) es).optionalStatements ); for (BlockStatement bs2 : statements) { if (bs2 instanceof LocalClassDeclarationStatement) { LocalClassDeclarationStatement lcds = ((LocalClassDeclarationStatement) bs2); if (lcds.lcd.name.equals(name)) return lcds.lcd; } if (bs2 == bs) break; } } s = es; } return null; } private boolean compile2(LocalVariableDeclarationStatement lvds) throws CompileException { assert lvds.modifiers.annotations.length == 0; if ((lvds.modifiers.flags & ~Mod.FINAL) != 0) { this.compileError( "The only allowed modifier in local variable declarations is \"final\"", lvds.getLocation() ); } for (VariableDeclarator vd : lvds.variableDeclarators) { LocalVariable lv = this.getLocalVariable(lvds, vd); lv.setSlot( this.codeContext.allocateLocalVariable(Descriptor.size(lv.type.getDescriptor()), vd.name, lv.type) ); if (vd.optionalInitializer != null) { if (vd.optionalInitializer instanceof Rvalue) { Rvalue rhs = (Rvalue) vd.optionalInitializer; this.assignmentConversion( lvds, // locatable this.compileGetValue(rhs), // sourceType lv.type, // targetType this.getConstantValue(rhs) // optionalConstantValue ); } else if (vd.optionalInitializer instanceof ArrayInitializer) { this.compileGetValue((ArrayInitializer) vd.optionalInitializer, lv.type); } else { throw new JaninoRuntimeException( "Unexpected rvalue or array initialized class " + vd.optionalInitializer.getClass().getName() ); } this.store(lvds, lv); } } return true; } /** @return The {@link LocalVariable} corresponding with the local variable declaration/declarator */ public LocalVariable getLocalVariable(LocalVariableDeclarationStatement lvds, VariableDeclarator vd) throws CompileException { if (vd.localVariable == null) { // Determine variable type. Type variableType = lvds.type; for (int k = 0; k < vd.brackets; ++k) variableType = new ArrayType(variableType); assert lvds.modifiers.annotations.length == 0; vd.localVariable = new LocalVariable( Mod.isFinal(lvds.modifiers.flags), // finaL this.getType(variableType) // type ); } return vd.localVariable; } private boolean compile2(ReturnStatement rs) throws CompileException { // Determine enclosing block, function and compilation Unit. FunctionDeclarator enclosingFunction = null; { Scope s = rs.getEnclosingScope(); while (s instanceof Statement || s instanceof CatchClause) s = s.getEnclosingScope(); enclosingFunction = (FunctionDeclarator) s; } IClass returnType = this.getReturnType(enclosingFunction); if (returnType == IClass.VOID) { if (rs.optionalReturnValue != null) this.compileError("Method must not return a value", rs.getLocation()); this.leaveStatements( rs.getEnclosingScope(), // from enclosingFunction, // to null // optionalStackValueType ); this.writeOpcode(rs, Opcode.RETURN); return false; } if (rs.optionalReturnValue == null) { this.compileError("Method must return a value", rs.getLocation()); return false; } IClass type = this.compileGetValue(rs.optionalReturnValue); this.assignmentConversion( rs, // locatable type, // sourceType returnType, // targetType this.getConstantValue(rs.optionalReturnValue) // optionalConstantValue ); this.leaveStatements( rs.getEnclosingScope(), // from enclosingFunction, // to returnType // optionalStackValueType ); this.writeOpcode(rs, Opcode.IRETURN + UnitCompiler.ilfda(returnType)); return false; } private boolean compile2(SynchronizedStatement ss) throws CompileException { // Evaluate monitor object expression. if (!this.iClassLoader.TYPE_java_lang_Object.isAssignableFrom(this.compileGetValue(ss.expression))) { this.compileError( "Monitor object of \"synchronized\" statement is not a subclass of \"Object\"", ss.getLocation() ); } this.codeContext.saveLocalVariables(); boolean canCompleteNormally = false; try { // Allocate a local variable for the monitor object. ss.monitorLvIndex = this.codeContext.allocateLocalVariable((short) 1); // Store the monitor object. this.writeOpcode(ss, Opcode.DUP); this.store(ss, this.iClassLoader.TYPE_java_lang_Object, ss.monitorLvIndex); // Create lock on the monitor object. this.writeOpcode(ss, Opcode.MONITORENTER); // Compile the statement body. final CodeContext.Offset monitorExitOffset = this.codeContext.new Offset(); final CodeContext.Offset beginningOfBody = this.codeContext.newOffset(); canCompleteNormally = this.compile(ss.body); if (canCompleteNormally) { this.writeBranch(ss, Opcode.GOTO, monitorExitOffset); } // Generate the exception handler. CodeContext.Offset here = this.codeContext.newOffset(); this.codeContext.addExceptionTableEntry( beginningOfBody, // startPC here, // endPC here, // handlerPC null // catchTypeFD ); this.leave(ss, this.iClassLoader.TYPE_java_lang_Throwable); this.writeOpcode(ss, Opcode.ATHROW); // Unlock monitor object. if (canCompleteNormally) { monitorExitOffset.set(); this.leave(ss, null); } } finally { this.codeContext.restoreLocalVariables(); } return canCompleteNormally; } private boolean compile2(ThrowStatement ts) throws CompileException { IClass expressionType = this.compileGetValue(ts.expression); this.checkThrownException( ts, // locatable expressionType, // type ts.getEnclosingScope() // scope ); this.writeOpcode(ts, Opcode.ATHROW); return false; } private boolean compile2(TryStatement ts) throws CompileException { if (ts.optionalFinally != null) ts.finallyOffset = this.codeContext.new Offset(); final CodeContext.Offset beginningOfBody = this.codeContext.newOffset(); final CodeContext.Offset afterStatement = this.codeContext.new Offset(); this.codeContext.saveLocalVariables(); try { // Allocate a LV for the JSR of the FINALLY clause. // // Notice: // For unclear reasons, this variable must not overlap with any of the body's variables (although the // body's variables are out of scope when it comes to the FINALLY clause!?), otherwise you get // java.lang.VerifyError: ... Accessing value from uninitialized local variable 4 // See bug #56. final short pcLvIndex = ( ts.optionalFinally != null ? this.codeContext.allocateLocalVariable((short) 1) : (short) 0 ); // Initialize all catch clauses as "unreachable" only to check later that they ARE indeed reachable. for (CatchClause catchClause : ts.catchClauses) { IClass caughtExceptionType = this.getType(catchClause.caughtException.type); catchClause.reachable = ( // Superclass or subclass of "java.lang.Error"? this.iClassLoader.TYPE_java_lang_Error.isAssignableFrom(caughtExceptionType) || caughtExceptionType.isAssignableFrom(this.iClassLoader.TYPE_java_lang_Error) // Superclass or subclass of "java.lang.RuntimeException"? || this.iClassLoader.TYPE_java_lang_RuntimeException.isAssignableFrom(caughtExceptionType) || caughtExceptionType.isAssignableFrom(this.iClassLoader.TYPE_java_lang_RuntimeException) ); } boolean canCompleteNormally = this.compile(ts.body); CodeContext.Offset afterBody = this.codeContext.newOffset(); if (canCompleteNormally) { this.writeBranch(ts, Opcode.GOTO, afterStatement); } if (beginningOfBody.offset != afterBody.offset) { // Avoid zero-length exception table entries. this.codeContext.saveLocalVariables(); try { for (int i = 0; i < ts.catchClauses.size(); ++i) { try { this.codeContext.saveLocalVariables(); CatchClause catchClause = (CatchClause) ts.catchClauses.get(i); IClass caughtExceptionType = this.getType(catchClause.caughtException.type); // Verify that the CATCH clause is reachable. if (!catchClause.reachable) { this.compileError("Catch clause is unreachable", catchClause.getLocation()); } // Allocate the "exception variable". LocalVariableSlot exceptionVarSlot = this.codeContext.allocateLocalVariable( (short) 1, catchClause.caughtException.name, caughtExceptionType ); final short evi = exceptionVarSlot.getSlotIndex(); // Kludge: Treat the exception variable like a local variable of the catch clause body. this.getLocalVariable(catchClause.caughtException).setSlot(exceptionVarSlot); this.codeContext.addExceptionTableEntry( beginningOfBody, // startPC afterBody, // endPC this.codeContext.newOffset(), // handlerPC caughtExceptionType.getDescriptor() // catchTypeFD ); this.store( catchClause, // locatable caughtExceptionType, // lvType evi // lvIndex ); if (this.compile(catchClause.body)) { canCompleteNormally = true; if ( i < ts.catchClauses.size() - 1 || ts.optionalFinally != null ) this.writeBranch(catchClause, Opcode.GOTO, afterStatement); } } finally { this.codeContext.restoreLocalVariables(); } } } finally { this.codeContext.restoreLocalVariables(); } } if (ts.optionalFinally != null) { CodeContext.Offset here = this.codeContext.newOffset(); this.codeContext.addExceptionTableEntry( beginningOfBody, // startPC here, // endPC here, // handlerPC null // catchTypeFD ); this.codeContext.saveLocalVariables(); try { // Save the exception object in an anonymous local variable. short evi = this.codeContext.allocateLocalVariable((short) 1); this.store( ts.optionalFinally, // locatable this.iClassLoader.TYPE_java_lang_Object, // lvType evi // lvIndex ); this.writeBranch(ts.optionalFinally, Opcode.JSR, ts.finallyOffset); this.load( ts.optionalFinally, // locatable this.iClassLoader.TYPE_java_lang_Object, // type evi // index ); this.writeOpcode(ts.optionalFinally, Opcode.ATHROW); // Compile the "finally" body. ts.finallyOffset.set(); this.store( ts.optionalFinally, // locatable this.iClassLoader.TYPE_java_lang_Object, // lvType pcLvIndex // lvIndex ); if (this.compile(ts.optionalFinally)) { if (pcLvIndex > 255) { this.writeOpcode(ts.optionalFinally, Opcode.WIDE); this.writeOpcode(ts.optionalFinally, Opcode.RET); this.writeShort(pcLvIndex); } else { this.writeOpcode(ts.optionalFinally, Opcode.RET); this.writeByte(pcLvIndex); } } } finally { // The exception object local variable allocated above MUST NOT BE RELEASED until after the FINALLY // block is compiled, for otherwise you get // java.lang.VerifyError: ... Accessing value from uninitialized register 7 this.codeContext.restoreLocalVariables(); } } afterStatement.set(); if (canCompleteNormally) this.leave(ts, null); return canCompleteNormally; } finally { this.codeContext.restoreLocalVariables(); } } // ------------ FunctionDeclarator.compile() ------------- private void compile(FunctionDeclarator fd, final ClassFile classFile) throws CompileException { ClassFile.MethodInfo mi; if (Mod.isPrivateAccess(fd.modifiers.flags)) { if (fd instanceof MethodDeclarator && !fd.isStatic()) { // To make the non-static private method invocable for enclosing types, enclosed types and types // enclosed by the same type, it is modified as follows: // + Access is changed from PRIVATE to PACKAGE // + The name is appended with "$" // + It is made static // + A parameter of type "declaring class" is prepended to the signature short modifiers = Mod.changeAccess( fd.modifiers.flags, // modifiers Mod.PACKAGE // newAccess ); modifiers |= Mod.STATIC; mi = classFile.addMethodInfo( new Modifiers(modifiers, fd.modifiers.annotations), // modifiersAnd fd.name + '$', // methodName MethodDescriptor.prependParameter( // methodMD this.toIMethod((MethodDeclarator) fd).getDescriptor(), // md this.resolve(fd.getDeclaringType()).getDescriptor() // parameterFD ) ); } else { // To make the static private method or private constructor invocable for enclosing types, enclosed // types and types enclosed by the same type, it is modified as follows: // + Access is changed from PRIVATE to PACKAGE assert fd.modifiers.annotations.length == 0 : "NYI"; short modifiers = Mod.changeAccess(fd.modifiers.flags, Mod.PACKAGE); mi = classFile.addMethodInfo( new Modifiers(modifiers, fd.modifiers.annotations), // modifiers fd.name, // methodName this.toIInvocable(fd).getDescriptor() // methodMD ); } } else { // Non-PRIVATE function. mi = classFile.addMethodInfo( fd.modifiers, // modifiers fd.name, // methodName this.toIInvocable(fd).getDescriptor() // methodMD ); } // Add "Exceptions" attribute (JVMS 4.7.4). { if (fd.thrownExceptions.length > 0) { final short eani = classFile.addConstantUtf8Info("Exceptions"); short[] tecciis = new short[fd.thrownExceptions.length]; for (int i = 0; i < fd.thrownExceptions.length; ++i) { tecciis[i] = classFile.addConstantClassInfo(this.getType(fd.thrownExceptions[i]).getDescriptor()); } mi.addAttribute(new ClassFile.ExceptionsAttribute(eani, tecciis)); } } // Add "Deprecated" attribute (JVMS 4.7.10) if (fd.hasDeprecatedDocTag()) { mi.addAttribute(new ClassFile.DeprecatedAttribute(classFile.addConstantUtf8Info("Deprecated"))); } if (Mod.isAbstract(fd.modifiers.flags) || Mod.isNative(fd.modifiers.flags)) return; // Create CodeContext. final CodeContext codeContext = new CodeContext(mi.getClassFile(), mi.getDescriptor()); CodeContext savedCodeContext = this.replaceCodeContext(codeContext); try { this.codeContext.saveLocalVariables(); // Define special parameter "this". if (!Mod.isStatic(fd.modifiers.flags)) { this.codeContext.allocateLocalVariable((short) 1, "this", this.resolve(fd.getDeclaringType())); } if (fd instanceof ConstructorDeclarator) { ConstructorDeclarator constructorDeclarator = (ConstructorDeclarator) fd; // Reserve space for synthetic parameters ("this$...", "val$..."). for (IField sf : constructorDeclarator.getDeclaringClass().syntheticFields.values()) { LocalVariable lv = new LocalVariable(true, sf.getType()); lv.setSlot(this.codeContext.allocateLocalVariable(Descriptor.size(sf.getDescriptor()), null, null)); constructorDeclarator.syntheticParameters.put(sf.getName(), lv); } } this.buildLocalVariableMap(fd); // Compile the constructor preamble. if (fd instanceof ConstructorDeclarator) { ConstructorDeclarator cd = (ConstructorDeclarator) fd; if (cd.optionalConstructorInvocation != null) { this.compile(cd.optionalConstructorInvocation); if (cd.optionalConstructorInvocation instanceof SuperConstructorInvocation) { this.assignSyntheticParametersToSyntheticFields(cd); this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd); } } else { // Determine qualification for superconstructor invocation. IClass outerClassOfSuperclass = this.resolve( cd.getDeclaringClass() ).getSuperclass().getOuterIClass(); QualifiedThisReference qualification = null; if (outerClassOfSuperclass != null) { qualification = new QualifiedThisReference( cd.getLocation(), // location new SimpleType(cd.getLocation(), outerClassOfSuperclass) // qualification ); } // Invoke the superconstructor. SuperConstructorInvocation sci = new SuperConstructorInvocation( cd.getLocation(), // location qualification, // optionalQualification new Rvalue[0] // arguments ); sci.setEnclosingScope(fd); this.compile(sci); this.assignSyntheticParametersToSyntheticFields(cd); this.initializeInstanceVariablesAndInvokeInstanceInitializers(cd); } } // Compile the function body. if (fd.optionalStatements == null) { this.compileError("Method must have a body", fd.getLocation()); return; } if (this.compileStatements(fd.optionalStatements)) { if (this.getReturnType(fd) != IClass.VOID) { this.compileError("Method must return a value", fd.getLocation()); } this.writeOpcode(fd, Opcode.RETURN); } } finally { this.codeContext.restoreLocalVariables(); this.replaceCodeContext(savedCodeContext); } // Don't continue code attribute generation if we had compile errors. if (this.compileErrorCount > 0) return; // Fix up and reallocate as needed. codeContext.fixUpAndRelocate(); // Do flow analysis. if (UnitCompiler.DEBUG) { try { codeContext.flowAnalysis(fd.toString()); } catch (RuntimeException ex) { ex.printStackTrace(); ; } } else { codeContext.flowAnalysis(fd.toString()); } final short lntani; if (this.debugLines) { lntani = classFile.addConstantUtf8Info("LineNumberTable"); } else { lntani = 0; } final short lvtani; if (this.debugVars) { UnitCompiler.makeLocalVariableNames(codeContext, mi); lvtani = classFile.addConstantUtf8Info("LocalVariableTable"); } else { lvtani = 0; } // Add the code context as a code attribute to the MethodInfo. mi.addAttribute(new ClassFile.AttributeInfo(classFile.addConstantUtf8Info("Code")) { @Override protected void storeBody(DataOutputStream dos) throws IOException { codeContext.storeCodeAttributeBody(dos, lntani, lvtani); } }); } /** Makes the variable name and class name Constant Pool names used by local variables. */ private static void makeLocalVariableNames(final CodeContext cc, final ClassFile.MethodInfo mi) { ClassFile cf = mi.getClassFile(); cf.addConstantUtf8Info("LocalVariableTable"); for (LocalVariableSlot slot : cc.getAllLocalVars()) { if (slot.getName() != null) { String typeName = slot.getType().getDescriptor(); cf.addConstantUtf8Info(typeName); cf.addConstantUtf8Info(slot.getName()); } } } private void buildLocalVariableMap(FunctionDeclarator fd) throws CompileException { Map localVars = new HashMap(); // Add function parameters. for (int i = 0; i < fd.formalParameters.parameters.length; ++i) { FormalParameter fp = fd.formalParameters.parameters[i]; IClass parameterIClass = this.getType(fp.type); LocalVariable lv = this.getLocalVariable( fp, i == fd.formalParameters.parameters.length - 1 && fd.formalParameters.variableArity ); lv.setSlot(this.codeContext.allocateLocalVariable( Descriptor.size(lv.type.getDescriptor()), fp.name, parameterIClass )); if (localVars.put(fp.name, lv) != null) { this.compileError("Redefinition of parameter \"" + fp.name + "\"", fd.getLocation()); } } fd.localVariables = localVars; if (fd instanceof ConstructorDeclarator) { ConstructorDeclarator cd = (ConstructorDeclarator) fd; if (cd.optionalConstructorInvocation != null) { UnitCompiler.buildLocalVariableMap(cd.optionalConstructorInvocation, localVars); } } if (fd.optionalStatements != null) { for (BlockStatement bs : fd.optionalStatements) localVars = this.buildLocalVariableMap(bs, localVars); } } /** Computes and fills in the 'local variable map' for the given {@code blockStatement}. */ private Map buildLocalVariableMap(BlockStatement blockStatement, final Map localVars) throws CompileException { final Map[] resVars = new Map[] { localVars }; BlockStatementVisitor bsv = new BlockStatementVisitor() { // CHECKSTYLE LineLengthCheck:OFF // Basic statements that use the default handlers. @Override public void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { UnitCompiler.buildLocalVariableMap(aci, localVars); } @Override public void visitBreakStatement(BreakStatement bs) { UnitCompiler.buildLocalVariableMap(bs, localVars); } @Override public void visitContinueStatement(ContinueStatement cs) { UnitCompiler.buildLocalVariableMap(cs, localVars); } @Override public void visitAssertStatement(AssertStatement as) { UnitCompiler.buildLocalVariableMap(as, localVars); } @Override public void visitEmptyStatement(EmptyStatement es) { UnitCompiler.buildLocalVariableMap(es, localVars); } @Override public void visitExpressionStatement(ExpressionStatement es) { UnitCompiler.buildLocalVariableMap(es, localVars); } @Override public void visitFieldDeclaration(FieldDeclaration fd) { UnitCompiler.buildLocalVariableMap(fd, localVars); } @Override public void visitReturnStatement(ReturnStatement rs) { UnitCompiler.buildLocalVariableMap(rs, localVars); } @Override public void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { UnitCompiler.buildLocalVariableMap(sci, localVars); } @Override public void visitThrowStatement(ThrowStatement ts) { UnitCompiler.buildLocalVariableMap(ts, localVars); } @Override public void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { UnitCompiler.buildLocalVariableMap(lcds, localVars); } // More complicated statements with specialized handlers, but don't add new variables in this scope. @Override public void visitBlock(Block b) { try { UnitCompiler.this.buildLocalVariableMap(b, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitDoStatement(DoStatement ds) { try { UnitCompiler.this.buildLocalVariableMap(ds, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitForStatement(ForStatement fs) { try { UnitCompiler.this.buildLocalVariableMap(fs, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitForEachStatement(ForEachStatement fes) { try { UnitCompiler.this.buildLocalVariableMap(fes, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitIfStatement(IfStatement is) { try { UnitCompiler.this.buildLocalVariableMap(is, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitInitializer(Initializer i) { try { UnitCompiler.this.buildLocalVariableMap(i, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSwitchStatement(SwitchStatement ss) { try { UnitCompiler.this.buildLocalVariableMap(ss, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSynchronizedStatement(SynchronizedStatement ss) { try { UnitCompiler.this.buildLocalVariableMap(ss, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitTryStatement(TryStatement ts) { try { UnitCompiler.this.buildLocalVariableMap(ts, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitWhileStatement(WhileStatement ws) { try { UnitCompiler.this.buildLocalVariableMap(ws, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // More complicated statements with specialized handlers, that can add variables in this scope. @Override public void visitLabeledStatement(LabeledStatement ls) { try { resVars[0] = UnitCompiler.this.buildLocalVariableMap(ls, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { try { resVars[0] = UnitCompiler.this.buildLocalVariableMap(lvds, localVars); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { blockStatement.accept(bsv); } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } return resVars[0]; } // Default handlers. private static Map buildLocalVariableMap(Statement s, final Map localVars) { return (s.localVariables = localVars); } private static Map buildLocalVariableMap(ConstructorInvocation ci, final Map localVars) { return (ci.localVariables = localVars); } // Specialized handlers. private void buildLocalVariableMap(Block block, Map localVars) throws CompileException { block.localVariables = localVars; for (BlockStatement bs : block.statements) localVars = this.buildLocalVariableMap(bs, localVars); } private void buildLocalVariableMap(DoStatement ds, final Map localVars) throws CompileException { ds.localVariables = localVars; this.buildLocalVariableMap(ds.body, localVars); } private void buildLocalVariableMap(ForStatement fs, final Map localVars) throws CompileException { Map inner = localVars; if (fs.optionalInit != null) { inner = this.buildLocalVariableMap(fs.optionalInit, localVars); } fs.localVariables = inner; this.buildLocalVariableMap(fs.body, inner); } private void buildLocalVariableMap(ForEachStatement fes, final Map localVars) throws CompileException { Map vars = new HashMap(); vars.putAll(localVars); LocalVariable elementLv = this.getLocalVariable(fes.currentElement, false); vars.put(fes.currentElement.name, elementLv); fes.localVariables = vars; this.buildLocalVariableMap(fes.body, vars); } private void buildLocalVariableMap(IfStatement is, final Map localVars) throws CompileException { is.localVariables = localVars; this.buildLocalVariableMap(is.thenStatement, localVars); if (is.optionalElseStatement != null) { this.buildLocalVariableMap(is.optionalElseStatement, localVars); } } private void buildLocalVariableMap(Initializer i, final Map localVars) throws CompileException { this.buildLocalVariableMap(i.block, localVars); } private void buildLocalVariableMap(SwitchStatement ss, final Map localVars) throws CompileException { ss.localVariables = localVars; Map vars = localVars; for (SwitchStatement.SwitchBlockStatementGroup sbsg : ss.sbsgs) { for (BlockStatement bs : sbsg.blockStatements) vars = this.buildLocalVariableMap(bs, vars); } } private void buildLocalVariableMap(SynchronizedStatement ss, final Map localVars) throws CompileException { ss.localVariables = localVars; this.buildLocalVariableMap(ss.body, localVars); } private void buildLocalVariableMap(TryStatement ts, final Map localVars) throws CompileException { ts.localVariables = localVars; this.buildLocalVariableMap(ts.body, localVars); for (CatchClause cc : ts.catchClauses) this.buildLocalVariableMap(cc, localVars); if (ts.optionalFinally != null) { this.buildLocalVariableMap(ts.optionalFinally, localVars); } } private void buildLocalVariableMap(WhileStatement ws, final Map localVars) throws CompileException { ws.localVariables = localVars; this.buildLocalVariableMap(ws.body, localVars); } private Map buildLocalVariableMap(LabeledStatement ls, final Map localVars) throws CompileException { ls.localVariables = localVars; return this.buildLocalVariableMap((BlockStatement) ls.body, localVars); } private Map buildLocalVariableMap(LocalVariableDeclarationStatement lvds, final Map localVars) throws CompileException { Map newVars = new HashMap(); newVars.putAll(localVars); for (VariableDeclarator vd : lvds.variableDeclarators) { LocalVariable lv = this.getLocalVariable(lvds, vd); if (newVars.put(vd.name, lv) != null) { this.compileError("Redefinition of local variable \"" + vd.name + "\" ", vd.getLocation()); } } lvds.localVariables = newVars; return newVars; } /** Adds the given {@code localVars} to the 'local variable map' of the given {@code catchClause}. */ protected void buildLocalVariableMap(CatchClause catchClause, Map localVars) throws CompileException { Map vars = new HashMap(); vars.putAll(localVars); LocalVariable lv = this.getLocalVariable(catchClause.caughtException); vars.put(catchClause.caughtException.name, lv); this.buildLocalVariableMap(catchClause.body, vars); } /** @return The {@link LocalVariable} corresponding with the {@code parameter} */ public LocalVariable getLocalVariable(FormalParameter parameter) throws CompileException { return this.getLocalVariable(parameter, false); } /** * @param isVariableArityParameter Whether the {@code parameter} is the last parameter of a 'variable arity' * (a.k.a. 'varargs') method declaration * @return The {@link LocalVariable} corresponding with the {@code parameter} */ public LocalVariable getLocalVariable(FormalParameter parameter, boolean isVariableArityParameter) throws CompileException { if (parameter.localVariable == null) { assert parameter.type != null; IClass parameterType = this.getType(parameter.type); if (isVariableArityParameter) { parameterType = parameterType.getArrayIClass(this.iClassLoader.TYPE_java_lang_Object); } parameter.localVariable = new LocalVariable(parameter.finaL, parameterType); } return parameter.localVariable; } // ------------------ Rvalue.compile() ---------------- /** Called to check whether the given {@link Rvalue} compiles or not. */ private void fakeCompile(Rvalue rv) throws CompileException { final Offset from = this.codeContext.newOffset(); this.compileContext(rv); this.compileGet(rv); Offset to = this.codeContext.newOffset(); this.codeContext.removeCode(from, to); } /** Some {@link Rvalue}s compile more efficiently when their value is not needed, e.g. "i++". */ private void compile(Rvalue rv) throws CompileException { RvalueVisitor rvv = new RvalueVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitArrayLength(ArrayLength al) { try { UnitCompiler.this.compile2(al); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAssignment(Assignment a) { try { UnitCompiler.this.compile2(a); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitUnaryOperation(UnaryOperation uo) { try { UnitCompiler.this.compile2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBinaryOperation(BinaryOperation bo) { try { UnitCompiler.this.compile2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCast(Cast c) { try { UnitCompiler.this.compile2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitClassLiteral(ClassLiteral cl) { try { UnitCompiler.this.compile2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitConditionalExpression(ConditionalExpression ce) { try { UnitCompiler.this.compile2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCrement(Crement c) { try { UnitCompiler.this.compile2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitInstanceof(Instanceof io) { try { UnitCompiler.this.compile2(io); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitMethodInvocation(MethodInvocation mi) { try { UnitCompiler.this.compile2(mi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { UnitCompiler.this.compile2(smi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitIntegerLiteral(IntegerLiteral il) { try { UnitCompiler.this.compile2(il); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { UnitCompiler.this.compile2(fpl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBooleanLiteral(BooleanLiteral bl) { try { UnitCompiler.this.compile2(bl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCharacterLiteral(CharacterLiteral cl) { try { UnitCompiler.this.compile2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitStringLiteral(StringLiteral sl) { try { UnitCompiler.this.compile2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNullLiteral(NullLiteral nl) { try { UnitCompiler.this.compile2(nl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSimpleConstant(SimpleConstant sl) { try { UnitCompiler.this.compile2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { try { UnitCompiler.this.compile2(naci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewArray(NewArray na) { try { UnitCompiler.this.compile2(na); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewInitializedArray(NewInitializedArray nia) { try { UnitCompiler.this.compile2(nia); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewClassInstance(NewClassInstance nci) { try { UnitCompiler.this.compile2(nci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParameterAccess(ParameterAccess pa) { try { UnitCompiler.this.compile2(pa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { UnitCompiler.this.compile2(qtr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitThisReference(ThisReference tr) { try { UnitCompiler.this.compile2(tr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAmbiguousName(AmbiguousName an) { try { UnitCompiler.this.compile2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { UnitCompiler.this.compile2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccess(FieldAccess fa) { try { UnitCompiler.this.compile2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { UnitCompiler.this.compile2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compile2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { try { UnitCompiler.this.compile2(lva); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { UnitCompiler.this.compile2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { rv.accept(rvv); } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } private void compile2(Rvalue rv) throws CompileException { this.pop(rv, this.compileGetValue(rv)); } private void compile2(Assignment a) throws CompileException { if (a.operator == "=") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.compileContext(a.lhs); this.assignmentConversion( a, // locatable this.compileGetValue(a.rhs), // sourceType this.getType(a.lhs), // targetType this.getConstantValue(a.rhs) // optionalConstantValue ); this.compileSet(a.lhs); return; } // Implement "|= ^= &= *= /= %= += -= <<= >>= >>>=". int lhsCs = this.compileContext(a.lhs); this.dup(a, lhsCs); IClass lhsType = this.compileGet(a.lhs); IClass resultType = this.compileArithmeticBinaryOperation( a, // locatable lhsType, // lhsType a.operator.substring( // operator 0, a.operator.length() - 1 ).intern(), /* <= IMPORTANT! */ a.rhs // rhs ); // Convert the result to LHS type (JLS7 15.26.2). if ( !this.tryIdentityConversion(resultType, lhsType) && !this.tryNarrowingPrimitiveConversion(a, resultType, lhsType) && !this.tryBoxingConversion(a, resultType, lhsType) // Java 5 ) this.compileError("Operand types unsuitable for '" + a.operator + "'", a.getLocation()); // Assign the result to the left operand. this.compileSet(a.lhs); } private void compile2(Crement c) throws CompileException { // Optimized crement of integer local variable. LocalVariable lv = this.isIntLv(c); if (lv != null) { this.compileLocalVariableCrement(c, lv); return; } int cs = this.compileContext(c.operand); this.dup(c, cs); IClass type = this.compileGet(c.operand); IClass promotedType = this.unaryNumericPromotion(c, type); this.writeOpcode(c, UnitCompiler.ilfd( promotedType, Opcode.ICONST_1, Opcode.LCONST_1, Opcode.FCONST_1, Opcode.DCONST_1 )); if (c.operator == "++") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.writeOpcode(c, Opcode.IADD + UnitCompiler.ilfd(promotedType)); } else if (c.operator == "--") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.writeOpcode(c, Opcode.ISUB + UnitCompiler.ilfd(promotedType)); } else { this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation()); } this.reverseUnaryNumericPromotion(c, promotedType, type); this.compileSet(c.operand); } private void compile2(ParenthesizedExpression pe) throws CompileException { this.compile(pe.value); } private boolean compile2(AlternateConstructorInvocation aci) throws CompileException { ConstructorDeclarator declaringConstructor = (ConstructorDeclarator) aci.getEnclosingScope(); IClass declaringIClass = this.resolve(declaringConstructor.getDeclaringClass()); this.writeOpcode(aci, Opcode.ALOAD_0); if (declaringIClass.getOuterIClass() != null) this.writeOpcode(aci, Opcode.ALOAD_1); this.invokeConstructor( aci, // locatable declaringConstructor, // scope (Rvalue) null, // optionalEnclosingInstance declaringIClass, // targetClass aci.arguments // arguments ); return true; } private boolean compile2(SuperConstructorInvocation sci) throws CompileException { ConstructorDeclarator declaringConstructor = (ConstructorDeclarator) sci.getEnclosingScope(); this.writeOpcode(sci, Opcode.ALOAD_0); ClassDeclaration declaringClass = declaringConstructor.getDeclaringClass(); IClass superclass = this.resolve(declaringClass).getSuperclass(); Rvalue optionalEnclosingInstance; if (sci.optionalQualification != null) { optionalEnclosingInstance = sci.optionalQualification; } else { IClass outerIClassOfSuperclass = superclass.getOuterIClass(); if (outerIClassOfSuperclass == null) { optionalEnclosingInstance = null; } else { optionalEnclosingInstance = new QualifiedThisReference( sci.getLocation(), // location new SimpleType(sci.getLocation(), outerIClassOfSuperclass) // qualification ); optionalEnclosingInstance.setEnclosingBlockStatement(sci); } } this.invokeConstructor( sci, // locatable declaringConstructor, // scope optionalEnclosingInstance, // optionalEnclosingInstance superclass, // targetClass sci.arguments // arguments ); return true; } /** * Some {@link Rvalue}s compile more efficiently when their value is the condition for a branch. *

* Notice that if "this" is a constant, then either {@code dst} is never branched to, or it is unconditionally * branched to. "Unexamined code" errors may result during bytecode validation. * * @param dst Where to jump * @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE} */ private void compileBoolean(Rvalue rv, final CodeContext.Offset dst, final boolean orientation) throws CompileException { RvalueVisitor rvv = new RvalueVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitArrayLength(ArrayLength al) { try { UnitCompiler.this.compileBoolean2(al, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAssignment(Assignment a) { try { UnitCompiler.this.compileBoolean2(a, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitUnaryOperation(UnaryOperation uo) { try { UnitCompiler.this.compileBoolean2(uo, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBinaryOperation(BinaryOperation bo) { try { UnitCompiler.this.compileBoolean2(bo, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCast(Cast c) { try { UnitCompiler.this.compileBoolean2(c, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitClassLiteral(ClassLiteral cl) { try { UnitCompiler.this.compileBoolean2(cl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitConditionalExpression(ConditionalExpression ce) { try { UnitCompiler.this.compileBoolean2(ce, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCrement(Crement c) { try { UnitCompiler.this.compileBoolean2(c, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitInstanceof(Instanceof io) { try { UnitCompiler.this.compileBoolean2(io, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitMethodInvocation(MethodInvocation mi) { try { UnitCompiler.this.compileBoolean2(mi, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { UnitCompiler.this.compileBoolean2(smi, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitIntegerLiteral(IntegerLiteral il) { try { UnitCompiler.this.compileBoolean2(il, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { UnitCompiler.this.compileBoolean2(fpl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBooleanLiteral(BooleanLiteral bl) { try { UnitCompiler.this.compileBoolean2(bl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCharacterLiteral(CharacterLiteral cl) { try { UnitCompiler.this.compileBoolean2(cl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitStringLiteral(StringLiteral sl) { try { UnitCompiler.this.compileBoolean2(sl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNullLiteral(NullLiteral nl) { try { UnitCompiler.this.compileBoolean2(nl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSimpleConstant(SimpleConstant sl) { try { UnitCompiler.this.compileBoolean2(sl, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { try { UnitCompiler.this.compileBoolean2(naci, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewArray(NewArray na) { try { UnitCompiler.this.compileBoolean2(na, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewInitializedArray(NewInitializedArray nia) { try { UnitCompiler.this.compileBoolean2(nia, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewClassInstance(NewClassInstance nci) { try { UnitCompiler.this.compileBoolean2(nci, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParameterAccess(ParameterAccess pa) { try { UnitCompiler.this.compileBoolean2(pa, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { UnitCompiler.this.compileBoolean2(qtr, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitThisReference(ThisReference tr) { try { UnitCompiler.this.compileBoolean2(tr, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAmbiguousName(AmbiguousName an) { try { UnitCompiler.this.compileBoolean2(an, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { UnitCompiler.this.compileBoolean2(aae, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccess(FieldAccess fa) { try { UnitCompiler.this.compileBoolean2(fa, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { UnitCompiler.this.compileBoolean2(fae, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compileBoolean2(scfae, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { try { UnitCompiler.this.compileBoolean2(lva, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { UnitCompiler.this.compileBoolean2(pe, dst, orientation); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { rv.accept(rvv); } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } /** * @param dst Where to jump * @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE} */ private void compileBoolean2(Rvalue rv, CodeContext.Offset dst, boolean orientation) throws CompileException { IClass type = this.compileGetValue(rv); IClassLoader icl = this.iClassLoader; if (type == icl.TYPE_java_lang_Boolean) { this.unboxingConversion(rv, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN); } else if (type != IClass.BOOLEAN) { this.compileError("Not a boolean expression", rv.getLocation()); } this.writeBranch(rv, orientation == UnitCompiler.JUMP_IF_TRUE ? Opcode.IFNE : Opcode.IFEQ, dst); } /** * @param dst Where to jump * @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE} */ private void compileBoolean2(UnaryOperation ue, CodeContext.Offset dst, boolean orientation) throws CompileException { if (ue.operator == "!") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.compileBoolean(ue.operand, dst, !orientation); return; } this.compileError("Boolean expression expected", ue.getLocation()); } /** * @param dst Where to jump * @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE} */ private void compileBoolean2(BinaryOperation bo, CodeContext.Offset dst, boolean orientation) throws CompileException { if (bo.op == "|" || bo.op == "^" || bo.op == "&") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.compileBoolean2((Rvalue) bo, dst, orientation); return; } if (bo.op == "||" || bo.op == "&&") { // SUPPRESS CHECKSTYLE StringLiteralEquality Object lhsCv = this.getConstantValue(bo.lhs); if (lhsCv instanceof Boolean) { if (((Boolean) lhsCv).booleanValue() ^ bo.op == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality // "true && a", "false || a" this.compileBoolean( bo.rhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE ); } else { // "false && a", "true || a" this.compileBoolean( bo.lhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE ); this.fakeCompile(bo.rhs); } return; } Object rhsCv = this.getConstantValue(bo.rhs); if (rhsCv instanceof Boolean) { if (((Boolean) rhsCv).booleanValue() ^ bo.op == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality // "a && true", "a || false" this.compileBoolean( bo.lhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE ); } else { // "a && false", "a || true" this.pop(bo.lhs, this.compileGetValue(bo.lhs)); this.compileBoolean( bo.rhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE ); } return; } // SUPPRESS CHECKSTYLE StringLiteralEquality if (bo.op == "||" ^ orientation == UnitCompiler.JUMP_IF_FALSE) { this.compileBoolean(bo.lhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE); this.compileBoolean(bo.rhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE); } else { CodeContext.Offset end = this.codeContext.new Offset(); this.compileBoolean(bo.lhs, end, UnitCompiler.JUMP_IF_FALSE ^ orientation == UnitCompiler.JUMP_IF_FALSE); // SUPPRESS CHECKSTYLE LineLength this.compileBoolean(bo.rhs, dst, UnitCompiler.JUMP_IF_TRUE ^ orientation == UnitCompiler.JUMP_IF_FALSE); end.set(); } return; } if ( bo.op == "==" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "!=" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "<=" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == ">=" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "<" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == ">" // SUPPRESS CHECKSTYLE StringLiteralEquality ) { int opIdx = ( bo.op == "==" ? 0 : // SUPPRESS CHECKSTYLE StringLiteralEquality bo.op == "!=" ? 1 : // SUPPRESS CHECKSTYLE StringLiteralEquality bo.op == "<" ? 2 : // SUPPRESS CHECKSTYLE StringLiteralEquality bo.op == ">=" ? 3 : // SUPPRESS CHECKSTYLE StringLiteralEquality bo.op == ">" ? 4 : // SUPPRESS CHECKSTYLE StringLiteralEquality bo.op == "<=" ? 5 : // SUPPRESS CHECKSTYLE StringLiteralEquality Integer.MIN_VALUE ); if (orientation == UnitCompiler.JUMP_IF_FALSE) opIdx ^= 1; // Comparison with "null". { boolean lhsIsNull = this.getConstantValue(bo.lhs) == null; boolean rhsIsNull = this.getConstantValue(bo.rhs) == null; if (lhsIsNull || rhsIsNull) { if (bo.op != "==" && bo.op != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.compileError( "Operator \"" + bo.op + "\" not allowed on operand \"null\"", bo.getLocation() ); } if (!lhsIsNull) { // x == null IClass lhsType = this.compileGetValue(bo.lhs); if (lhsType.isPrimitive()) { this.compileError( "Cannot compare primitive type \"" + lhsType.toString() + "\" with \"null\"", bo.getLocation() ); } } else if (!rhsIsNull) { // null == x IClass rhsType = this.compileGetValue(bo.rhs); if (rhsType.isPrimitive()) { this.compileError( "Cannot compare \"null\" with primitive type \"" + rhsType.toString() + "\"", bo.getLocation() ); } } else { // null == null this.pushConstant(bo, null); } this.writeBranch(bo, Opcode.IFNULL + opIdx, dst); return; } } IClass lhsType = this.compileGetValue(bo.lhs); CodeContext.Inserter convertLhsInserter = this.codeContext.newInserter(); IClass rhsType = this.compileGetValue(bo.rhs); // 15.20.1 Numerical comparison. if ( this.getUnboxedType(lhsType).isPrimitiveNumeric() && this.getUnboxedType(rhsType).isPrimitiveNumeric() && !( (bo.op == "==" || bo.op == "!=") // SUPPRESS CHECKSTYLE StringLiteralEquality && !lhsType.isPrimitive() && !rhsType.isPrimitive() ) ) { IClass promotedType = this.binaryNumericPromotion(bo, lhsType, convertLhsInserter, rhsType); if (promotedType == IClass.INT) { this.writeBranch(bo, Opcode.IF_ICMPEQ + opIdx, dst); } else if (promotedType == IClass.LONG) { this.writeOpcode(bo, Opcode.LCMP); this.writeBranch(bo, Opcode.IFEQ + opIdx, dst); } else if (promotedType == IClass.FLOAT) { if (bo.op == ">" || bo.op == ">=") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.writeOpcode(bo, Opcode.FCMPL); } else { this.writeOpcode(bo, Opcode.FCMPG); } this.writeBranch(bo, Opcode.IFEQ + opIdx, dst); } else if (promotedType == IClass.DOUBLE) { if (bo.op == ">" || bo.op == ">=") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.writeOpcode(bo, Opcode.DCMPL); } else { this.writeOpcode(bo, Opcode.DCMPG); } this.writeBranch(bo, Opcode.IFEQ + opIdx, dst); } else { throw new JaninoRuntimeException("Unexpected promoted type \"" + promotedType + "\""); } return; } // JLS7 15.21.2 Boolean Equality Operators == and !=. if ( (lhsType == IClass.BOOLEAN && this.getUnboxedType(rhsType) == IClass.BOOLEAN) || (rhsType == IClass.BOOLEAN && this.getUnboxedType(lhsType) == IClass.BOOLEAN) ) { if (bo.op != "==" && bo.op != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.compileError("Operator \"" + bo.op + "\" not allowed on boolean operands", bo.getLocation()); } IClassLoader icl = this.iClassLoader; // Unbox LHS if necessary. if (lhsType == icl.TYPE_java_lang_Boolean) { this.codeContext.pushInserter(convertLhsInserter); try { this.unboxingConversion(bo, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN); } finally { this.codeContext.popInserter(); } } // Unbox RHS if necessary. if (rhsType == icl.TYPE_java_lang_Boolean) { this.unboxingConversion(bo, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN); } this.writeBranch(bo, Opcode.IF_ICMPEQ + opIdx, dst); return; } // Reference comparison. // Note: Comparison with "null" is already handled above. if (!lhsType.isPrimitive() && !rhsType.isPrimitive()) { if (bo.op != "==" && bo.op != "!=") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.compileError("Operator \"" + bo.op + "\" not allowed on reference operands", bo.getLocation()); } if ( !this.isCastReferenceConvertible(lhsType, rhsType) || !this.isCastReferenceConvertible(rhsType, lhsType) ) this.compileError("Incomparable types '" + lhsType + "' and '" + rhsType + "'", bo.getLocation()); this.writeBranch(bo, Opcode.IF_ACMPEQ + opIdx, dst); return; } this.compileError("Cannot compare types \"" + lhsType + "\" and \"" + rhsType + "\"", bo.getLocation()); } this.compileError("Boolean expression expected", bo.getLocation()); } /** * @param dst Where to jump * @param orientation {@link #JUMP_IF_TRUE} or {@link #JUMP_IF_FALSE} */ private void compileBoolean2(ParenthesizedExpression pe, CodeContext.Offset dst, boolean orientation) throws CompileException { this.compileBoolean(pe.value, dst, orientation); } /** * Generates code that determines the context of the {@link Rvalue} and puts it on the operand stack. Most * expressions do not have a "context", but some do. E.g. for "x[y]", the context is "x, y". The bottom line is * that for statements like "x[y] += 3" the context is only evaluated once. * * @return The size of the context on the operand stack */ private int compileContext(Rvalue rv) throws CompileException { final int[] res = new int[1]; RvalueVisitor rvv = new RvalueVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitArrayLength(ArrayLength al) { try { res[0] = UnitCompiler.this.compileContext2(al); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAssignment(Assignment a) { res[0] = UnitCompiler.this.compileContext2(a); } @Override public void visitUnaryOperation(UnaryOperation uo) { res[0] = UnitCompiler.this.compileContext2(uo); } @Override public void visitBinaryOperation(BinaryOperation bo) { res[0] = UnitCompiler.this.compileContext2(bo); } @Override public void visitCast(Cast c) { res[0] = UnitCompiler.this.compileContext2(c); } @Override public void visitClassLiteral(ClassLiteral cl) { res[0] = UnitCompiler.this.compileContext2(cl); } @Override public void visitConditionalExpression(ConditionalExpression ce) { res[0] = UnitCompiler.this.compileContext2(ce); } @Override public void visitCrement(Crement c) { res[0] = UnitCompiler.this.compileContext2(c); } @Override public void visitInstanceof(Instanceof io) { res[0] = UnitCompiler.this.compileContext2(io); } @Override public void visitMethodInvocation(MethodInvocation mi) { res[0] = UnitCompiler.this.compileContext2(mi); } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.compileContext2(smi); } @Override public void visitIntegerLiteral(IntegerLiteral il) { res[0] = UnitCompiler.this.compileContext2(il); } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { res[0] = UnitCompiler.this.compileContext2(fpl); } @Override public void visitBooleanLiteral(BooleanLiteral bl) { res[0] = UnitCompiler.this.compileContext2(bl); } @Override public void visitCharacterLiteral(CharacterLiteral cl) { res[0] = UnitCompiler.this.compileContext2(cl); } @Override public void visitStringLiteral(StringLiteral sl) { res[0] = UnitCompiler.this.compileContext2(sl); } @Override public void visitNullLiteral(NullLiteral nl) { res[0] = UnitCompiler.this.compileContext2(nl); } @Override public void visitSimpleConstant(SimpleConstant sl) { res[0] = UnitCompiler.this.compileContext2(sl); } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.compileContext2(naci); } @Override public void visitNewArray(NewArray na) { res[0] = UnitCompiler.this.compileContext2(na); } @Override public void visitNewInitializedArray(NewInitializedArray nia) { res[0] = UnitCompiler.this.compileContext2(nia); } @Override public void visitNewClassInstance(NewClassInstance nci) { res[0] = UnitCompiler.this.compileContext2(nci); } @Override public void visitParameterAccess(ParameterAccess pa) { res[0] = UnitCompiler.this.compileContext2(pa); } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { res[0] = UnitCompiler.this.compileContext2(qtr); } @Override public void visitThisReference(ThisReference tr) { res[0] = UnitCompiler.this.compileContext2(tr); } @Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.compileContext2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.compileContext2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.compileContext2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.compileContext2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.compileContext2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.compileContext2(lva); } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.compileContext2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { rv.accept(rvv); return res[0]; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } @SuppressWarnings("static-method") private int compileContext2(Rvalue rv) { return 0; } private int compileContext2(AmbiguousName an) throws CompileException { return this.compileContext(this.toRvalueOrCompileException(this.reclassify(an))); } private int compileContext2(FieldAccess fa) throws CompileException { if (fa.field.isStatic()) { Rvalue rv = fa.lhs.toRvalue(); if (rv != null) { this.warning( "CNSFA", "Left-hand side of static field access should be a type, not an rvalue", fa.lhs.getLocation() ); // JLS7 15.11.1.3.1.1: this.pop(fa.lhs, this.compileGetValue(rv)); } return 0; } else { this.compileGetValue(this.toRvalueOrCompileException(fa.lhs)); return 1; } } private int compileContext2(ArrayLength al) throws CompileException { if (!this.compileGetValue(al.lhs).isArray()) { this.compileError("Cannot determine length of non-array type", al.getLocation()); } return 1; } private int compileContext2(ArrayAccessExpression aae) throws CompileException { IClass lhsType = this.compileGetValue(aae.lhs); if (!lhsType.isArray()) { this.compileError( "Subscript not allowed on non-array type \"" + lhsType.toString() + "\"", aae.getLocation() ); } IClass indexType = this.compileGetValue(aae.index); if ( !this.tryIdentityConversion(indexType, IClass.INT) && !this.tryWideningPrimitiveConversion(aae, indexType, IClass.INT) ) this.compileError( "Index expression of type \"" + indexType + "\" cannot be widened to \"int\"", aae.getLocation() ); return 2; } private int compileContext2(FieldAccessExpression fae) throws CompileException { this.determineValue(fae); return this.compileContext(fae.value); } private int compileContext2(SuperclassFieldAccessExpression scfae) throws CompileException { this.determineValue(scfae); return this.compileContext(scfae.value); } private int compileContext2(ParenthesizedExpression pe) throws CompileException { return this.compileContext(pe.value); } /** * Generates code that determines the value of the {@link Rvalue} and puts it on the operand stack. This method * relies on that the "context" of the {@link Rvalue} is on top of the operand stack (see {@link * #compileContext(Rvalue)}). * * @return The type of the {@link Rvalue} */ private IClass compileGet(Rvalue rv) throws CompileException { final IClass[] res = new IClass[1]; RvalueVisitor rvv = new RvalueVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitArrayLength(ArrayLength al) { res[0] = UnitCompiler.this.compileGet2(al); } @Override public void visitAssignment(Assignment a) { try { res[0] = UnitCompiler.this.compileGet2(a); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitUnaryOperation(UnaryOperation uo) { try { res[0] = UnitCompiler.this.compileGet2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBinaryOperation(BinaryOperation bo) { try { res[0] = UnitCompiler.this.compileGet2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCast(Cast c) { try { res[0] = UnitCompiler.this.compileGet2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitClassLiteral(ClassLiteral cl) { try { res[0] = UnitCompiler.this.compileGet2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitConditionalExpression(ConditionalExpression ce) { try { res[0] = UnitCompiler.this.compileGet2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCrement(Crement c) { try { res[0] = UnitCompiler.this.compileGet2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitInstanceof(Instanceof io) { try { res[0] = UnitCompiler.this.compileGet2(io); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitMethodInvocation(MethodInvocation mi) { try { res[0] = UnitCompiler.this.compileGet2(mi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { res[0] = UnitCompiler.this.compileGet2(smi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitIntegerLiteral(IntegerLiteral il) { try { res[0] = UnitCompiler.this.compileGet2(il); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { res[0] = UnitCompiler.this.compileGet2(fpl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBooleanLiteral(BooleanLiteral bl) { try { res[0] = UnitCompiler.this.compileGet2(bl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCharacterLiteral(CharacterLiteral cl) { try { res[0] = UnitCompiler.this.compileGet2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitStringLiteral(StringLiteral sl) { try { res[0] = UnitCompiler.this.compileGet2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNullLiteral(NullLiteral nl) { try { res[0] = UnitCompiler.this.compileGet2(nl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSimpleConstant(SimpleConstant sl) { try { res[0] = UnitCompiler.this.compileGet2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { try { res[0] = UnitCompiler.this.compileGet2(naci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewArray(NewArray na) { try { res[0] = UnitCompiler.this.compileGet2(na); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewInitializedArray(NewInitializedArray nia) { try { res[0] = UnitCompiler.this.compileGet2(nia); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewClassInstance(NewClassInstance nci) { try { res[0] = UnitCompiler.this.compileGet2(nci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParameterAccess(ParameterAccess pa) { try { res[0] = UnitCompiler.this.compileGet2(pa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { res[0] = UnitCompiler.this.compileGet2(qtr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitThisReference(ThisReference tr) { try { res[0] = UnitCompiler.this.compileGet2(tr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.compileGet2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.compileGet2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.compileGet2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.compileGet2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.compileGet2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.compileGet2(lva); } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.compileGet2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { rv.accept(rvv); return res[0]; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } private IClass compileGet2(BooleanRvalue brv) throws CompileException { CodeContext.Offset isTrue = this.codeContext.new Offset(); this.compileBoolean(brv, isTrue, UnitCompiler.JUMP_IF_TRUE); this.writeOpcode(brv, Opcode.ICONST_0); CodeContext.Offset end = this.codeContext.new Offset(); this.writeBranch(brv, Opcode.GOTO, end); isTrue.set(); this.writeOpcode(brv, Opcode.ICONST_1); end.set(); return IClass.BOOLEAN; } private IClass compileGet2(AmbiguousName an) throws CompileException { return this.compileGet(this.toRvalueOrCompileException(this.reclassify(an))); } private IClass compileGet2(LocalVariableAccess lva) { return this.load(lva, lva.localVariable); } private IClass compileGet2(FieldAccess fa) throws CompileException { this.checkAccessible(fa.field, fa.getEnclosingBlockStatement()); this.getfield(fa, fa.field); return fa.field.getType(); } private IClass compileGet2(ArrayLength al) { this.writeOpcode(al, Opcode.ARRAYLENGTH); return IClass.INT; } private IClass compileGet2(ThisReference tr) throws CompileException { this.referenceThis(tr); return this.getIClass(tr); } private IClass compileGet2(QualifiedThisReference qtr) throws CompileException { this.referenceThis( qtr, // locatable this.getDeclaringClass(qtr), // declaringClass this.getDeclaringTypeBodyDeclaration(qtr), // declaringTypeBodyDeclaration this.getTargetIClass(qtr) // targetIClass ); return this.getTargetIClass(qtr); } private IClass compileGet2(ClassLiteral cl) throws CompileException { final Location loc = cl.getLocation(); final IClassLoader icl = this.iClassLoader; IClass iClass = this.getType(cl.type); if (iClass.isPrimitive()) { // Primitive class literal. this.writeOpcode(cl, Opcode.GETSTATIC); String wrapperClassDescriptor = ( iClass == IClass.VOID ? "Ljava/lang/Void;" : iClass == IClass.BYTE ? "Ljava/lang/Byte;" : iClass == IClass.CHAR ? "Ljava/lang/Character;" : iClass == IClass.DOUBLE ? "Ljava/lang/Double;" : iClass == IClass.FLOAT ? "Ljava/lang/Float;" : iClass == IClass.INT ? "Ljava/lang/Integer;" : iClass == IClass.LONG ? "Ljava/lang/Long;" : iClass == IClass.SHORT ? "Ljava/lang/Short;" : iClass == IClass.BOOLEAN ? "Ljava/lang/Boolean;" : null ); if (wrapperClassDescriptor == null) { throw new JaninoRuntimeException("SNO: Unidentifiable primitive type \"" + iClass + "\""); } this.writeConstantFieldrefInfo( wrapperClassDescriptor, // classFD "TYPE", // fieldName "Ljava/lang/Class;" // fieldFD ); return icl.TYPE_java_lang_Class; } // Non-primitive class literal. TypeDeclaration declaringType; for (Scope s = cl.getEnclosingBlockStatement();; s = s.getEnclosingScope()) { if (s instanceof TypeDeclaration) { declaringType = (AbstractTypeDeclaration) s; break; } } // Check if synthetic method "static Class class$(String className)" is already declared. if (declaringType.getMethodDeclaration("class$") == null) this.declareClassDollarMethod(cl); // Determine the statics of the declaring class (this is where static fields declarations are found). List statics; if (declaringType instanceof ClassDeclaration) { statics = ((ClassDeclaration) declaringType).variableDeclaratorsAndInitializers; } else if (declaringType instanceof InterfaceDeclaration) { statics = ((InterfaceDeclaration) declaringType).constantDeclarations; } else { throw new JaninoRuntimeException( "SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration" ); } String className = Descriptor.toClassName(iClass.getDescriptor()); // Compose the "class-dollar" field name. This i done as follows: // Type Class-name Field-name // String java.lang.String class$java$lang$String // String[] [Ljava.lang.String; array$Ljava$lang$String // String[][] [[Ljava.lang.String; array$$Ljava$lang$String // String[][][] [[[java.lang.String; array$$$Ljava$lang$String // int[] [I array$I // int[][] [[I array$$I String classDollarFieldName; { if (className.startsWith("[")) { classDollarFieldName = "array" + className.replace('.', '$').replace('[', '$'); if (classDollarFieldName.endsWith(";")) { classDollarFieldName = classDollarFieldName.substring(0, classDollarFieldName.length() - 1); } } else { classDollarFieldName = "class$" + className.replace('.', '$'); } } // Declare the static "class dollar field" if not already done. ADD_CLASS_DOLLAR_FIELD: { for (BlockStatement bs : statics) { if (!((TypeBodyDeclaration) bs).isStatic()) continue; if (bs instanceof FieldDeclaration) { for (VariableDeclarator vd : ((FieldDeclaration) bs).variableDeclarators) { if (vd.name.equals(classDollarFieldName)) { break ADD_CLASS_DOLLAR_FIELD; } } } } Type classType = new SimpleType(loc, icl.TYPE_java_lang_Class); FieldDeclaration fd = new FieldDeclaration( loc, // location null, // optionalDocComment new Modifiers(Mod.STATIC), // modifiers classType, // type new VariableDeclarator[] { // variableDeclarators new VariableDeclarator( loc, // location classDollarFieldName, // name 0, // brackets (Rvalue) null // optionalInitializer ) } ); if (declaringType instanceof ClassDeclaration) { ((ClassDeclaration) declaringType).addFieldDeclaration(fd); } else if (declaringType instanceof InterfaceDeclaration) { ((InterfaceDeclaration) declaringType).addConstantDeclaration(fd); } else { throw new JaninoRuntimeException( "SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration" ); } } // return (class$X != null) ? class$X : (class$X = class$("X")); Type declaringClassOrInterfaceType = new SimpleType(loc, this.resolve(declaringType)); Lvalue classDollarFieldAccess = new FieldAccessExpression( loc, // location declaringClassOrInterfaceType, // lhs classDollarFieldName // fieldName ); ConditionalExpression ce = new ConditionalExpression( loc, // location new BinaryOperation( // lhs loc, // location classDollarFieldAccess, // lhs "!=", // op new NullLiteral(loc, "null") // rhs ), classDollarFieldAccess, // mhs new Assignment( // rhs loc, // location classDollarFieldAccess, // lhs "=", // operator new MethodInvocation( // rhs loc, // location declaringClassOrInterfaceType, // optionalTarget "class$", // methodName new Rvalue[] { // arguments new StringLiteral( loc, // location '"' + className + '"' // constantValue ) } ) ) ); ce.setEnclosingBlockStatement(cl.getEnclosingBlockStatement()); return this.compileGet(ce); } private IClass compileGet2(Assignment a) throws CompileException { if (a.operator == "=") { // SUPPRESS CHECKSTYLE StringLiteralEquality int lhsCs = this.compileContext(a.lhs); IClass rhsType = this.compileGetValue(a.rhs); IClass lhsType = this.getType(a.lhs); Object rhsCv = this.getConstantValue(a.rhs); this.assignmentConversion(a, rhsType, lhsType, rhsCv); this.dupx(a, lhsType, lhsCs); this.compileSet(a.lhs); return lhsType; } // Implement "|= ^= &= *= /= %= += -= <<= >>= >>>=". int lhsCs = this.compileContext(a.lhs); this.dup(a, lhsCs); IClass lhsType = this.compileGet(a.lhs); IClass resultType = this.compileArithmeticBinaryOperation( a, // locatable lhsType, // lhsType a.operator.substring( // operator 0, a.operator.length() - 1 ).intern(), /* <= IMPORTANT! */ a.rhs // rhs ); // Convert the result to LHS type (JLS7 15.26.2). if ( !this.tryIdentityConversion(resultType, lhsType) && !this.tryNarrowingPrimitiveConversion(a, resultType, lhsType) ) throw new JaninoRuntimeException("SNO: \"" + a.operator + "\" reconversion failed"); this.dupx(a, lhsType, lhsCs); this.compileSet(a.lhs); return lhsType; } private IClass compileGet2(ConditionalExpression ce) throws CompileException { IClass mhsType, rhsType; CodeContext.Inserter mhsConvertInserter, rhsConvertInserter; CodeContext.Offset toEnd = this.codeContext.new Offset(); { Object cv = this.getConstantValue(ce.lhs); if (cv instanceof Boolean) { if (((Boolean) cv).booleanValue()) { mhsType = this.compileGetValue(ce.mhs); mhsConvertInserter = this.codeContext.newInserter(); rhsType = this.getType(ce.rhs); rhsConvertInserter = null; } else { mhsType = this.getType(ce.mhs); mhsConvertInserter = null; rhsType = this.compileGetValue(ce.rhs); rhsConvertInserter = this.codeContext.currentInserter(); } } else { CodeContext.Offset toRhs = this.codeContext.new Offset(); this.compileBoolean(ce.lhs, toRhs, UnitCompiler.JUMP_IF_FALSE); mhsType = this.compileGetValue(ce.mhs); mhsConvertInserter = this.codeContext.newInserter(); this.writeBranch(ce, Opcode.GOTO, toEnd); toRhs.set(); rhsType = this.compileGetValue(ce.rhs); rhsConvertInserter = this.codeContext.currentInserter(); } } IClass expressionType; if (mhsType == rhsType) { // JLS7 15.25, list 1, bullet 1: "b ? T : T => T" expressionType = mhsType; } else if (this.tryUnboxingConversion(ce.mhs, mhsType, rhsType, mhsConvertInserter)) { // JLS7 15.25, list 1, bullet 2: "b ? Integer : int => int" expressionType = rhsType; } else if (this.tryUnboxingConversion(ce.rhs, rhsType, mhsType, rhsConvertInserter)) { // JLS7 15.25, list 1, bullet 2: "b ? int : Integer => int" expressionType = mhsType; } else if (this.getConstantValue(ce.mhs) == null && !rhsType.isPrimitive()) { // JLS7 15.25, list 1, bullet 3: "b ? null : ReferenceType => ReferenceType" expressionType = rhsType; } else if (!mhsType.isPrimitive() && this.getConstantValue(ce.rhs) == null) { // JLS7 15.25, list 1, bullet 3: "b ? ReferenceType : null => ReferenceType" expressionType = mhsType; } else if (this.isConvertibleToPrimitiveNumeric(mhsType) && this.isConvertibleToPrimitiveNumeric(rhsType)) { // TODO JLS7 15.25, list 1, bullet 4, bullet 1: "b ? Byte : Short => short" // TODO JLS7 15.25, list 1, bullet 4, bullet 2: "b ? 127 : byte => byte" // TODO JLS7 15.25, list 1, bullet 4, bullet 3: "b ? 127 : byte => byte" // JLS7 15.25, list 1, bullet 4, bullet 4: "b ? Integer : Double => double" expressionType = this.binaryNumericPromotion( ce, // locatable mhsType, // type1 mhsConvertInserter, // convertInserter1 rhsType, // type2 rhsConvertInserter // convertInserter2 ); } else if (!mhsType.isPrimitive() && !rhsType.isPrimitive()) { // JLS7 15.25, list 1, bullet 5: "b ? Base : Derived => Base" if (mhsType.isAssignableFrom(rhsType)) { expressionType = mhsType; } else if (rhsType.isAssignableFrom(mhsType)) { expressionType = rhsType; } else { this.compileError( "Reference types \"" + mhsType + "\" and \"" + rhsType + "\" don't match", ce.getLocation() ); return this.iClassLoader.TYPE_java_lang_Object; } } else { this.compileError( "Incompatible expression types \"" + mhsType + "\" and \"" + rhsType + "\"", ce.getLocation() ); return this.iClassLoader.TYPE_java_lang_Object; } toEnd.set(); return expressionType; } private IClass compileGet2(Crement c) throws CompileException { // Optimized crement of integer local variable. LocalVariable lv = this.isIntLv(c); if (lv != null) { if (!c.pre) this.load(c, lv); this.compileLocalVariableCrement(c, lv); if (c.pre) this.load(c, lv); return lv.type; } // Compile operand context. int cs = this.compileContext(c.operand); // DUP operand context. this.dup(c, cs); // Get operand value. IClass type = this.compileGet(c.operand); // DUPX operand value. if (!c.pre) this.dupx(c, type, cs); // Apply "unary numeric promotion". IClass promotedType = this.unaryNumericPromotion(c, type); // Crement. this.writeOpcode(c, UnitCompiler.ilfd( promotedType, Opcode.ICONST_1, Opcode.LCONST_1, Opcode.FCONST_1, Opcode.DCONST_1 )); if (c.operator == "++") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.writeOpcode(c, Opcode.IADD + UnitCompiler.ilfd(promotedType)); } else if (c.operator == "--") { // SUPPRESS CHECKSTYLE StringLiteralEquality this.writeOpcode(c, Opcode.ISUB + UnitCompiler.ilfd(promotedType)); } else { this.compileError("Unexpected operator \"" + c.operator + "\"", c.getLocation()); } this.reverseUnaryNumericPromotion(c, promotedType, type); // DUPX cremented operand value. if (c.pre) this.dupx(c, type, cs); // Set operand. this.compileSet(c.operand); return type; } private void compileLocalVariableCrement(Crement c, LocalVariable lv) { this.crement(c, lv, c.operator); } private void crement(Locatable locatable, LocalVariable lv, String operator) { if (lv.getSlotIndex() > 255) { this.writeOpcode(locatable, Opcode.WIDE); this.writeOpcode(locatable, Opcode.IINC); this.writeShort(lv.getSlotIndex()); this.writeShort(operator == "++" ? 1 : -1); // SUPPRESS CHECKSTYLE StringLiteralEquality } else { this.writeOpcode(locatable, Opcode.IINC); this.writeByte(lv.getSlotIndex()); this.writeByte(operator == "++" ? 1 : -1); // SUPPRESS CHECKSTYLE StringLiteralEquality } } private IClass compileGet2(ArrayAccessExpression aae) throws CompileException { IClass lhsComponentType = this.getType(aae); this.writeOpcode(aae, Opcode.IALOAD + UnitCompiler.ilfdabcs(lhsComponentType)); return lhsComponentType; } private IClass compileGet2(FieldAccessExpression fae) throws CompileException { this.determineValue(fae); return this.compileGet(fae.value); } private IClass compileGet2(SuperclassFieldAccessExpression scfae) throws CompileException { this.determineValue(scfae); return this.compileGet(scfae.value); } private IClass compileGet2(UnaryOperation uo) throws CompileException { if (uo.operator == "!") { // SUPPRESS CHECKSTYLE StringLiteralEquality return this.compileGet2((BooleanRvalue) uo); } if (uo.operator == "+") { // SUPPRESS CHECKSTYLE StringLiteralEquality return this.unaryNumericPromotion( uo, this.convertToPrimitiveNumericType(uo, this.compileGetValue(uo.operand)) ); } if (uo.operator == "-") { // SUPPRESS CHECKSTYLE StringLiteralEquality { Object ncv = this.getNegatedConstantValue(uo.operand); if (ncv != UnitCompiler.NOT_CONSTANT) { return this.unaryNumericPromotion(uo, this.pushConstant(uo, ncv)); } } IClass promotedType = this.unaryNumericPromotion( uo, this.convertToPrimitiveNumericType(uo, this.compileGetValue(uo.operand)) ); this.writeOpcode(uo, Opcode.INEG + UnitCompiler.ilfd(promotedType)); return promotedType; } if (uo.operator == "~") { // SUPPRESS CHECKSTYLE StringLiteralEquality IClass operandType = this.compileGetValue(uo.operand); IClass promotedType = this.unaryNumericPromotion(uo, operandType); if (promotedType == IClass.INT) { this.writeOpcode(uo, Opcode.ICONST_M1); this.writeOpcode(uo, Opcode.IXOR); return IClass.INT; } if (promotedType == IClass.LONG) { this.writeOpcode(uo, Opcode.LDC2_W); this.writeConstantLongInfo(-1L); this.writeOpcode(uo, Opcode.LXOR); return IClass.LONG; } this.compileError("Operator \"~\" not applicable to type \"" + promotedType + "\"", uo.getLocation()); } this.compileError("Unexpected operator \"" + uo.operator + "\"", uo.getLocation()); return this.iClassLoader.TYPE_java_lang_Object; } private IClass compileGet2(Instanceof io) throws CompileException { IClass lhsType = this.compileGetValue(io.lhs); IClass rhsType = this.getType(io.rhs); if ( lhsType.isInterface() || rhsType.isInterface() // We cannot precompute the result from type information as the value might be null, but we should detect // when the instanceof is statically impossible. || lhsType.isAssignableFrom(rhsType) || rhsType.isAssignableFrom(lhsType) ) { this.writeOpcode(io, Opcode.INSTANCEOF); this.writeConstantClassInfo(rhsType.getDescriptor()); } else { this.compileError("\"" + lhsType + "\" can never be an instance of \"" + rhsType + "\"", io.getLocation()); } return IClass.BOOLEAN; } private IClass compileGet2(BinaryOperation bo) throws CompileException { if ( bo.op == "||" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "&&" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "==" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "!=" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "<" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == ">" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == "<=" // SUPPRESS CHECKSTYLE StringLiteralEquality || bo.op == ">=" // SUPPRESS CHECKSTYLE StringLiteralEquality ) { // Eventually calls "compileBoolean()". return this.compileGet2((BooleanRvalue) bo); } // Implements "| ^ & * / % + - << >> >>>". return this.compileArithmeticOperation( bo, // locatable null, // type bo.unrollLeftAssociation(), // operands bo.op // operator ); } private IClass compileGet2(Cast c) throws CompileException { // JLS7 5.5 Casting Conversion. IClass tt = this.getType(c.targetType); IClass vt = this.compileGetValue(c.value); if ( this.tryIdentityConversion(vt, tt) || this.tryWideningPrimitiveConversion(c, vt, tt) || this.tryNarrowingPrimitiveConversion(c, vt, tt) || this.tryWideningReferenceConversion(vt, tt) || this.tryNarrowingReferenceConversion(c, vt, tt) || this.tryBoxingConversion(c, vt, tt) || this.tryUnboxingConversion(c, vt, tt, null) ) return tt; // JAVAC obviously also permits 'boxing conversion followed by widening reference conversion' and 'unboxing // conversion followed by widening primitive conversion', although these are not described in JLS7 5.5. For the // sake of compatibility, we implement them. // See also http://jira.codehaus.org/browse/JANINO-153 { IClass boxedType = this.isBoxingConvertible(vt); if (boxedType != null && this.isWideningReferenceConvertible(boxedType, tt)) { this.boxingConversion(c, vt, boxedType); return tt; } IClass unboxedType = this.isUnboxingConvertible(vt); if (unboxedType != null && this.isWideningPrimitiveConvertible(unboxedType, tt)) { this.unboxingConversion(c, vt, unboxedType); this.tryWideningPrimitiveConversion(c, unboxedType, tt); return tt; } } this.compileError("Cannot cast \"" + vt + "\" to \"" + tt + "\"", c.getLocation()); return tt; } private IClass compileGet2(ParenthesizedExpression pe) throws CompileException { return this.compileGet(pe.value); } private IClass compileGet2(MethodInvocation mi) throws CompileException { IClass.IMethod iMethod = this.findIMethod(mi); if (mi.optionalTarget == null) { // JLS7 6.5.7.1, 15.12.4.1.1.1 TypeBodyDeclaration scopeTbd; ClassDeclaration scopeClassDeclaration; { Scope s; for ( s = mi.getEnclosingBlockStatement(); !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope() ); scopeTbd = (TypeBodyDeclaration) s; if (!(s instanceof ClassDeclaration)) s = s.getEnclosingScope(); scopeClassDeclaration = (ClassDeclaration) s; } if (iMethod.isStatic()) { this.warning( "IASM", "Implicit access to static method \"" + iMethod.toString() + "\"", mi.getLocation() ); // JLS7 15.12.4.1.1.1.1 ; } else { this.warning( "IANSM", "Implicit access to non-static method \"" + iMethod.toString() + "\"", mi.getLocation() ); // JLS7 15.12.4.1.1.1.2 if (scopeTbd.isStatic()) { this.compileError( "Instance method \"" + iMethod.toString() + "\" cannot be invoked in static context", mi.getLocation() ); } this.referenceThis( mi, // locatable scopeClassDeclaration, // declaringClass scopeTbd, // declaringTypeBodyDeclaration iMethod.getDeclaringIClass() // targetIClass ); } } else { // 6.5.7.2 boolean staticContext = this.isType(mi.optionalTarget); if (staticContext) { this.getType(this.toTypeOrCompileException(mi.optionalTarget)); } else { this.compileGetValue(this.toRvalueOrCompileException(mi.optionalTarget)); } if (iMethod.isStatic()) { if (!staticContext) { // JLS7 15.12.4.1.2.1 this.pop(mi.optionalTarget, this.getType(mi.optionalTarget)); } } else { if (staticContext) { this.compileError( "Instance method \"" + mi.methodName + "\" cannot be invoked in static context", mi.getLocation() ); } } } // Evaluate method parameters (JLS7 15.12.4.2). // If this method is vararg, rewritten all args starting from lastParamIndex to the end as if they were elements // of an array. IClass[] parameterTypes = iMethod.getParameterTypes(); Rvalue[] adjustedArgs = null; final int actualSize = mi.arguments.length; if (iMethod.isVarargs() && iMethod.argsNeedAdjust()) { adjustedArgs = new Rvalue[parameterTypes.length]; Rvalue[] lastArgs = new Rvalue[actualSize - parameterTypes.length + 1]; final Location loc = mi.getLocation(); if (lastArgs.length > 0) { for (int i = 0, j = parameterTypes.length - 1; i < lastArgs.length; ++i, ++j) { lastArgs[i] = mi.arguments[j]; } } for (int i = parameterTypes.length - 2; i >= 0; --i) { adjustedArgs[i] = mi.arguments[i]; } adjustedArgs[adjustedArgs.length - 1] = new NewInitializedArray( loc, // location parameterTypes[parameterTypes.length - 1], // arrayIClass new ArrayInitializer(loc, lastArgs) // arrayInitializer ); } else { adjustedArgs = mi.arguments; } for (int i = 0; i < adjustedArgs.length; ++i) { this.assignmentConversion( mi, // location this.compileGetValue(adjustedArgs[i]), // sourceType parameterTypes[i], // targetType this.getConstantValue(adjustedArgs[i]) // optionalConstantValue ); } // Invoke! this.checkAccessible(iMethod, mi.getEnclosingBlockStatement()); if (iMethod.getDeclaringIClass().isInterface()) { this.invoke(mi, iMethod); } else { if (!iMethod.isStatic() && iMethod.getAccess() == Access.PRIVATE) { // In order to make a non-static private method invocable for enclosing types, enclosed types and types // enclosed by the same type, "compile(FunctionDeclarator)" modifies it on-the-fly as follows: // + Access is changed from PRIVATE to PACKAGE // + The name is appended with "$" // + It is made static // + A parameter of type "declaring class" is prepended to the signature // Hence, the invocation of such a method must be modified accordingly. this.writeOpcode(mi, Opcode.INVOKESTATIC); this.writeConstantMethodrefInfo( iMethod.getDeclaringIClass().getDescriptor(), // classFD iMethod.getName() + '$', // methodName MethodDescriptor.prependParameter( // methodMD iMethod.getDescriptor(), iMethod.getDeclaringIClass().getDescriptor() ) ); } else { this.invoke(mi, iMethod); } } return iMethod.getReturnType(); } private IClass compileGet2(SuperclassMethodInvocation scmi) throws CompileException { final IClass.IMethod iMethod = this.findIMethod(scmi); Scope s; for ( s = scmi.getEnclosingBlockStatement(); s instanceof Statement || s instanceof CatchClause; s = s.getEnclosingScope() ); FunctionDeclarator fd = s instanceof FunctionDeclarator ? (FunctionDeclarator) s : null; if (fd == null) { this.compileError("Cannot invoke superclass method in non-method scope", scmi.getLocation()); return IClass.INT; } if (Mod.isStatic(fd.modifiers.flags)) { this.compileError("Cannot invoke superclass method in static context", scmi.getLocation()); } this.load(scmi, this.resolve(fd.getDeclaringType()), 0); // Evaluate method parameters. // TODO: adjust args IClass[] parameterTypes = iMethod.getParameterTypes(); for (int i = 0; i < scmi.arguments.length; ++i) { this.assignmentConversion( scmi, // locatable this.compileGetValue(scmi.arguments[i]), // sourceType parameterTypes[i], // targetType this.getConstantValue(scmi.arguments[i]) // optionalConstantValue ); } // Invoke! this.writeOpcode(scmi, Opcode.INVOKESPECIAL); this.writeConstantMethodrefInfo( iMethod.getDeclaringIClass().getDescriptor(), // classFD scmi.methodName, // methodName iMethod.getDescriptor() // methodMD ); return iMethod.getReturnType(); } private IClass compileGet2(NewClassInstance nci) throws CompileException { if (nci.iClass == null) nci.iClass = this.getType(nci.type); this.writeOpcode(nci, Opcode.NEW); this.writeConstantClassInfo(nci.iClass.getDescriptor()); this.writeOpcode(nci, Opcode.DUP); if (nci.iClass.isInterface()) this.compileError("Cannot instantiate \"" + nci.iClass + "\"", nci.getLocation()); this.checkAccessible(nci.iClass, nci.getEnclosingBlockStatement()); if (nci.iClass.isAbstract()) { this.compileError("Cannot instantiate abstract \"" + nci.iClass + "\"", nci.getLocation()); } // Determine the enclosing instance for the new object. Rvalue optionalEnclosingInstance; if (nci.optionalQualification != null) { if (nci.iClass.getOuterIClass() == null) { this.compileError("Static member class cannot be instantiated with qualified NEW"); } // Enclosing instance defined by qualification (JLS7 15.9.2.BL1.B3.B2). optionalEnclosingInstance = nci.optionalQualification; } else { Scope s = nci.getEnclosingBlockStatement(); for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope()); TypeBodyDeclaration enclosingTypeBodyDeclaration = (TypeBodyDeclaration) s; TypeDeclaration enclosingTypeDeclaration = (TypeDeclaration) s.getEnclosingScope(); if ( !(enclosingTypeDeclaration instanceof ClassDeclaration) || enclosingTypeBodyDeclaration.isStatic() ) { // No enclosing instance in // + interface method declaration or // + static type body declaration (here: method or initializer or field declarator) // context (JLS7 15.9.2.BL1.B3.B1.B1). if (nci.iClass.getOuterIClass() != null) { this.compileError( "Instantiation of \"" + nci.type + "\" requires an enclosing instance", nci.getLocation() ); } optionalEnclosingInstance = null; } else { // Determine the type of the enclosing instance for the new object. IClass optionalOuterIClass = nci.iClass.getDeclaringIClass(); if (optionalOuterIClass == null) { // No enclosing instance needed for a top-level class object. optionalEnclosingInstance = null; } else { // Find an appropriate enclosing instance for the new inner class object among the enclosing // instances of the current object (JLS7 15.9.2.BL1.B3.B1.B2). optionalEnclosingInstance = new QualifiedThisReference( nci.getLocation(), // location new SimpleType( // qualification nci.getLocation(), optionalOuterIClass ) ); optionalEnclosingInstance.setEnclosingBlockStatement(nci.getEnclosingBlockStatement()); } } } this.invokeConstructor( nci, // l nci.getEnclosingBlockStatement(), // scope optionalEnclosingInstance, // optionalEnclosingInstance nci.iClass, // targetClass nci.arguments // arguments ); return nci.iClass; } private IClass compileGet2(NewAnonymousClassInstance naci) throws CompileException { // Find constructors. AnonymousClassDeclaration acd = naci.anonymousClassDeclaration; IClass sc = this.resolve(acd).getSuperclass(); IClass.IConstructor[] iConstructors = sc.getDeclaredIConstructors(); if (iConstructors.length == 0) throw new JaninoRuntimeException("SNO: Base class has no constructors"); // Determine most specific constructor. IClass.IConstructor iConstructor = (IClass.IConstructor) this.findMostSpecificIInvocable( naci, // locatable iConstructors, // iInvocables naci.arguments, // arguments acd // contextScope ); IClass[] pts = iConstructor.getParameterTypes(); // Determine formal parameters of anonymous constructor. FormalParameters parameters; Location loc = naci.getLocation(); { List l = new ArrayList(); // Pass the enclosing instance of the base class as parameter #1. if (naci.optionalQualification != null) l.add(new FormalParameter( loc, // location true, // finaL new SimpleType(loc, this.getType(naci.optionalQualification)), // type "this$base" // name )); for (int i = 0; i < pts.length; ++i) l.add(new FormalParameter( loc, // location true, // finaL new SimpleType(loc, pts[i]), // type "p" + i // name )); parameters = new FormalParameters( loc, (FormalParameter[]) l.toArray(new FormalParameter[l.size()]), false ); } // Determine thrown exceptions of anonymous constructor. IClass[] tes = iConstructor.getThrownExceptions(); Type[] tets = new Type[tes.length]; for (int i = 0; i < tes.length; ++i) tets[i] = new SimpleType(loc, tes[i]); // The anonymous constructor merely invokes the constructor of its superclass. int j = 0; Rvalue optionalQualificationAccess; if (naci.optionalQualification == null) { optionalQualificationAccess = null; } else { optionalQualificationAccess = new ParameterAccess(loc, parameters.parameters[j++]); } Rvalue[] parameterAccesses = new Rvalue[pts.length]; for (int i = 0; i < pts.length; ++i) { parameterAccesses[i] = new ParameterAccess(loc, parameters.parameters[j++]); } // Generate the anonymous constructor for the anonymous class (JLS7 15.9.5.1). ConstructorDeclarator anonymousConstructor = new ConstructorDeclarator( loc, // location null, // optionalDocComment new Modifiers(Mod.PACKAGE), // modifiers parameters, // parameters tets, // thrownExceptions new SuperConstructorInvocation( // optionalConstructorInvocation loc, // location optionalQualificationAccess, // optionalQualification parameterAccesses // arguments ), Collections.EMPTY_LIST // optionalStatements ); // Compile the anonymous class. acd.addConstructor(anonymousConstructor); try { this.compile(acd); // Instantiate the anonymous class. this.writeOpcode(naci, Opcode.NEW); this.writeConstantClassInfo(this.resolve(naci.anonymousClassDeclaration).getDescriptor()); // TODO: adjust argument (for varargs case ?) // Invoke the anonymous constructor. this.writeOpcode(naci, Opcode.DUP); Rvalue[] arguments2; if (naci.optionalQualification == null) { arguments2 = naci.arguments; } else { arguments2 = new Rvalue[naci.arguments.length + 1]; arguments2[0] = naci.optionalQualification; System.arraycopy(naci.arguments, 0, arguments2, 1, naci.arguments.length); } // Adjust if needed. // TODO: Not doing this now because we don't need vararg-annonymous class (yet). // Rvalue[] adjustedArgs = null; // final int paramsTypeLength = iConstructor.getParameterTypes().length; // if (argsNeedAdjusting[0]) { // adjustedArgs = new Rvalue[paramsTypeLength]; // } // Notice: The enclosing instance of the anonymous class is "this", not the qualification of the // NewAnonymousClassInstance. Scope s; for ( s = naci.getEnclosingBlockStatement(); !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope() ); ThisReference oei; if (((TypeBodyDeclaration) s).isStatic()) { oei = null; } else { oei = new ThisReference(loc); oei.setEnclosingBlockStatement(naci.getEnclosingBlockStatement()); } this.invokeConstructor( naci, // locatable naci.getEnclosingBlockStatement(), // scope oei, // optionalEnclosingInstance this.resolve(naci.anonymousClassDeclaration), // targetClass arguments2 // arguments ); } finally { // Remove the synthetic constructor that was temporarily added. This is necessary because this NACI // expression (and all other expressions) are sometimes compiled more than once (see "fakeCompile()"), and // we'd end up with TWO synthetic constructors. See JANINO-143. acd.constructors.remove(acd.constructors.size() - 1); } return this.resolve(naci.anonymousClassDeclaration); } private IClass compileGet2(ParameterAccess pa) throws CompileException { LocalVariable lv = this.getLocalVariable(pa.formalParameter); this.load(pa, lv); return lv.type; } private IClass compileGet2(NewArray na) throws CompileException { for (Rvalue dimExpr : na.dimExprs) { IClass dimType = this.compileGetValue(dimExpr); if (dimType != IClass.INT && this.unaryNumericPromotion( na, // locatable dimType // type ) != IClass.INT) this.compileError("Invalid array size expression type", na.getLocation()); } return this.newArray( na, // locatable na.dimExprs.length, // dimExprCount na.dims, // dims this.getType(na.type) // componentType ); } private IClass compileGet2(NewInitializedArray nia) throws CompileException { IClass at = nia.arrayType == null ? nia.arrayIClass : this.getType(nia.arrayType); this.compileGetValue(nia.arrayInitializer, at); return at; } private void compileGetValue(ArrayInitializer ai, IClass arrayType) throws CompileException { if (!arrayType.isArray()) { this.compileError("Array initializer not allowed for non-array type \"" + arrayType.toString() + "\""); } IClass ct = arrayType.getComponentType(); this.pushConstant(ai, new Integer(ai.values.length)); this.newArray( ai, // locatable 1, // dimExprCount 0, // dims ct // componentType ); for (int i = 0; i < ai.values.length; ++i) { this.writeOpcode(ai, Opcode.DUP); this.pushConstant(ai, new Integer(i)); ArrayInitializerOrRvalue aiorv = ai.values[i]; if (aiorv instanceof Rvalue) { Rvalue rv = (Rvalue) aiorv; this.assignmentConversion( ai, // locatable this.compileGetValue(rv), // sourceType ct, // targetType this.getConstantValue(rv) // optionalConstantValue ); } else if (aiorv instanceof ArrayInitializer) { this.compileGetValue((ArrayInitializer) aiorv, ct); } else { throw new JaninoRuntimeException( "Unexpected array initializer or rvalue class " + aiorv.getClass().getName() ); } this.writeOpcode(ai, Opcode.IASTORE + UnitCompiler.ilfdabcs(ct)); } } private IClass compileGet2(Literal l) throws CompileException { return this.pushConstant(l, this.getConstantValue(l)); } private IClass compileGet2(SimpleConstant sl) throws CompileException { return this.pushConstant(sl, sl.value); } /** * Convenience function that calls {@link #compileContext(Rvalue)} and {@link #compileGet(Rvalue)}. * * @return The type of the Rvalue */ private IClass compileGetValue(Rvalue rv) throws CompileException { Object cv = this.getConstantValue(rv); if (cv != UnitCompiler.NOT_CONSTANT) { this.fakeCompile(rv); // To check that, e.g., "a" compiles in "true || a". this.pushConstant(rv, cv); return this.getType(rv); } this.compileContext(rv); return this.compileGet(rv); } // -------------------- Rvalue.getConstantValue() ----------------- /** * Special return value for the {@link #getConstantValue(Java.Rvalue)} method family indicating that the given * {@link Java.Rvalue} does not evaluate to a constant value. */ public static final Object NOT_CONSTANT = IClass.NOT_CONSTANT; /** * Attempts to evaluate as a constant expression. * * @return {@link #NOT_CONSTANT} iff the rvalue is not a constant value */ public final Object getConstantValue(Rvalue rv) throws CompileException { if (rv.constantValue != Rvalue.CONSTANT_VALUE_UNKNOWN) return rv.constantValue; final Object[] res = new Object[1]; RvalueVisitor rvv = new RvalueVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitArrayLength(ArrayLength al) { res[0] = UnitCompiler.this.getConstantValue2(al); } @Override public void visitAssignment(Assignment a) { res[0] = UnitCompiler.this.getConstantValue2(a); } @Override public void visitUnaryOperation(UnaryOperation uo) { try { res[0] = UnitCompiler.this.getConstantValue2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBinaryOperation(BinaryOperation bo) { try { res[0] = UnitCompiler.this.getConstantValue2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCast(Cast c) { try { res[0] = UnitCompiler.this.getConstantValue2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitClassLiteral(ClassLiteral cl) { res[0] = UnitCompiler.this.getConstantValue2(cl); } @Override public void visitConditionalExpression(ConditionalExpression ce) { try { res[0] = UnitCompiler.this.getConstantValue2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCrement(Crement c) { res[0] = UnitCompiler.this.getConstantValue2(c); } @Override public void visitInstanceof(Instanceof io) { res[0] = UnitCompiler.this.getConstantValue2(io); } @Override public void visitMethodInvocation(MethodInvocation mi) { res[0] = UnitCompiler.this.getConstantValue2(mi); } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.getConstantValue2(smi); } @Override public void visitIntegerLiteral(IntegerLiteral il) { try { res[0] = UnitCompiler.this.getConstantValue2(il); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { res[0] = UnitCompiler.this.getConstantValue2(fpl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBooleanLiteral(BooleanLiteral bl) { res[0] = UnitCompiler.this.getConstantValue2(bl); } @Override public void visitCharacterLiteral(CharacterLiteral cl) { res[0] = UnitCompiler.this.getConstantValue2(cl); } @Override public void visitStringLiteral(StringLiteral sl) { res[0] = UnitCompiler.this.getConstantValue2(sl); } @Override public void visitNullLiteral(NullLiteral nl) { res[0] = UnitCompiler.this.getConstantValue2(nl); } @Override public void visitSimpleConstant(SimpleConstant sl) { res[0] = UnitCompiler.this.getConstantValue2(sl); } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.getConstantValue2(naci); } @Override public void visitNewArray(NewArray na) { res[0] = UnitCompiler.this.getConstantValue2(na); } @Override public void visitNewInitializedArray(NewInitializedArray nia) { res[0] = UnitCompiler.this.getConstantValue2(nia); } @Override public void visitNewClassInstance(NewClassInstance nci) { res[0] = UnitCompiler.this.getConstantValue2(nci); } @Override public void visitParameterAccess(ParameterAccess pa) { res[0] = UnitCompiler.this.getConstantValue2(pa); } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { res[0] = UnitCompiler.this.getConstantValue2(qtr); } @Override public void visitThisReference(ThisReference tr) { res[0] = UnitCompiler.this.getConstantValue2(tr); } @Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.getConstantValue2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { res[0] = UnitCompiler.this.getConstantValue2(aae); } @Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.getConstantValue2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { res[0] = UnitCompiler.this.getConstantValue2(fae); } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { res[0] = UnitCompiler.this.getConstantValue2(scfae); } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.getConstantValue2(lva); } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.getConstantValue2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { rv.accept(rvv); rv.constantValue = res[0]; return rv.constantValue; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } @SuppressWarnings("static-method") private Object getConstantValue2(Rvalue rv) { return UnitCompiler.NOT_CONSTANT; } private Object getConstantValue2(AmbiguousName an) throws CompileException { return this.getConstantValue(this.toRvalueOrCompileException(this.reclassify(an))); } @SuppressWarnings("static-method") private Object getConstantValue2(FieldAccess fa) throws CompileException { return fa.field.getConstantValue(); } private Object getConstantValue2(UnaryOperation uo) throws CompileException { if (uo.operator.equals("+")) return this.getConstantValue(uo.operand); if (uo.operator.equals("-")) return this.getNegatedConstantValue(uo.operand); if (uo.operator.equals("!")) { Object cv = this.getConstantValue(uo.operand); return ( cv == Boolean.TRUE ? Boolean.FALSE : cv == Boolean.FALSE ? Boolean.TRUE : UnitCompiler.NOT_CONSTANT ); } return UnitCompiler.NOT_CONSTANT; } private Object getConstantValue2(ConditionalExpression ce) throws CompileException { Object lhsValue = this.getConstantValue(ce.lhs); if (lhsValue instanceof Boolean) { return ( ((Boolean) lhsValue).booleanValue() ? this.getConstantValue(ce.mhs) : this.getConstantValue(ce.rhs) ); } return UnitCompiler.NOT_CONSTANT; } private Object getConstantValue2(BinaryOperation bo) throws CompileException { // "|", "^", "&", "*", "/", "%", "+", "-", "==", "!=". if ( // CHECKSTYLE StringLiteralEquality:OFF bo.op == "|" || bo.op == "^" || bo.op == "&" || bo.op == "*" || bo.op == "/" || bo.op == "%" || bo.op == "+" || bo.op == "-" || bo.op == "==" || bo.op == "!=" // CHECKSTYLE StringLiteralEquality:ON ) { // Unroll the constant operands. List cvs = new ArrayList(); for (Iterator it = bo.unrollLeftAssociation(); it.hasNext();) { Object cv = this.getConstantValue(((Rvalue) it.next())); if (cv == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT; cvs.add(cv); } // Compute the constant value of the unrolled binary operation. Iterator it = cvs.iterator(); Object lhs = it.next(); while (it.hasNext()) { if (lhs == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT; Object rhs = it.next(); // String concatenation? // SUPPRESS CHECKSTYLE StringLiteralEquality if (bo.op == "+" && (lhs instanceof String || rhs instanceof String)) { StringBuilder sb = new StringBuilder(lhs.toString()).append(rhs); while (it.hasNext()) sb.append(it.next().toString()); return sb.toString(); } if (lhs instanceof Number && rhs instanceof Number) { try { if (lhs instanceof Double || rhs instanceof Double) { double lhsD = ((Number) lhs).doubleValue(); double rhsD = ((Number) rhs).doubleValue(); lhs = ( // CHECKSTYLE StringLiteralEquality:OFF 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) : bo.op == "==" ? Boolean.valueOf(lhsD == rhsD) : bo.op == "!=" ? Boolean.valueOf(lhsD != rhsD) : UnitCompiler.NOT_CONSTANT // CHECKSTYLE StringLiteralEquality:ON ); continue; } if (lhs instanceof Float || rhs instanceof Float) { float lhsF = ((Number) lhs).floatValue(); float rhsF = ((Number) rhs).floatValue(); lhs = ( // CHECKSTYLE StringLiteralEquality:OFF 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) : bo.op == "==" ? Boolean.valueOf(lhsF == rhsF) : bo.op == "!=" ? Boolean.valueOf(lhsF != rhsF) : UnitCompiler.NOT_CONSTANT // CHECKSTYLE StringLiteralEquality:ON ); continue; } if (lhs instanceof Long || rhs instanceof Long) { long lhsL = ((Number) lhs).longValue(); long rhsL = ((Number) rhs).longValue(); lhs = ( // CHECKSTYLE StringLiteralEquality:OFF 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) : bo.op == "==" ? Boolean.valueOf(lhsL == rhsL) : bo.op == "!=" ? Boolean.valueOf(lhsL != rhsL) : UnitCompiler.NOT_CONSTANT // CHECKSTYLE StringLiteralEquality:ON ); continue; } if ( lhs instanceof Integer || lhs instanceof Byte || lhs instanceof Short || rhs instanceof Integer || lhs instanceof Byte || lhs instanceof Short ) { int lhsI = ((Number) lhs).intValue(); int rhsI = ((Number) rhs).intValue(); lhs = ( // CHECKSTYLE StringLiteralEquality:OFF 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) : bo.op == "==" ? Boolean.valueOf(lhsI == rhsI) : bo.op == "!=" ? Boolean.valueOf(lhsI != rhsI) : UnitCompiler.NOT_CONSTANT // CHECKSTYLE StringLiteralEquality:ON ); continue; } } catch (ArithmeticException ae) { // Most likely a divide by zero or modulo by zero. Guess we can't make this expression into a // constant. return UnitCompiler.NOT_CONSTANT; } throw new IllegalStateException(); } if (lhs instanceof Character && rhs instanceof Character) { char lhsC = ((Character) lhs).charValue(); char rhsC = ((Character) rhs).charValue(); lhs = ( bo.op == "==" ? Boolean.valueOf(lhsC == rhsC) : // SUPPRESS CHECKSTYLE StringLiteralEquality bo.op == "!=" ? Boolean.valueOf(lhsC != rhsC) : // SUPPRESS CHECKSTYLE StringLiteralEquality UnitCompiler.NOT_CONSTANT ); continue; } if (lhs == null || rhs == null) { lhs = ( bo.op == "==" ? Boolean.valueOf(lhs == rhs) : // SUPPRESS CHECKSTYLE StringLiteralEquality bo.op == "!=" ? Boolean.valueOf(lhs != rhs) : // SUPPRESS CHECKSTYLE StringLiteralEquality UnitCompiler.NOT_CONSTANT ); continue; } return UnitCompiler.NOT_CONSTANT; } return lhs; } // "&&" and "||" with constant LHS operand. if (bo.op == "&&" || bo.op == "||") { // SUPPRESS CHECKSTYLE StringLiteralEquality Object lhsValue = this.getConstantValue(bo.lhs); if (lhsValue instanceof Boolean) { boolean lhsBv = ((Boolean) lhsValue).booleanValue(); return ( bo.op == "&&" // SUPPRESS CHECKSTYLE StringLiteralEquality ? (lhsBv ? this.getConstantValue(bo.rhs) : Boolean.FALSE) : (lhsBv ? Boolean.TRUE : this.getConstantValue(bo.rhs)) ); } } return UnitCompiler.NOT_CONSTANT; } private Object getConstantValue2(Cast c) throws CompileException { Object cv = this.getConstantValue(c.value); if (cv == UnitCompiler.NOT_CONSTANT) return UnitCompiler.NOT_CONSTANT; if (cv instanceof Number) { 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 UnitCompiler.NOT_CONSTANT; } private Object getConstantValue2(ParenthesizedExpression pe) throws CompileException { return this.getConstantValue(pe.value); } @SuppressWarnings("static-method") private Object getConstantValue2(IntegerLiteral il) throws CompileException { String v = il.value; if (v.startsWith("0x")) { return ( v.endsWith("L") || v.endsWith("l") ? (Object) Long.valueOf(UnitCompiler.hex2Long(il, v.substring(2, v.length() - 1))) : Integer.valueOf(UnitCompiler.hex2Int(il, v.substring(2))) ); } if (v.startsWith("0")) { return ( v.endsWith("L") || v.endsWith("l") ? (Object) Long.valueOf(UnitCompiler.oct2Long(il, v.substring(0, v.length() - 1))) : Integer.valueOf(UnitCompiler.oct2Int(il, v)) ); } try { return ( v.endsWith("L") || v.endsWith("l") ? (Object) new Long(v.substring(0, v.length() - 1)) : new Integer(v) ); } catch (NumberFormatException e) { // SUPPRESS CHECKSTYLE AvoidHidingCause throw UnitCompiler.compileException(il, "Value of decimal integer literal '" + v + "' is out of range"); } } @SuppressWarnings("static-method") private Object getConstantValue2(FloatingPointLiteral fpl) throws CompileException { String v = fpl.value; char lastChar = v.charAt(v.length() - 1); if (lastChar == 'f' || lastChar == 'F') { v = v.substring(0, v.length() - 1); float fv; try { fv = Float.parseFloat(v); } catch (NumberFormatException e) { throw new JaninoRuntimeException("SNO: parsing float literal '" + v + "': " + e.getMessage(), e); } if (Float.isInfinite(fv)) { throw UnitCompiler.compileException(fpl, "Value of float literal '" + v + "' is out of range"); } if (Float.isNaN(fv)) { throw new JaninoRuntimeException("SNO: parsing float literal '" + v + "' results in NaN"); } // Check for FLOAT underrun. if (fv == 0.0F) { for (int i = 0; i < v.length(); ++i) { char c = v.charAt(i); if ("123456789".indexOf(c) != -1) { throw UnitCompiler.compileException( fpl, "Literal '" + v + "' is too small to be represented as a float" ); } if (c != '0' && c != '.') break; } } return new Float(fv); } if (lastChar == 'd' || lastChar == 'D') v = v.substring(0, v.length() - 1); double dv; try { dv = Double.parseDouble(v); } catch (NumberFormatException e) { throw new JaninoRuntimeException("SNO: parsing double literal '" + v + "': " + e.getMessage(), e); } if (Double.isInfinite(dv)) { throw UnitCompiler.compileException(fpl, "Value of double literal '" + v + "' is out of range"); } if (Double.isNaN(dv)) { throw new JaninoRuntimeException("SNO: parsing double literal '" + v + "' results is NaN"); } // Check for DOUBLE underrun. if (dv == 0.0F) { for (int i = 0; i < v.length(); ++i) { char c = v.charAt(i); if ("123456789".indexOf(c) != -1) { throw UnitCompiler.compileException( fpl, "Literal '" + v + "' is too small to be represented as a double" ); } if (c != '0' && c != '.') break; } } return new Double(dv); } @SuppressWarnings("static-method") private Object getConstantValue2(BooleanLiteral bl) { if (bl.value == "true") return Boolean.TRUE; // SUPPRESS CHECKSTYLE StringLiteralEquality if (bl.value == "false") return Boolean.FALSE; // SUPPRESS CHECKSTYLE StringLiteralEquality throw new JaninoRuntimeException(bl.value); } @SuppressWarnings("static-method") private Object getConstantValue2(CharacterLiteral cl) { String v = cl.value; return Character.valueOf(UnitCompiler.unescape(v.substring(1, v.length() - 1)).charAt(0)); } @SuppressWarnings("static-method") private Object getConstantValue2(StringLiteral sl) { if (sl == null || sl.value == null) { return ""; } String v = sl.value; return UnitCompiler.unescape(v.substring(1, v.length() - 1)); } @SuppressWarnings("static-method") private Object getConstantValue2(NullLiteral nl) { return null; } @SuppressWarnings("static-method") private Object getConstantValue2(SimpleConstant sl) { return sl.value; } /** * Attempts to evaluate the negated value of a constant {@link Rvalue}. This is particularly relevant for the * smallest value of an integer or long literal. * * @return {@link #NOT_CONSTANT} iff value is not constant; otherwise a {@link String}, {@link Byte}, {@link * Short}, {@link Integer}, {@link Boolean}, {@link Character}, {@link Float}, {@link Long}, {@link Double} * or {@code null} */ private Object getNegatedConstantValue(Rvalue rv) throws CompileException { final Object[] res = new Object[1]; RvalueVisitor rvv = new RvalueVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitArrayLength(ArrayLength al) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(al); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAssignment(Assignment a) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(a); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitUnaryOperation(UnaryOperation uo) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBinaryOperation(BinaryOperation bo) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCast(Cast c) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitClassLiteral(ClassLiteral cl) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitConditionalExpression(ConditionalExpression ce) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCrement(Crement c) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitInstanceof(Instanceof io) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(io); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitMethodInvocation(MethodInvocation mi) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(mi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(smi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitIntegerLiteral(IntegerLiteral il) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(il); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(fpl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBooleanLiteral(BooleanLiteral bl) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(bl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCharacterLiteral(CharacterLiteral cl) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(cl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitStringLiteral(StringLiteral sl) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNullLiteral(NullLiteral nl) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(nl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSimpleConstant(SimpleConstant sl) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(sl); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(naci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewArray(NewArray na) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(na); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewInitializedArray(NewInitializedArray nia) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(nia); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewClassInstance(NewClassInstance nci) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(nci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParameterAccess(ParameterAccess pa) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(pa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(qtr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitThisReference(ThisReference tr) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(tr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(lva); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.getNegatedConstantValue2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { rv.accept(rvv); return res[0]; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } private Object getNegatedConstantValue2(Rvalue rv) throws CompileException { Object cv = this.getConstantValue(rv); if (cv instanceof Byte) return new Byte((byte) -((Byte) cv).byteValue()); if (cv instanceof Short) return new Short((short) -((Short) cv).shortValue()); if (cv instanceof Integer) return new Integer(-((Integer) cv).intValue()); if (cv instanceof Long) return new Long(-((Long) cv).longValue()); if (cv instanceof Float) return new Float(-((Float) cv).floatValue()); if (cv instanceof Double) return new Double(-((Double) cv).doubleValue()); return UnitCompiler.NOT_CONSTANT; } private Object getNegatedConstantValue2(UnaryOperation uo) throws CompileException { return ( uo.operator.equals("+") ? this.getNegatedConstantValue(uo.operand) : uo.operator.equals("-") ? this.getConstantValue(uo.operand) : UnitCompiler.NOT_CONSTANT ); } private Object getNegatedConstantValue2(ParenthesizedExpression pe) throws CompileException { return this.getNegatedConstantValue(pe.value); } private Object getNegatedConstantValue2(IntegerLiteral il) throws CompileException { String v = il.value; if ("2147483648".equals(v) || "020000000000".equals(v)) { return new Integer(Integer.MIN_VALUE); } char lastChar = v.charAt(v.length() - 1); if (lastChar == 'l' || lastChar == 'L') { String v2 = v.substring(0, v.length() - 1); if ("9223372036854775808".equals(v2) || "01000000000000000000000".equals(v2)) { return new Long(Long.MIN_VALUE); } } return this.getNegatedConstantValue2((Rvalue) il); } // ------------ BlockStatement.generatesCode() ------------- /** Checks whether invocation of {@link #compile(BlockStatement)} would generate more than zero code bytes. */ private boolean generatesCode(BlockStatement bs) throws CompileException { final boolean[] res = new boolean[1]; BlockStatementVisitor bsv = new BlockStatementVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitInitializer(Initializer i) { try { res[0] = UnitCompiler.this.generatesCode2(i); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldDeclaration(FieldDeclaration fd) { try { res[0] = UnitCompiler.this.generatesCode2(fd); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLabeledStatement(LabeledStatement ls) { res[0] = UnitCompiler.this.generatesCode2(ls); } @Override public void visitBlock(Block b) { try { res[0] = UnitCompiler.this.generatesCode2(b); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitExpressionStatement(ExpressionStatement es) { res[0] = UnitCompiler.this.generatesCode2(es); } @Override public void visitIfStatement(IfStatement is) { res[0] = UnitCompiler.this.generatesCode2(is); } @Override public void visitForStatement(ForStatement fs) { res[0] = UnitCompiler.this.generatesCode2(fs); } @Override public void visitForEachStatement(ForEachStatement fes) { res[0] = UnitCompiler.this.generatesCode2(fes); } @Override public void visitWhileStatement(WhileStatement ws) { res[0] = UnitCompiler.this.generatesCode2(ws); } @Override public void visitTryStatement(TryStatement ts) { res[0] = UnitCompiler.this.generatesCode2(ts); } @Override public void visitSwitchStatement(SwitchStatement ss) { res[0] = UnitCompiler.this.generatesCode2(ss); } @Override public void visitSynchronizedStatement(SynchronizedStatement ss) { res[0] = UnitCompiler.this.generatesCode2(ss); } @Override public void visitDoStatement(DoStatement ds) { res[0] = UnitCompiler.this.generatesCode2(ds); } @Override public void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { res[0] = UnitCompiler.this.generatesCode2(lvds); } @Override public void visitReturnStatement(ReturnStatement rs) { res[0] = UnitCompiler.this.generatesCode2(rs); } @Override public void visitThrowStatement(ThrowStatement ts) { res[0] = UnitCompiler.this.generatesCode2(ts); } @Override public void visitBreakStatement(BreakStatement bs) { res[0] = UnitCompiler.this.generatesCode2(bs); } @Override public void visitContinueStatement(ContinueStatement cs) { res[0] = UnitCompiler.this.generatesCode2(cs); } @Override public void visitAssertStatement(AssertStatement as) { res[0] = UnitCompiler.this.generatesCode2(as); } @Override public void visitEmptyStatement(EmptyStatement es) { res[0] = UnitCompiler.this.generatesCode2(es); } @Override public void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { res[0] = UnitCompiler.this.generatesCode2(lcds); } @Override public void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { res[0] = UnitCompiler.this.generatesCode2(aci); } @Override public void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { res[0] = UnitCompiler.this.generatesCode2(sci); } // CHECKSTYLE LineLengthCheck:ON }; try { bs.accept(bsv); return res[0]; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } @SuppressWarnings("static-method") private boolean generatesCode2(BlockStatement bs) { return true; } @SuppressWarnings("static-method") private boolean generatesCode2(AssertStatement as) { return true; } @SuppressWarnings("static-method") private boolean generatesCode2(EmptyStatement es) { return false; } @SuppressWarnings("static-method") private boolean generatesCode2(LocalClassDeclarationStatement lcds) { return false; } private boolean generatesCode2(Initializer i) throws CompileException { return this.generatesCode(i.block); } private boolean generatesCode2(List l) throws CompileException { for (BlockStatement bs : l) if (this.generatesCode(bs)) return true; return false; } private boolean generatesCode2(Block b) throws CompileException { return this.generatesCode2(b.statements); } private boolean generatesCode2(FieldDeclaration fd) throws CompileException { // Code is only generated if at least one of the declared variables has a non-constant-final initializer. for (VariableDeclarator vd : fd.variableDeclarators) { if (this.getNonConstantFinalInitializer(fd, vd) != null) return true; } return false; } // ------------ BlockStatement.leave() ------------- /** * Clean up the statement context. This is currently relevant for "try ... catch ... finally" statements (execute * "finally" clause) and "synchronized" statements (monitorexit). *

* Statements like "return", "break", "continue" must call this method for all the statements they terminate. *

* Notice: If {@code optionalStackValueType} is {@code null}, 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(BlockStatement, IClass)} that require a specific operand stack state (e.g. an empty operand stack for * JSR). */ private void leave(BlockStatement bs, final IClass optionalStackValueType) { BlockStatementVisitor bsv = new BlockStatementVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitInitializer(Initializer i) { UnitCompiler.this.leave2(i, optionalStackValueType); } @Override public void visitFieldDeclaration(FieldDeclaration fd) { UnitCompiler.this.leave2(fd, optionalStackValueType); } @Override public void visitLabeledStatement(LabeledStatement ls) { UnitCompiler.this.leave2(ls, optionalStackValueType); } @Override public void visitBlock(Block b) { UnitCompiler.this.leave2(b, optionalStackValueType); } @Override public void visitExpressionStatement(ExpressionStatement es) { UnitCompiler.this.leave2(es, optionalStackValueType); } @Override public void visitIfStatement(IfStatement is) { UnitCompiler.this.leave2(is, optionalStackValueType); } @Override public void visitForStatement(ForStatement fs) { UnitCompiler.this.leave2(fs, optionalStackValueType); } @Override public void visitForEachStatement(ForEachStatement fes) { UnitCompiler.this.leave2(fes, optionalStackValueType); } @Override public void visitWhileStatement(WhileStatement ws) { UnitCompiler.this.leave2(ws, optionalStackValueType); } @Override public void visitTryStatement(TryStatement ts) { UnitCompiler.this.leave2(ts, optionalStackValueType); } @Override public void visitSwitchStatement(SwitchStatement ss) { UnitCompiler.this.leave2(ss, optionalStackValueType); } @Override public void visitSynchronizedStatement(SynchronizedStatement ss) { UnitCompiler.this.leave2(ss, optionalStackValueType); } @Override public void visitDoStatement(DoStatement ds) { UnitCompiler.this.leave2(ds, optionalStackValueType); } @Override public void visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatement lvds) { UnitCompiler.this.leave2(lvds, optionalStackValueType); } @Override public void visitReturnStatement(ReturnStatement rs) { UnitCompiler.this.leave2(rs, optionalStackValueType); } @Override public void visitThrowStatement(ThrowStatement ts) { UnitCompiler.this.leave2(ts, optionalStackValueType); } @Override public void visitBreakStatement(BreakStatement bs) { UnitCompiler.this.leave2(bs, optionalStackValueType); } @Override public void visitContinueStatement(ContinueStatement cs) { UnitCompiler.this.leave2(cs, optionalStackValueType); } @Override public void visitAssertStatement(AssertStatement as) { UnitCompiler.this.leave2(as, optionalStackValueType); } @Override public void visitEmptyStatement(EmptyStatement es) { UnitCompiler.this.leave2(es, optionalStackValueType); } @Override public void visitLocalClassDeclarationStatement(LocalClassDeclarationStatement lcds) { UnitCompiler.this.leave2(lcds, optionalStackValueType); } @Override public void visitAlternateConstructorInvocation(AlternateConstructorInvocation aci) { UnitCompiler.this.leave2(aci, optionalStackValueType); } @Override public void visitSuperConstructorInvocation(SuperConstructorInvocation sci) { UnitCompiler.this.leave2(sci, optionalStackValueType); } // CHECKSTYLE LineLengthCheck:ON }; bs.accept(bsv); } private void leave2(BlockStatement bs, IClass optionalStackValueType) {} private void leave2(SynchronizedStatement ss, IClass optionalStackValueType) { this.load(ss, this.iClassLoader.TYPE_java_lang_Object, ss.monitorLvIndex); this.writeOpcode(ss, Opcode.MONITOREXIT); } private void leave2(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(ts, optionalStackValueType, sv); } this.writeBranch(ts, Opcode.JSR, ts.finallyOffset); if (optionalStackValueType != null) { this.load(ts, optionalStackValueType, sv); } } finally { this.codeContext.restoreLocalVariables(); } } } // ---------------- Lvalue.compileSet() ----------------- /** * Generates code that stores a value in the {@link Lvalue}. Expects the {@link Lvalue}'s context (see {@link * #compileContext}) and a value of the {@link Lvalue}'s type on the operand stack. */ private void compileSet(Lvalue lv) throws CompileException { LvalueVisitor lvv = new LvalueVisitor() { // CHECKSTYLE LineLengthCheck:OFF @Override public void visitAmbiguousName(AmbiguousName an) { try { UnitCompiler.this.compileSet2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { UnitCompiler.this.compileSet2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccess(FieldAccess fa) { try { UnitCompiler.this.compileSet2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { UnitCompiler.this.compileSet2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { UnitCompiler.this.compileSet2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { UnitCompiler.this.compileSet2(lva); } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { UnitCompiler.this.compileSet2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { lv.accept(lvv); } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } private void compileSet2(AmbiguousName an) throws CompileException { this.compileSet(this.toLvalueOrCompileException(this.reclassify(an))); } private void compileSet2(LocalVariableAccess lva) { this.store(lva, lva.localVariable); } private void compileSet2(FieldAccess fa) throws CompileException { this.checkAccessible(fa.field, fa.getEnclosingBlockStatement()); this.putfield(fa, fa.field); } private void compileSet2(ArrayAccessExpression aae) throws CompileException { this.writeOpcode(aae, Opcode.IASTORE + UnitCompiler.ilfdabcs(this.getType(aae))); } private void compileSet2(FieldAccessExpression fae) throws CompileException { this.determineValue(fae); this.compileSet(this.toLvalueOrCompileException(fae.value)); } private void compileSet2(SuperclassFieldAccessExpression scfae) throws CompileException { this.determineValue(scfae); this.compileSet(this.toLvalueOrCompileException(scfae.value)); } private void compileSet2(ParenthesizedExpression pe) throws CompileException { this.compileSet(this.toLvalueOrCompileException(pe.value)); } // ---------------- Atom.getType() ---------------- private IClass getType(Atom a) throws CompileException { final IClass[] res = new IClass[1]; AtomVisitor av = new AtomVisitor() { // CHECKSTYLE LineLengthCheck:OFF // AtomVisitor @Override public void visitPackage(Package p) { try { res[0] = UnitCompiler.this.getType2(p); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // TypeVisitor @Override public void visitArrayType(ArrayType at) { try { res[0] = UnitCompiler.this.getType2(at); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBasicType(BasicType bt) { res[0] = UnitCompiler.this.getType2(bt); } @Override public void visitReferenceType(ReferenceType rt) { try { res[0] = UnitCompiler.this.getType2(rt); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitRvalueMemberType(RvalueMemberType rmt) { try { res[0] = UnitCompiler.this.getType2(rmt); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSimpleType(SimpleType st) { res[0] = UnitCompiler.this.getType2(st); } // RvalueVisitor @Override public void visitArrayLength(ArrayLength al) { res[0] = UnitCompiler.this.getType2(al); } @Override public void visitAssignment(Assignment a) { try { res[0] = UnitCompiler.this.getType2(a); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitUnaryOperation(UnaryOperation uo) { try { res[0] = UnitCompiler.this.getType2(uo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitBinaryOperation(BinaryOperation bo) { try { res[0] = UnitCompiler.this.getType2(bo); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCast(Cast c) { try { res[0] = UnitCompiler.this.getType2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitClassLiteral(ClassLiteral cl) { res[0] = UnitCompiler.this.getType2(cl); } @Override public void visitConditionalExpression(ConditionalExpression ce) { try { res[0] = UnitCompiler.this.getType2(ce); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitCrement(Crement c) { try { res[0] = UnitCompiler.this.getType2(c); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitInstanceof(Instanceof io) { res[0] = UnitCompiler.this.getType2(io); } @Override public void visitMethodInvocation(MethodInvocation mi) { try { res[0] = UnitCompiler.this.getType2(mi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { try { res[0] = UnitCompiler.this.getType2(smi); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitIntegerLiteral(IntegerLiteral il) { res[0] = UnitCompiler.this.getType2(il); } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { res[0] = UnitCompiler.this.getType2(fpl); } @Override public void visitBooleanLiteral(BooleanLiteral bl) { res[0] = UnitCompiler.this.getType2(bl); } @Override public void visitCharacterLiteral(CharacterLiteral cl) { res[0] = UnitCompiler.this.getType2(cl); } @Override public void visitStringLiteral(StringLiteral sl) { res[0] = UnitCompiler.this.getType2(sl); } @Override public void visitNullLiteral(NullLiteral nl) { res[0] = UnitCompiler.this.getType2(nl); } @Override public void visitSimpleConstant(SimpleConstant sl) { res[0] = UnitCompiler.this.getType2(sl); } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.getType2(naci); } @Override public void visitNewArray(NewArray na) { try { res[0] = UnitCompiler.this.getType2(na); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewInitializedArray(NewInitializedArray nia) { try { res[0] = UnitCompiler.this.getType2(nia); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitNewClassInstance(NewClassInstance nci) { try { res[0] = UnitCompiler.this.getType2(nci); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitParameterAccess(ParameterAccess pa) { try { res[0] = UnitCompiler.this.getType2(pa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { try { res[0] = UnitCompiler.this.getType2(qtr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitThisReference(ThisReference tr) { try { res[0] = UnitCompiler.this.getType2(tr); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // LvalueVisitor @Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.getType2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { try { res[0] = UnitCompiler.this.getType2(aae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccess(FieldAccess fa) { try { res[0] = UnitCompiler.this.getType2(fa); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { try { res[0] = UnitCompiler.this.getType2(fae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { try { res[0] = UnitCompiler.this.getType2(scfae); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.getType2(lva); } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { try { res[0] = UnitCompiler.this.getType2(pe); } catch (CompileException e) { throw new UncheckedCompileException(e); } } // CHECKSTYLE LineLengthCheck:ON }; try { a.accept(av); return res[0] != null ? res[0] : this.iClassLoader.TYPE_java_lang_Object; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } @SuppressWarnings("static-method") private IClass getType2(SimpleType st) { return st.iClass; } @SuppressWarnings("static-method") private IClass getType2(BasicType bt) { switch (bt.index) { case BasicType.VOID: return IClass.VOID; case BasicType.BYTE: return IClass.BYTE; case BasicType.SHORT: return IClass.SHORT; case BasicType.CHAR: return IClass.CHAR; case BasicType.INT: return IClass.INT; case BasicType.LONG: return IClass.LONG; case BasicType.FLOAT: return IClass.FLOAT; case BasicType.DOUBLE: return IClass.DOUBLE; case BasicType.BOOLEAN: return IClass.BOOLEAN; default: throw new JaninoRuntimeException("Invalid index " + bt.index); } } private IClass getType2(ReferenceType rt) throws CompileException { String[] identifiers = rt.identifiers; IClass result = this.getReferenceType( rt.getLocation(), rt.getEnclosingScope(), identifiers, identifiers.length ); if (result == null) { this.compileError("Reference type '" + rt + "' not found", rt.getLocation()); return this.iClassLoader.TYPE_java_lang_Object; } return result; } /** @return The resolved {@link IClass}, or {@code null} */ private IClass getReferenceType(Location location, Scope scope, String[] identifiers, int n) throws CompileException { if (n == 1) { return this.getReferenceType(location, identifiers[0], scope); } // JLS7 6.5.5.1 Unnamed package member type name (one identifier). // JLS7 6.5.5.2.1 Qualified type name (two or more identifiers). { String className = Java.join(identifiers, ".", 0, n); IClass result = this.findTypeByName(location, className); if (result != null) return result; } // JLS7 6.5.5.2.2 referenceType '.' memberTypeName if (n >= 2) { IClass enclosingType = this.getReferenceType(location, scope, identifiers, n - 1); if (enclosingType != null) { String memberTypeName = identifiers[n - 1]; IClass memberType = this.findMemberType(enclosingType, memberTypeName, location); if (memberType == null) { this.compileError( "'" + enclosingType + "' declares no member type '" + memberTypeName + "'", location ); return this.iClassLoader.TYPE_java_lang_Object; } return memberType; } } return null; } /** * JLS7 6.5.5.1 Simple type name (single identifier) * * @return The resolved {@link IClass}, or {@code null} */ private IClass getReferenceType(Location location, String simpleTypeName, Scope scope) throws CompileException { BlockStatement scopeBlockStatement = null; TypeDeclaration scopeTypeDeclaration = null; CompilationUnit scopeCompilationUnit; for (Scope s = scope.getEnclosingScope();; s = s.getEnclosingScope()) { if (s instanceof BlockStatement && scopeBlockStatement == null) { scopeBlockStatement = (BlockStatement) s; } if (s instanceof TypeDeclaration && scopeTypeDeclaration == null) { scopeTypeDeclaration = (TypeDeclaration) s; } if (s instanceof CompilationUnit) { scopeCompilationUnit = (CompilationUnit) s; break; } } // JLS7 ??? Type variable. if (scopeTypeDeclaration instanceof NamedTypeDeclaration) { TypeParameter[] optionalTypeParameters = ((NamedTypeDeclaration) scopeTypeDeclaration).getOptionalTypeParameters(); if (optionalTypeParameters != null) { for (TypeParameter tp : optionalTypeParameters) { if (tp.name.equals(simpleTypeName)) { IClass[] boundTypes; if (tp.optionalBound == null) { boundTypes = new IClass[] { this.iClassLoader.TYPE_java_lang_Object }; } else { boundTypes = new IClass[tp.optionalBound.length]; for (int i = 0; i < boundTypes.length; i++) { boundTypes[i] = this.getType(tp.optionalBound[i]); } } return boundTypes[0]; } } } } // 6.5.5.1.1 Local class. { LocalClassDeclaration lcd = UnitCompiler.findLocalClassDeclaration( scope, simpleTypeName ); if (lcd != null) return this.resolve(lcd); } // 6.5.5.1.2 Member type. if (scopeTypeDeclaration != null) { // If enclosed by another type declaration... for ( Scope s = scopeTypeDeclaration; !(s instanceof CompilationUnit); s = s.getEnclosingScope() ) { if (s instanceof TypeDeclaration) { IClass mt = this.findMemberType( this.resolve((AbstractTypeDeclaration) s), simpleTypeName, location ); if (mt != null) return mt; } } } // 6.5.5.1.4a Single-type import. { IClass importedClass = this.importSingleType(simpleTypeName, location); if (importedClass != null) return importedClass; } // 6.5.5.1.4b Type declared in same compilation unit. { PackageMemberTypeDeclaration pmtd = ( scopeCompilationUnit.getPackageMemberTypeDeclaration(simpleTypeName) ); if (pmtd != null) return this.resolve(pmtd); } // 6.5.5.1.5 Type declared in other compilation unit of same package. { String pkg = ( scopeCompilationUnit.optionalPackageDeclaration == null ? null : scopeCompilationUnit.optionalPackageDeclaration.packageName ); String className = pkg == null ? simpleTypeName : pkg + "." + simpleTypeName; IClass result = this.findTypeByName(location, className); if (result != null) return result; } // 6.5.5.1.6 Type-import-on-demand declaration. { IClass importedClass = this.importTypeOnDemand(simpleTypeName, location); if (importedClass != null) return importedClass; } // JLS7 6.5.2.BL1.B2: Type imported through single 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 (!this.isAccessible(mt, scopeBlockStatement)) continue; if (importedMemberType != null && importedMemberType != mt) { this.compileError( "Ambiguous static imports: \"" + importedMemberType.toString() + "\" vs. \"" + mt.toString() + "\"" ); } importedMemberType = mt; } } if (importedMemberType != null) return importedMemberType; } } // JLS7 6.5.2.BL1.B2: Type imported through static-import-on-demand. { IClass importedMemberType = null; for (IClass ic : this.staticImportsOnDemand) { IClass[] memberTypes = ic.getDeclaredIClasses(); for (IClass mt : memberTypes) { if (!this.isAccessible(mt, scopeBlockStatement)) continue; if (mt.getDescriptor().endsWith('$' + simpleTypeName + ';')) { if (importedMemberType != null) { this.compileError( "Ambiguous static imports: \"" + importedMemberType.toString() + "\" vs. \"" + mt.toString() + "\"" ); } importedMemberType = mt; } } } if (importedMemberType != null) return importedMemberType; } // Unnamed package member type. { IClass result = this.findTypeByName(location, simpleTypeName); if (result != null) return result; } // 6.5.5.1.8 Give up. this.compileError("Cannot determine simple type name \"" + simpleTypeName + "\"", location); return this.iClassLoader.TYPE_java_lang_Object; } private IClass getType2(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(ArrayType at) throws CompileException { return this.getType(at.componentType).getArrayIClass(this.iClassLoader.TYPE_java_lang_Object); } private IClass getType2(AmbiguousName an) throws CompileException { return this.getType(this.reclassify(an)); } private IClass getType2(Package p) throws CompileException { this.compileError("Unknown variable or type \"" + p.name + "\"", p.getLocation()); return this.iClassLoader.TYPE_java_lang_Object; } @SuppressWarnings("static-method") private IClass getType2(LocalVariableAccess lva) { return lva.localVariable.type; } @SuppressWarnings("static-method") private IClass getType2(FieldAccess fa) throws CompileException { return fa.field.getType(); } @SuppressWarnings("static-method") private IClass getType2(ArrayLength al) { return IClass.INT; } private IClass getType2(ThisReference tr) throws CompileException { return this.getIClass(tr); } private IClass getType2(QualifiedThisReference qtr) throws CompileException { return this.getTargetIClass(qtr); } private IClass getType2(ClassLiteral cl) { return this.iClassLoader.TYPE_java_lang_Class; } private IClass getType2(Assignment a) throws CompileException { return this.getType(a.lhs); } private IClass getType2(ConditionalExpression ce) throws CompileException { IClass mhsType = this.getType(ce.mhs); IClass rhsType = this.getType(ce.rhs); if (mhsType == rhsType) { // JLS7 15.25, list 1, bullet 1: "b ? T : T => T" return mhsType; } else if (this.isUnboxingConvertible(mhsType) == rhsType) { // JLS7 15.25, list 1, bullet 2: "b ? Integer : int => int" return rhsType; } else if (this.isUnboxingConvertible(rhsType) == mhsType) { // JLS7 15.25, list 1, bullet 2: "b ? int : Integer => int" return mhsType; } else if (this.getConstantValue(ce.mhs) == null && !rhsType.isPrimitive()) { // JLS7 15.25, list 1, bullet 3: "b ? null : ReferenceType => ReferenceType" return rhsType; } else if (!mhsType.isPrimitive() && this.getConstantValue(ce.rhs) == null) { // JLS7 15.25, list 1, bullet 3: "b ? ReferenceType : null => ReferenceType" return mhsType; } else if (this.isConvertibleToPrimitiveNumeric(mhsType) && this.isConvertibleToPrimitiveNumeric(rhsType)) { // JLS7 15.25, list 1, bullet 4, bullet 1: "b ? Byte : Short => short" if ( (mhsType == IClass.BYTE || mhsType == this.iClassLoader.TYPE_java_lang_Byte) && (rhsType == IClass.SHORT || rhsType == this.iClassLoader.TYPE_java_lang_Short) ) return IClass.SHORT; if ( (rhsType == IClass.BYTE || rhsType == this.iClassLoader.TYPE_java_lang_Byte) && (mhsType == IClass.SHORT || mhsType == this.iClassLoader.TYPE_java_lang_Short) ) return IClass.SHORT; // JLS7 15.25, list 1, bullet 4, bullet 2: "b ? 127 : byte => byte" if ( (mhsType == IClass.BYTE || mhsType == IClass.SHORT || mhsType == IClass.CHAR) && ce.rhs.constantValue != null && this.assignmentConversion(ce.rhs, ce.rhs.constantValue, mhsType) != null ) return mhsType; if ( (rhsType == IClass.BYTE || rhsType == IClass.SHORT || rhsType == IClass.CHAR) && ce.mhs.constantValue != null && this.assignmentConversion(ce.mhs, ce.mhs.constantValue, rhsType) != null ) return rhsType; // TODO JLS7 15.25, list 1, bullet 4, bullet 3: "b ? 127 : byte => byte" // JLS7 15.25, list 1, bullet 4, bullet 4: "b ? Integer : Double => double" return this.binaryNumericPromotionType(ce, this.getUnboxedType(mhsType), this.getUnboxedType(rhsType)); } else if (!mhsType.isPrimitive() && !rhsType.isPrimitive()) { // JLS7 15.25, list 1, bullet 5: "b ? Base : Derived => Base" 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.TYPE_java_lang_Object; } } else { this.compileError( "Incompatible expression types \"" + mhsType + "\" and \"" + rhsType + "\"", ce.getLocation() ); return this.iClassLoader.TYPE_java_lang_Object; } } private IClass getType2(Crement c) throws CompileException { return this.getType(c.operand); } private IClass getType2(ArrayAccessExpression aae) throws CompileException { return this.getType(aae.lhs).getComponentType(); } private IClass getType2(FieldAccessExpression fae) throws CompileException { this.determineValue(fae); return this.getType(fae.value); } private IClass getType2(SuperclassFieldAccessExpression scfae) throws CompileException { this.determineValue(scfae); return this.getType(scfae.value); } private IClass getType2(UnaryOperation uo) throws CompileException { if (uo.operator == "!") return IClass.BOOLEAN; // SUPPRESS CHECKSTYLE StringLiteralEquality // SUPPRESS CHECKSTYLE StringLiteralEquality if (uo.operator == "+" || uo.operator == "-" || uo.operator == "~") { return this.unaryNumericPromotionType(uo, this.getUnboxedType(this.getType(uo.operand))); } this.compileError("Unexpected operator \"" + uo.operator + "\"", uo.getLocation()); return IClass.BOOLEAN; } @SuppressWarnings("static-method") private IClass getType2(Instanceof io) { return IClass.BOOLEAN; } private IClass getType2(BinaryOperation bo) throws CompileException { if ( // CHECKSTYLE StringLiteralEquality:OFF bo.op == "||" || bo.op == "&&" || bo.op == "==" || bo.op == "!=" || bo.op == "<" || bo.op == ">" || bo.op == "<=" || bo.op == ">=" // CHECKSTYLE StringLiteralEquality:ON ) return IClass.BOOLEAN; if (bo.op == "|" || bo.op == "^" || bo.op == "&") { // SUPPRESS CHECKSTYLE StringLiteralEquality IClass lhsType = this.getType(bo.lhs); return ( lhsType == IClass.BOOLEAN || lhsType == this.iClassLoader.TYPE_java_lang_Boolean ? IClass.BOOLEAN : this.binaryNumericPromotionType(bo, lhsType, this.getType(bo.rhs)) ); } // SUPPRESS CHECKSTYLE StringLiteralEquality 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(((Rvalue) ops.next()))); if (bo.op == "+" && lhsType == icl.TYPE_java_lang_String) { // SUPPRESS CHECKSTYLE StringLiteralEquality return icl.TYPE_java_lang_String; } // Determine the expression type. do { IClass rhsType = this.getUnboxedType(this.getType(((Rvalue) ops.next()))); if (bo.op == "+" && rhsType == icl.TYPE_java_lang_String) { // SUPPRESS CHECKSTYLE StringLiteralEquality return icl.TYPE_java_lang_String; } lhsType = this.binaryNumericPromotionType(bo, lhsType, rhsType); } while (ops.hasNext()); return lhsType; } if (bo.op == "<<" || bo.op == ">>" || bo.op == ">>>") { // SUPPRESS CHECKSTYLE StringLiteralEquality IClass lhsType = this.getType(bo.lhs); return this.unaryNumericPromotionType(bo, lhsType); } this.compileError("Unexpected operator \"" + bo.op + "\"", bo.getLocation()); return this.iClassLoader.TYPE_java_lang_Object; } /** @return Iff {@code type} is a primitive wrapper type, the unwrapped {@code type}, otherwise {@code type} */ private IClass getUnboxedType(IClass type) { IClass c = this.isUnboxingConvertible(type); return c != null ? c : type; } private IClass getType2(Cast c) throws CompileException { return this.getType(c.targetType); } private IClass getType2(ParenthesizedExpression pe) throws CompileException { return this.getType(pe.value); } private IClass getType2(MethodInvocation mi) throws CompileException { //TODO: cache this (as blackhole) if (mi.iMethod == null) { mi.iMethod = this.findIMethod(mi); } return mi.iMethod.getReturnType(); } private IClass getType2(SuperclassMethodInvocation scmi) throws CompileException { return this.findIMethod(scmi).getReturnType(); } private IClass getType2(NewClassInstance nci) throws CompileException { if (nci.iClass == null) nci.iClass = this.getType(nci.type); return nci.iClass; } private IClass getType2(NewAnonymousClassInstance naci) { return this.resolve(naci.anonymousClassDeclaration); } private IClass getType2(ParameterAccess pa) throws CompileException { return this.getLocalVariable(pa.formalParameter).type; } private IClass getType2(NewArray na) throws CompileException { IClass res = this.getType(na.type); return res.getArrayIClass(na.dimExprs.length + na.dims, this.iClassLoader.TYPE_java_lang_Object); } private IClass getType2(NewInitializedArray nia) throws CompileException { return nia.arrayType == null ? nia.arrayIClass : this.getType(nia.arrayType); } @SuppressWarnings("static-method") private IClass getType2(IntegerLiteral il) { String v = il.value; char lastChar = v.charAt(v.length() - 1); return lastChar == 'l' || lastChar == 'L' ? IClass.LONG : IClass.INT; } @SuppressWarnings("static-method") private IClass getType2(FloatingPointLiteral fpl) { String v = fpl.value; char lastChar = v.charAt(v.length() - 1); return lastChar == 'f' || lastChar == 'F' ? IClass.FLOAT : IClass.DOUBLE; } @SuppressWarnings("static-method") private IClass getType2(BooleanLiteral bl) { return IClass.BOOLEAN; } @SuppressWarnings("static-method") private IClass getType2(CharacterLiteral cl) { return IClass.CHAR; } private IClass getType2(StringLiteral sl) { return this.iClassLoader.TYPE_java_lang_String; } @SuppressWarnings("static-method") private IClass getType2(NullLiteral nl) { return IClass.VOID; } private IClass getType2(SimpleConstant sl) { Object v = sl.value; if (v instanceof Byte) return IClass.BYTE; if (v instanceof Short) return IClass.SHORT; if (v instanceof Integer) return IClass.INT; if (v instanceof Long) return IClass.LONG; if (v instanceof Float) return IClass.FLOAT; if (v instanceof Double) return IClass.DOUBLE; if (v instanceof Boolean) return IClass.BOOLEAN; if (v instanceof Character) return IClass.CHAR; if (v instanceof String) return this.iClassLoader.TYPE_java_lang_String; if (v == null) return IClass.VOID; throw new JaninoRuntimeException("Invalid SimpleLiteral value type '" + v.getClass() + "'"); } // ---------------- Atom.isType() --------------- private boolean isType(Atom a) throws CompileException { final boolean[] res = new boolean[1]; AtomVisitor av = new AtomVisitor() { // CHECKSTYLE LineLengthCheck:OFF // AtomVisitor @Override public void visitPackage(Package p) { res[0] = UnitCompiler.this.isType2(p); } // TypeVisitor @Override public void visitArrayType(ArrayType at) { res[0] = UnitCompiler.this.isType2(at); } @Override public void visitBasicType(BasicType bt) { res[0] = UnitCompiler.this.isType2(bt); } @Override public void visitReferenceType(ReferenceType rt) { res[0] = UnitCompiler.this.isType2(rt); } @Override public void visitRvalueMemberType(RvalueMemberType rmt) { res[0] = UnitCompiler.this.isType2(rmt); } @Override public void visitSimpleType(SimpleType st) { res[0] = UnitCompiler.this.isType2(st); } // RvalueVisitor @Override public void visitArrayLength(ArrayLength al) { res[0] = UnitCompiler.this.isType2(al); } @Override public void visitAssignment(Assignment a) { res[0] = UnitCompiler.this.isType2(a); } @Override public void visitUnaryOperation(UnaryOperation uo) { res[0] = UnitCompiler.this.isType2(uo); } @Override public void visitBinaryOperation(BinaryOperation bo) { res[0] = UnitCompiler.this.isType2(bo); } @Override public void visitCast(Cast c) { res[0] = UnitCompiler.this.isType2(c); } @Override public void visitClassLiteral(ClassLiteral cl) { res[0] = UnitCompiler.this.isType2(cl); } @Override public void visitConditionalExpression(ConditionalExpression ce) { res[0] = UnitCompiler.this.isType2(ce); } @Override public void visitCrement(Crement c) { res[0] = UnitCompiler.this.isType2(c); } @Override public void visitInstanceof(Instanceof io) { res[0] = UnitCompiler.this.isType2(io); } @Override public void visitMethodInvocation(MethodInvocation mi) { res[0] = UnitCompiler.this.isType2(mi); } @Override public void visitSuperclassMethodInvocation(SuperclassMethodInvocation smi) { res[0] = UnitCompiler.this.isType2(smi); } @Override public void visitIntegerLiteral(IntegerLiteral il) { res[0] = UnitCompiler.this.isType2(il); } @Override public void visitFloatingPointLiteral(FloatingPointLiteral fpl) { res[0] = UnitCompiler.this.isType2(fpl); } @Override public void visitBooleanLiteral(BooleanLiteral bl) { res[0] = UnitCompiler.this.isType2(bl); } @Override public void visitCharacterLiteral(CharacterLiteral cl) { res[0] = UnitCompiler.this.isType2(cl); } @Override public void visitStringLiteral(StringLiteral sl) { res[0] = UnitCompiler.this.isType2(sl); } @Override public void visitNullLiteral(NullLiteral nl) { res[0] = UnitCompiler.this.isType2(nl); } @Override public void visitSimpleConstant(SimpleConstant sl) { res[0] = UnitCompiler.this.isType2(sl); } @Override public void visitNewAnonymousClassInstance(NewAnonymousClassInstance naci) { res[0] = UnitCompiler.this.isType2(naci); } @Override public void visitNewArray(NewArray na) { res[0] = UnitCompiler.this.isType2(na); } @Override public void visitNewInitializedArray(NewInitializedArray nia) { res[0] = UnitCompiler.this.isType2(nia); } @Override public void visitNewClassInstance(NewClassInstance nci) { res[0] = UnitCompiler.this.isType2(nci); } @Override public void visitParameterAccess(ParameterAccess pa) { res[0] = UnitCompiler.this.isType2(pa); } @Override public void visitQualifiedThisReference(QualifiedThisReference qtr) { res[0] = UnitCompiler.this.isType2(qtr); } @Override public void visitThisReference(ThisReference tr) { res[0] = UnitCompiler.this.isType2(tr); } // LvalueVisitor @Override public void visitAmbiguousName(AmbiguousName an) { try { res[0] = UnitCompiler.this.isType2(an); } catch (CompileException e) { throw new UncheckedCompileException(e); } } @Override public void visitArrayAccessExpression(ArrayAccessExpression aae) { res[0] = UnitCompiler.this.isType2(aae); } @Override public void visitFieldAccess(FieldAccess fa) { res[0] = UnitCompiler.this.isType2(fa); } @Override public void visitFieldAccessExpression(FieldAccessExpression fae) { res[0] = UnitCompiler.this.isType2(fae); } @Override public void visitSuperclassFieldAccessExpression(SuperclassFieldAccessExpression scfae) { res[0] = UnitCompiler.this.isType2(scfae); } @Override public void visitLocalVariableAccess(LocalVariableAccess lva) { res[0] = UnitCompiler.this.isType2(lva); } @Override public void visitParenthesizedExpression(ParenthesizedExpression pe) { res[0] = UnitCompiler.this.isType2(pe); } // CHECKSTYLE LineLengthCheck:ON }; try { a.accept(av); return res[0]; } catch (UncheckedCompileException uce) { throw uce.compileException; // SUPPRESS CHECKSTYLE AvoidHidingCause } } @SuppressWarnings("static-method") private boolean isType2(Atom a) { return a instanceof Type; } private boolean isType2(AmbiguousName an) throws CompileException { return this.isType(this.reclassify(an)); } /** * Determines whether the given {@link IClass.IMember} is accessible in the given context, according to * JLS7 6.6.1.BL1.B4. Issues a {@link #compileError(String)} if not. */ private boolean isAccessible(IClass.IMember member, Scope contextScope) throws CompileException { // You have to check that both the class and member are accessible in this scope. IClass declaringIClass = member.getDeclaringIClass(); boolean acc = this.isAccessible(declaringIClass, contextScope); acc = acc && this.isAccessible(declaringIClass, member.getAccess(), contextScope); return acc; } /** * Checks whether the given {@link IClass.IMember} is accessible in the given context, according to JLS7 * 6.6.1.BL1.B4. Issues a {@link #compileError(String)} if not. */ private void checkAccessible(IClass.IMember member, 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); } /** * Determines whether a member (class, interface, field or method) declared in a given class is accessible from a * given block statement context, according to JLS7 6.6.1.4. */ private boolean isAccessible(IClass iClassDeclaringMember, Access memberAccess, Scope contextScope) throws CompileException { return null == this.internalCheckAccessible(iClassDeclaringMember, memberAccess, contextScope); } /** * Verifies that a member (class, interface, field or method) declared in a given class is accessible from a given * block statement context, according to JLS7 6.6.1.4. Issue a {@link #compileError(String)} if not. */ private void checkAccessible( IClass iClassDeclaringMember, Access memberAccess, 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, Scope contextScope ) throws CompileException { // At this point, memberAccess is PUBLIC, DEFAULT, PROTECTED or PRIVATE. // PUBLIC members are always accessible. if (memberAccess == Access.PUBLIC) return null; // At this point, the member is DEFAULT, PROECTEDED or PRIVATE accessible. // Determine the class declaring the context. IClass iClassDeclaringContext = null; for (Scope s = contextScope; !(s instanceof CompilationUnit); s = s.getEnclosingScope()) { if (s instanceof TypeDeclaration) { iClassDeclaringContext = this.resolve((TypeDeclaration) s); break; } } // Access is always allowed for block statements declared in the same class as the member. if (iClassDeclaringContext == iClassDeclaringMember) return null; // Check whether the member and the context block statement are enclosed by the same top-level type. if (iClassDeclaringContext != null) { IClass topLevelIClassEnclosingMember = iClassDeclaringMember; for (IClass c = iClassDeclaringMember.getDeclaringIClass(); c != null; c = c.getDeclaringIClass()) { topLevelIClassEnclosingMember = c; } IClass topLevelIClassEnclosingContextBlockStatement = iClassDeclaringContext; for ( IClass c = iClassDeclaringContext.getDeclaringIClass(); c != null; c = c.getDeclaringIClass() ) topLevelIClassEnclosingContextBlockStatement = c; if (topLevelIClassEnclosingMember == topLevelIClassEnclosingContextBlockStatement) return null; } if (memberAccess == Access.PRIVATE) { return "Private member cannot be accessed from type \"" + iClassDeclaringContext + "\"."; } // At this point, the member is DEFAULT or PROTECTED accessible. // Check whether the member and the context block statement are declared in the same package. if (Descriptor.areInSamePackage( iClassDeclaringMember.getDescriptor(), iClassDeclaringContext.getDescriptor() )) return null; if (memberAccess == Access.DEFAULT) { return ( "Member with \"" + memberAccess + "\" access cannot be accessed from type \"" + iClassDeclaringContext + "\"." ); } // At this point, the member is PROTECTED accessible. // Check whether the class declaring the context block statement is a subclass of the class declaring the // member or a nested class whose parent is a subclass { IClass parentClass = iClassDeclaringContext; do { if (iClassDeclaringMember.isAssignableFrom(parentClass)) { return null; } parentClass = parentClass.getOuterIClass(); } while (parentClass != null); } return ( "Protected member cannot be accessed from type \"" + iClassDeclaringContext + "\", which is neither declared in the same package as nor is a subclass of \"" + iClassDeclaringMember + "\"." ); } /** * Determines whether the given {@link IClass} is accessible in the given context, according to JLS7 6.6.1.2 and * 6.6.1.4. */ private boolean isAccessible(IClass type, Scope contextScope) throws CompileException { return null == this.internalCheckAccessible(type, contextScope); } /** * Checks whether the given {@link IClass} is accessible in the given context, according to JLS7 6.6.1.2 and * 6.6.1.4. Issues a {@link #compileError(String)} if not. */ private void checkAccessible(IClass type, BlockStatement contextBlockStatement) throws CompileException { String message = this.internalCheckAccessible(type, contextBlockStatement); if (message != null) this.compileError(message, contextBlockStatement.getLocation()); } private String internalCheckAccessible(IClass type, Scope contextScope) throws CompileException { // Determine the type declaring the type. IClass iClassDeclaringType = type.getDeclaringIClass(); // Check accessibility of package member type. if (iClassDeclaringType == null) { if (type.getAccess() == Access.PUBLIC) { return null; } else if (type.getAccess() == Access.DEFAULT) { // Determine the type declaring the context block statement. IClass iClassDeclaringContextBlockStatement; for (Scope s = contextScope;; s = s.getEnclosingScope()) { if (s instanceof TypeDeclaration) { iClassDeclaringContextBlockStatement = this.resolve((TypeDeclaration) s); break; } if (s instanceof EnclosingScopeOfTypeDeclaration) { iClassDeclaringContextBlockStatement = this.resolve( ((EnclosingScopeOfTypeDeclaration) s).typeDeclaration ); break; } } // Check whether the type is accessed from within the same package. String packageDeclaringType = Descriptor.getPackageName(type.getDescriptor()); String contextPackage = Descriptor.getPackageName(iClassDeclaringContextBlockStatement.getDescriptor()); if ( packageDeclaringType == null ? contextPackage != null : !packageDeclaringType.equals(contextPackage) ) return "\"" + type + "\" is inaccessible from this package"; return null; } else { throw new JaninoRuntimeException("\"" + type + "\" has unexpected access \"" + type.getAccess() + "\""); } } // "type" is a member type at this point. return this.internalCheckAccessible(iClassDeclaringType, type.getAccess(), contextScope); } private Type toTypeOrCompileException(Atom a) throws CompileException { Type result = a.toType(); if (result == null) { this.compileError("Expression \"" + a.toString() + "\" is not a type", a.getLocation()); return new SimpleType(a.getLocation(), this.iClassLoader.TYPE_java_lang_Object); } return result; } private Rvalue toRvalueOrCompileException(final Atom a) throws CompileException { Rvalue result = a.toRvalue(); if (result == null) { this.compileError("Expression \"" + a.toString() + "\" is not an rvalue", a.getLocation()); return new StringLiteral(a.getLocation(), "\"X\""); } return result; } private Lvalue toLvalueOrCompileException(final Atom a) throws CompileException { Lvalue result = a.toLvalue(); if (result == null) { this.compileError("Expression \"" + a.toString() + "\" is not an lvalue", a.getLocation()); return new Lvalue(a.getLocation()) { @Override public String toString() { return a.toString(); } @Override public void accept(AtomVisitor visitor) {} @Override public void accept(RvalueVisitor visitor) {} @Override public void accept(LvalueVisitor visitor) {} @Override public void accept(ElementValueVisitor 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(ConstructorDeclarator cd) throws CompileException { for (IClass.IField sf : cd.getDeclaringClass().syntheticFields.values()) { LocalVariable syntheticParameter = (LocalVariable) cd.syntheticParameters.get(sf.getName()); if (syntheticParameter == null) { throw new JaninoRuntimeException( "SNO: Synthetic parameter for synthetic field \"" + sf.getName() + "\" not found" ); } ExpressionStatement es = new ExpressionStatement(new Assignment( cd.getLocation(), // location new FieldAccess( // lhs cd.getLocation(), // location new ThisReference(cd.getLocation()), // lhs sf // field ), "=", // operator new LocalVariableAccess( // rhs cd.getLocation(), // location syntheticParameter // localVariable ) )); es.setEnclosingScope(cd); this.compile(es); } } /** Compiles the instance variable initializers and the instance initializers in their lexical order. */ void initializeInstanceVariablesAndInvokeInstanceInitializers(ConstructorDeclarator cd) throws CompileException { // Compilation of block statments can create synthetic variables, so we must not use an iterator. List vdai = cd.getDeclaringClass().variableDeclaratorsAndInitializers; for (int i = 0; i < vdai.size(); i++) { BlockStatement bs = (BlockStatement) vdai.get(i); if (!((TypeBodyDeclaration) bs).isStatic()) { 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(Scope from, Scope to, IClass optionalStackValueType) { for (Scope s = from; s != to; s = s.getEnclosingScope()) { if (s instanceof BlockStatement) { this.leave((BlockStatement) s, optionalStackValueType); } } } /** * The LHS operand of type {@code lhsType} is expected on the stack. *

* The following operators are supported: {@code   | ^ & * / % + - << >> >>>} */ private IClass compileArithmeticBinaryOperation( Locatable locatable, IClass lhsType, String operator, Rvalue rhs ) throws CompileException { return this.compileArithmeticOperation( locatable, lhsType, Arrays.asList(new Rvalue[] { rhs }).iterator(), operator ); } /** * Execute an arithmetic operation on a sequence of {@code operands}. If {@code type} is non-null, the first * operand with that type is already on the stack. *

* The following operators are supported: {@code   | ^ & * / % + - << >> >>>} */ private IClass compileArithmeticOperation( final Locatable locatable, IClass type, Iterator operands, String operator ) throws CompileException { if (operator == "|" || operator == "^" || operator == "&") { // SUPPRESS CHECKSTYLE StringLiteralEquality final int iopcode = ( // CHECKSTYLE StringLiteralEquality:OFF operator == "&" ? Opcode.IAND : operator == "|" ? Opcode.IOR : operator == "^" ? Opcode.IXOR : // CHECKSTYLE StringLiteralEquality:OFF Integer.MAX_VALUE ); do { Rvalue operand = (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(locatable, type, convertLhsInserter, rhsType); if (promotedType == IClass.INT) { this.writeOpcode(locatable, iopcode); } else if (promotedType == IClass.LONG) { this.writeOpcode(locatable, iopcode + 1); } else { this.compileError(( "Operator \"" + operator + "\" not defined on types \"" + type + "\" and \"" + rhsType + "\"" ), locatable.getLocation()); } type = promotedType; } else if ( (type == IClass.BOOLEAN || this.getUnboxedType(type) == IClass.BOOLEAN) && (rhsType == IClass.BOOLEAN || this.getUnboxedType(rhsType) == IClass.BOOLEAN) ) { IClassLoader icl = this.iClassLoader; if (type == icl.TYPE_java_lang_Boolean) { this.codeContext.pushInserter(convertLhsInserter); try { this.unboxingConversion(locatable, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN); } finally { this.codeContext.popInserter(); } } if (rhsType == icl.TYPE_java_lang_Boolean) { this.unboxingConversion(locatable, icl.TYPE_java_lang_Boolean, IClass.BOOLEAN); } this.writeOpcode(locatable, iopcode); type = IClass.BOOLEAN; } else { this.compileError(( "Operator \"" + operator + "\" not defined on types \"" + type + "\" and \"" + rhsType + "\"" ), locatable.getLocation()); type = IClass.INT; } } } while (operands.hasNext()); return type; } // SUPPRESS CHECKSTYLE StringLiteralEquality 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 { Rvalue operand = (Rvalue) operands.next(); IClass operandType = this.getType(operand); IClassLoader icl = this.iClassLoader; // String concatenation? // SUPPRESS CHECKSTYLE StringLiteralEquality if ( operator == "+" && (type == icl.TYPE_java_lang_String || operandType == icl.TYPE_java_lang_String) ) { return this.compileStringConcatenation(locatable, 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(locatable, 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 + "\"", locatable.getLocation()); opcode = iopcode; } this.writeOpcode(locatable, opcode); } } while (operands.hasNext()); return type; } if (operator == "<<" || operator == ">>" || operator == ">>>") { // SUPPRESS CHECKSTYLE StringLiteralEquality final int iopcode = ( operator == "<<" ? Opcode.ISHL : operator == ">>" ? Opcode.ISHR : operator == ">>>" ? Opcode.IUSHR : Integer.MAX_VALUE ); do { Rvalue operand = (Rvalue) operands.next(); if (type == null) { type = this.compileGetValue(operand); } else { CodeContext.Inserter convertLhsInserter = this.codeContext.newInserter(); final IClass rhsType = this.compileGetValue(operand); IClass promotedLhsType; this.codeContext.pushInserter(convertLhsInserter); try { promotedLhsType = this.unaryNumericPromotion(locatable, type); } finally { this.codeContext.popInserter(); } if (promotedLhsType != IClass.INT && promotedLhsType != IClass.LONG) { this.compileError( "Shift operation not allowed on operand type \"" + type + "\"", locatable.getLocation() ); } IClass promotedRhsType = this.unaryNumericPromotion(locatable, rhsType); if (promotedRhsType != IClass.INT && promotedRhsType != IClass.LONG) { this.compileError( "Shift distance of type \"" + rhsType + "\" is not allowed", locatable.getLocation() ); } if (promotedRhsType == IClass.LONG) this.writeOpcode(locatable, Opcode.L2I); this.writeOpcode(locatable, 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 Rvalue}s) */ private IClass compileStringConcatenation( final Locatable locatable, IClass type, Rvalue operand, Iterator operands ) throws CompileException { boolean operandOnStack; if (type != null) { this.stringConversion(locatable, type); operandOnStack = true; } else { operandOnStack = false; } // Compute list of operands and merge consecutive constant operands. List tmp = new ArrayList(); do { Object cv = this.getConstantValue(operand); if (cv == UnitCompiler.NOT_CONSTANT) { // Non-constant operand. final Rvalue finalOperand = operand; tmp.add(new Compilable() { @Override public void compile() throws CompileException { UnitCompiler.this.stringConversion(locatable, UnitCompiler.this.compileGetValue(finalOperand)); } }); operand = operands.hasNext() ? (Rvalue) operands.next() : null; } else { // Constant operand. Check to see whether the next operand is also constant. if (operands.hasNext()) { operand = (Rvalue) operands.next(); Object cv2 = this.getConstantValue(operand); if (cv2 != UnitCompiler.NOT_CONSTANT) { StringBuilder sb = new StringBuilder(cv.toString()).append(cv2); for (;;) { if (!operands.hasNext()) { operand = null; break; } operand = (Rvalue) operands.next(); Object cv3 = this.getConstantValue(operand); if (cv3 == UnitCompiler.NOT_CONSTANT) 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 (final String s : ss) { tmp.add(new Compilable() { @Override public void compile() throws CompileException { UnitCompiler.this.pushConstant(locatable, 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 ? UnitCompiler.STRING_CONCAT_LIMIT - 1 : UnitCompiler.STRING_CONCAT_LIMIT)) { // String concatenation through "a.concat(b).concat(c)". for (Compilable c : tmp) { c.compile(); // Concatenate. if (operandOnStack) { this.invoke(locatable, this.iClassLoader.METH_java_lang_String__concat__java_lang_String); } else { operandOnStack = true; } } return this.iClassLoader.TYPE_java_lang_String; } // String concatenation through "new StringBuilder(a).append(b).append(c).append(d).toString()". Iterator it = tmp.iterator(); // "new StringBuilder(String a)": if (operandOnStack) { this.writeOpcode(locatable, Opcode.NEW); this.writeConstantClassInfo(Descriptor.JAVA_LANG_STRINGBUILDER); this.writeOpcode(locatable, Opcode.DUP_X1); this.writeOpcode(locatable, Opcode.SWAP); } else { this.writeOpcode(locatable, Opcode.NEW); this.writeConstantClassInfo(Descriptor.JAVA_LANG_STRINGBUILDER); this.writeOpcode(locatable, Opcode.DUP); ((Compilable) it.next()).compile(); } this.invoke(locatable, this.iClassLoader.CTOR_java_lang_StringBuilder__java_lang_String); while (it.hasNext()) { ((Compilable) it.next()).compile(); // "StringBuilder.append(String b)": this.invoke(locatable, this.iClassLoader.METH_java_lang_StringBuilder__append__java_lang_String); } // "StringBuilder.toString()": this.invoke(locatable, this.iClassLoader.METH_java_lang_StringBuilder__toString); return this.iClassLoader.TYPE_java_lang_String; } /** Helper interface for string conversion. */ interface Compilable { void compile() throws CompileException; } /** Converts object of type "sourceType" to type "String" (JLS7 15.18.1.1). */ private void stringConversion(Locatable locatable, IClass sourceType) throws CompileException { this.invoke(locatable, ( sourceType == IClass.BYTE ? this.iClassLoader.METH_java_lang_String__valueOf__int : sourceType == IClass.SHORT ? this.iClassLoader.METH_java_lang_String__valueOf__int : sourceType == IClass.INT ? this.iClassLoader.METH_java_lang_String__valueOf__int : sourceType == IClass.LONG ? this.iClassLoader.METH_java_lang_String__valueOf__long : sourceType == IClass.FLOAT ? this.iClassLoader.METH_java_lang_String__valueOf__float : sourceType == IClass.DOUBLE ? this.iClassLoader.METH_java_lang_String__valueOf__double : sourceType == IClass.CHAR ? this.iClassLoader.METH_java_lang_String__valueOf__char : sourceType == IClass.BOOLEAN ? this.iClassLoader.METH_java_lang_String__valueOf__boolean : this.iClassLoader.METH_java_lang_String__valueOf__java_lang_Object )); } /** * Expects the object to initialize on the stack. *

* Notice: This method is used both for explicit constructor invocation (first statement of a constructor body) and * implicit constructor invocation (right after NEW). * * @param optionalEnclosingInstance Used if the target class is an inner class */ private void invokeConstructor( Locatable locatable, Scope scope, Rvalue optionalEnclosingInstance, IClass targetClass, 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( locatable, // l iConstructors, // iInvocables arguments, // arguments scope // contextScope ); // Check exceptions that the constructor may throw. IClass[] thrownExceptions = iConstructor.getThrownExceptions(); for (IClass te : thrownExceptions) { this.checkThrownException(locatable, te, scope); } // 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 + "\"", locatable.getLocation() ); } } } // Pass local variables to constructor as synthetic parameters. { IClass.IField[] syntheticFields = targetClass.getSyntheticIFields(); // Determine enclosing function declarator and type declaration. TypeBodyDeclaration scopeTbd; TypeDeclaration scopeTypeDeclaration; { Scope s = scope; for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope()); scopeTbd = (TypeBodyDeclaration) s; scopeTypeDeclaration = scopeTbd.getDeclaringType(); } if (!(scopeTypeDeclaration instanceof ClassDeclaration)) { if (syntheticFields.length > 0) { throw new JaninoRuntimeException("SNO: Target class has synthetic fields"); } return; } ClassDeclaration scopeClassDeclaration = (ClassDeclaration) scopeTypeDeclaration; for (IClass.IField sf : syntheticFields) { if (!sf.getName().startsWith("val$")) continue; IClass.IField eisf = (IClass.IField) scopeClassDeclaration.syntheticFields.get(sf.getName()); if (eisf != null) { if (scopeTbd instanceof MethodDeclarator) { this.load(locatable, this.resolve(scopeClassDeclaration), 0); this.getfield(locatable, eisf); } else if (scopeTbd instanceof ConstructorDeclarator) { ConstructorDeclarator constructorDeclarator = (ConstructorDeclarator) scopeTbd; LocalVariable syntheticParameter = ( (LocalVariable) constructorDeclarator.syntheticParameters.get(sf.getName()) ); if (syntheticParameter == null) { this.compileError(( "Compiler limitation: Constructor cannot access local variable \"" + sf.getName().substring(4) + "\" declared in an enclosing block because none of the methods accesses it. " + "As a workaround, declare a dummy method that accesses the local variable." ), locatable.getLocation()); this.writeOpcode(locatable, Opcode.ACONST_NULL); } else { this.load(locatable, syntheticParameter); } } else if (scopeTbd instanceof FieldDeclaration) { this.compileError(( "Compiler limitation: Field initializers cannot access local variable \"" + sf.getName().substring(4) + "\" declared in an enclosing block because none of the methods accesses it. " + "As a workaround, declare a dummy method that accesses the local variable." ), locatable.getLocation()); this.writeOpcode(scopeTbd, Opcode.ACONST_NULL); } else { throw new AssertionError(scopeTbd); } } else { String localVariableName = sf.getName().substring(4); LocalVariable lv; DETERMINE_LV: { Scope s; // Does one of the enclosing blocks declare a local variable with that name? for (s = scope; s instanceof BlockStatement; s = s.getEnclosingScope()) { BlockStatement bs = (BlockStatement) s; Scope es = bs.getEnclosingScope(); List statements; if (es instanceof Block) { statements = ((Block) es).statements; } else if (es instanceof FunctionDeclarator) { statements = ((FunctionDeclarator) es).optionalStatements; } else if (es instanceof ForEachStatement) { FunctionDeclarator.FormalParameter fp = ((ForEachStatement) es).currentElement; if (fp.name.equals(localVariableName)) { lv = this.getLocalVariable(fp); break DETERMINE_LV; } continue; } else { continue; } for (BlockStatement bs2 : statements) { if (bs2 == bs) break; if (bs2 instanceof LocalVariableDeclarationStatement) { LocalVariableDeclarationStatement lvds = ( (LocalVariableDeclarationStatement) bs2 ); for (VariableDeclarator vd : lvds.variableDeclarators) { if (vd.name.equals(localVariableName)) { lv = this.getLocalVariable(lvds, vd); break DETERMINE_LV; } } } } } // Does the declaring function declare a parameter with that name? while (!(s instanceof FunctionDeclarator)) s = s.getEnclosingScope(); FunctionDeclarator fd = (FunctionDeclarator) s; for (FormalParameter fp : fd.formalParameters.parameters) { if (fp.name.equals(localVariableName)) { lv = this.getLocalVariable(fp); break DETERMINE_LV; } } throw new JaninoRuntimeException( "SNO: Synthetic field \"" + sf.getName() + "\" neither maps a synthetic field of an enclosing instance nor a local variable" ); } this.load(locatable, lv); } } } // Evaluate constructor arguments. Rvalue[] adjustedArgs = null; IClass[] parameterTypes = iConstructor.getParameterTypes(); int actualSize = arguments.length; if (iConstructor.isVarargs() && iConstructor.argsNeedAdjust()) { adjustedArgs = new Rvalue[parameterTypes.length]; Rvalue[] lastArgs = new Rvalue[actualSize - parameterTypes.length + 1]; for (int i = 0, j = parameterTypes.length - 1; i < lastArgs.length; ++i, ++j) { lastArgs[i] = arguments[j]; } for (int i = parameterTypes.length - 2; i >= 0; --i) { adjustedArgs[i] = arguments[i]; } Location loc = (lastArgs.length == 0 ? locatable : lastArgs[lastArgs.length - 1]).getLocation(); adjustedArgs[adjustedArgs.length - 1] = new NewInitializedArray( loc, // location parameterTypes[parameterTypes.length - 1], // arrayIClass new ArrayInitializer(loc, lastArgs) // arrayInitializer ); arguments = adjustedArgs; } for (int i = 0; i < arguments.length; ++i) { this.assignmentConversion( locatable, // locatable this.compileGetValue(arguments[i]), // sourceType parameterTypes[i], // targetType this.getConstantValue(arguments[i]) // optionalConstantValue ); } // Invoke! // Notice that the method descriptor is "iConstructor.getDescriptor()" prepended with the synthetic parameters. this.invoke(locatable, iConstructor); } /** @return The {@link IField}s that are declared by the {@code fieldDeclaration} */ private IClass.IField[] getIFields(final FieldDeclaration fieldDeclaration) { IClass.IField[] res = new IClass.IField[fieldDeclaration.variableDeclarators.length]; for (int i = 0; i < res.length; ++i) { final VariableDeclarator variableDeclarator = fieldDeclaration.variableDeclarators[i]; res[i] = this.resolve(fieldDeclaration.getDeclaringType()).new IField() { // Implement IMember. @Override public Access getAccess() { switch (fieldDeclaration.modifiers.flags & 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"); } } @Override public Annotation[] getAnnotations() { return fieldDeclaration.modifiers.annotations; } // Implement "IField". @Override public boolean isStatic() { return Mod.isStatic(fieldDeclaration.modifiers.flags); } @Override public IClass getType() throws CompileException { return UnitCompiler.this.getType(fieldDeclaration.type).getArrayIClass( variableDeclarator.brackets, UnitCompiler.this.iClassLoader.TYPE_java_lang_Object ); } @Override public String getName() { return variableDeclarator.name; } @Override public Object getConstantValue() throws CompileException { if ( Mod.isFinal(fieldDeclaration.modifiers.flags) && variableDeclarator.optionalInitializer instanceof Rvalue ) { Object constantInitializerValue = UnitCompiler.this.getConstantValue( (Rvalue) variableDeclarator.optionalInitializer ); if (constantInitializerValue != UnitCompiler.NOT_CONSTANT) { return UnitCompiler.this.assignmentConversion( variableDeclarator.optionalInitializer, // locatable constantInitializerValue, // value this.getType() // targetType ); } } return UnitCompiler.NOT_CONSTANT; } }; } return res; } /** * Determine the non-constant-final initializer of the given {@link VariableDeclarator}. * * @return {@code null} if the variable is declared without an initializer or if the initializer is * constant-final */ ArrayInitializerOrRvalue getNonConstantFinalInitializer(FieldDeclaration fd, VariableDeclarator vd) throws CompileException { // Check if optional initializer exists. if (vd.optionalInitializer == null) return null; // Check if initializer is constant-final. if ( Mod.isStatic(fd.modifiers.flags) && Mod.isFinal(fd.modifiers.flags) && vd.optionalInitializer instanceof Rvalue && this.getConstantValue((Rvalue) vd.optionalInitializer) != UnitCompiler.NOT_CONSTANT ) return null; return vd.optionalInitializer; } private Atom reclassify(AmbiguousName an) throws CompileException { if (an.reclassified == null) { an.reclassified = this.reclassifyName( an.getLocation(), an.getEnclosingBlockStatement(), an.identifiers, an.n ); } return an.reclassified; } /** Reclassifies the ambiguous name consisting of the first {@code n} of the {@code identifiers} (JLS7 6.5.2.2). */ private Atom reclassifyName(Location location, Scope scope, final String[] identifiers, int n) throws CompileException { if (n == 1) return this.reclassifyName( location, scope, identifiers[0] ); // 6.5.2.2 Atom lhs = this.reclassifyName( location, scope, identifiers, n - 1 ); String rhs = identifiers[n - 1]; // 6.5.2.2.1 if (UnitCompiler.DEBUG) System.out.println("lhs = " + lhs); if (lhs instanceof Package) { String className = ((Package) lhs).name + '.' + rhs; IClass result = this.findTypeByName(location, className); if (result != null) return new SimpleType(location, result); return new Package(location, className); } // 6.5.2.2.3.2 EXPRESSION.length if ("length".equals(rhs) && this.getType(lhs).isArray()) { ArrayLength al = new ArrayLength(location, this.toRvalueOrCompileException(lhs)); if (!(scope instanceof BlockStatement)) { this.compileError("\".length\" only allowed in expression context"); return al; } al.setEnclosingBlockStatement((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 FieldAccess fa = new FieldAccess( location, lhs, field ); fa.setEnclosingBlockStatement((BlockStatement) scope); return fa; } } IClass[] classes = lhsType.getDeclaredIClasses(); for (final IClass memberType : classes) { String name = Descriptor.toClassName(memberType.getDescriptor()); name = name.substring(name.lastIndexOf('$') + 1); if (name.equals(rhs)) { // 6.5.2.2.2.3 TYPE.TYPE // 6.5.2.2.3.3 EXPRESSION.TYPE return new SimpleType(location, memberType); } } this.compileError( "\"" + rhs + "\" is neither a method, a field, nor a member class of \"" + lhsType + "\"", location ); return new Atom(location) { @Override public String toString() { return Java.join(identifiers, "."); } @Override public final void accept(AtomVisitor visitor) {} }; } /** * Find the named {@link IClass} in this compilation unit, or through the {@link #iClassLoader}. * * @param className Fully qualified class name, e.g. "pkg1.pkg2.Outer$Inner". * @return {@code null} iff an {@code IClass} with that name could not be loaded * @throws CompileException An exception was raised while loading the {@link IClass} */ private IClass findTypeByName(Location location, String className) throws CompileException { // Is the type defined in the same compilation unit? IClass res = this.findClass(className); if (res != null) return res; try { return this.iClassLoader.loadIClass(Descriptor.fromClassName(className)); } catch (ClassNotFoundException ex) { // SUPPRESS CHECKSTYLE AvoidHidingCause if (ex.getException() instanceof CompileException) throw (CompileException) ex.getException(); throw new CompileException(className, location, ex); } } /** JLS7 6.5.2.1 */ private Atom reclassifyName(Location location, Scope scope, final String identifier) throws CompileException { // Determine scope block statement, type body declaration, type and compilation unit. TypeBodyDeclaration scopeTbd = null; AbstractTypeDeclaration scopeTypeDeclaration = null; CompilationUnit scopeCompilationUnit; { Scope s = scope; while ( (s instanceof BlockStatement || s instanceof CatchClause) && !(s instanceof TypeBodyDeclaration) ) s = s.getEnclosingScope(); if (s instanceof TypeBodyDeclaration) { scopeTbd = (TypeBodyDeclaration) s; s = s.getEnclosingScope(); } if (s instanceof TypeDeclaration) { scopeTypeDeclaration = (AbstractTypeDeclaration) s; s = s.getEnclosingScope(); } while (!(s instanceof CompilationUnit)) s = s.getEnclosingScope(); scopeCompilationUnit = (CompilationUnit) s; } // 6.5.2.1.BL1 // 6.5.2.BL1.B1.B1.1 (JLS7: 6.5.2.BL1.B1.B1.1) / 6.5.6.1.1 Local variable. // 6.5.2.BL1.B1.B1.2 (JLS7: 6.5.2.BL1.B1.B1.2) / 6.5.6.1.1 Parameter. { Scope s = scope; if (s instanceof BlockStatement) { BlockStatement bs = (BlockStatement) s; LocalVariable lv = bs.findLocalVariable(identifier); if (lv != null) { LocalVariableAccess lva = new LocalVariableAccess(location, lv); lva.setEnclosingBlockStatement(bs); return lva; } s = s.getEnclosingScope(); } while (s instanceof BlockStatement || s instanceof CatchClause) s = s.getEnclosingScope(); if (s instanceof FunctionDeclarator) { s = s.getEnclosingScope(); } if (s instanceof InnerClassDeclaration) { InnerClassDeclaration icd = (InnerClassDeclaration) s; // SUPPRESS CHECKSTYLE UsageDistance s = s.getEnclosingScope(); if (s instanceof AnonymousClassDeclaration) { s = s.getEnclosingScope(); } else if (s instanceof FieldDeclaration) { s = s.getEnclosingScope().getEnclosingScope(); } while (s instanceof BlockStatement) { LocalVariable lv = ((BlockStatement) s).findLocalVariable(identifier); if (lv != null) { if (!lv.finaL) { this.compileError( "Cannot access non-final local variable \"" + identifier + "\" from inner class" ); } final IClass lvType = lv.type; IClass.IField iField = new SimpleIField( this.resolve(icd), "val$" + identifier, lvType ); icd.defineSyntheticField(iField); FieldAccess fa = new FieldAccess( location, // location new QualifiedThisReference( // lhs location, // location new SimpleType(location, this.resolve(icd)) // qualification ), iField // field ); fa.setEnclosingBlockStatement((BlockStatement) scope); return fa; } s = s.getEnclosingScope(); while (s instanceof BlockStatement) s = s.getEnclosingScope(); if (!(s instanceof FunctionDeclarator)) break; s = s.getEnclosingScope(); if (!(s instanceof InnerClassDeclaration)) break; icd = (InnerClassDeclaration) s; s = s.getEnclosingScope(); } } } // 6.5.2.BL1.B1.B1.3 (JLS7: 6.5.2.BL1.B1.B1.3) / 6.5.6.1.2.1 Field. BlockStatement enclosingBlockStatement = null; for (Scope s = scope; !(s instanceof CompilationUnit); s = s.getEnclosingScope()) { if (s instanceof BlockStatement && enclosingBlockStatement == null) { enclosingBlockStatement = (BlockStatement) s; } if (s instanceof TypeDeclaration) { final AbstractTypeDeclaration enclosingTypeDecl = (AbstractTypeDeclaration) s; final IClass etd = this.resolve(enclosingTypeDecl); final IClass.IField f = this.findIField(etd, identifier, location); if (f != null) { if (f.isStatic()) { this.warning("IASF", ( "Implicit access to static field \"" + identifier + "\" of declaring class (better write \"" + f.getDeclaringIClass() + '.' + f.getName() + "\")" ), location); } else if (f.getDeclaringIClass() == etd) { this.warning("IANSF", ( "Implicit access to non-static field \"" + identifier + "\" of declaring class (better write \"this." + f.getName() + "\")" ), location); } else { this.warning("IANSFEI", ( "Implicit access to non-static field \"" + identifier + "\" of enclosing instance (better write \"" + f.getDeclaringIClass() + ".this." + f.getName() + "\")" ), location); } SimpleType ct = new SimpleType(scopeTypeDeclaration.getLocation(), etd); 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 QualifiedThisReference(location, ct); } } Rvalue res = new FieldAccess( location, lhs, f ); res.setEnclosingBlockStatement(enclosingBlockStatement); return res; } } } // JLS7 6.5.2.BL1.B2.1 Static field imported through single static import. { List l = (List) this.singleStaticImports.get(identifier); if (l != null) { for (Object o : l) { if (o instanceof IField) { FieldAccess fieldAccess = new FieldAccess( location, new SimpleType(location, ((IField) o).getDeclaringIClass()), (IField) o ); fieldAccess.setEnclosingBlockStatement(enclosingBlockStatement); return fieldAccess; } } } } // JLS7 6.5.2.BL1.B2.2 Static field imported through static-import-on-demand. { IField importedField = null; for (IClass iClass : this.staticImportsOnDemand) { IField f = iClass.getDeclaredIField(identifier); if (f != null) { // JLS7 7.5.4 Static-Import-on-Demand Declaration if (!this.isAccessible(f, enclosingBlockStatement)) continue; if (importedField != null) { this.compileError( "Ambiguous static field import: \"" + importedField.toString() + "\" vs. \"" + f.toString() + "\"" ); } importedField = f; } } if (importedField != null) { if (!importedField.isStatic()) 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 ("java".equals(identifier)) return new Package(location, identifier); // JLS7: 6.5.2.BL1.B3.1 Unnamed package class // JLS7: 6.5.2.BL1.B3.2 Unnamed package interface // JLS7: 7.4.2 { IClass unnamedPackageType = this.findTypeByName(location, identifier); if (unnamedPackageType != null) return new SimpleType(location, unnamedPackageType); } // 6.5.2.BL1.B1.B2.1 (JLS7: 6.5.2.BL1.B3.3) Local class. { LocalClassDeclaration lcd = UnitCompiler.findLocalClassDeclaration(scope, identifier); if (lcd != null) return new SimpleType(location, this.resolve(lcd)); } // 6.5.2.BL1.B1.B2.2 (JLS7: 6.5.2.BL1.B3.4) Member type. if (scopeTypeDeclaration != null) { IClass memberType = this.findMemberType( this.resolve(scopeTypeDeclaration), identifier, location ); if (memberType != null) return new SimpleType(location, memberType); } // 6.5.2.BL1.B1.B3.1 (JLS7: 6.5.2.BL1.B1.B4.1) Single type import. { IClass iClass = this.importSingleType(identifier, location); if (iClass != null) return new SimpleType(location, iClass); } // 6.5.2.BL1.B1.B3.2 (JLS7: 6.5.2.BL1.B1.B3.1) Package member class/interface declared in this compilation // unit. // Notice that JLS2 looks this up AFTER local class, member type, single type import, while JLS3 looks this up // BEFORE local class, member type, single type import. { PackageMemberTypeDeclaration pmtd = scopeCompilationUnit.getPackageMemberTypeDeclaration(identifier); if (pmtd != null) return new SimpleType(location, this.resolve(pmtd)); } // 6.5.2.BL1.B1.B4 Class or interface declared in same package. // Notice: Why is this missing in JLS3? { String className = ( scopeCompilationUnit.optionalPackageDeclaration == null ? identifier : scopeCompilationUnit.optionalPackageDeclaration.packageName + '.' + identifier ); IClass result = this.findTypeByName(location, className); if (result != null) return new SimpleType(location, result); } // 6.5.2.BL1.B1.B5 (JLS7: 6.5.2.BL1.B1.B4.2), 6.5.2.BL1.B1.B6 Type-import-on-demand. { IClass importedClass = this.importTypeOnDemand(identifier, location); if (importedClass != null) { return new SimpleType(location, importedClass); } } // JLS7 6.5.2.BL1.B1.B4.3 Type imported through single static import. { List l = (List) this.singleStaticImports.get(identifier); if (l != null) { for (Object o : l) { if (o instanceof IClass) return new SimpleType(null, (IClass) o); } } } // JLS7 6.5.2.BL1.B1.B4.4 Type imported through static-import-on-demand. { IClass importedType = null; for (IClass ic : this.staticImportsOnDemand) { IClass[] memberTypes = ic.getDeclaredIClasses(); for (IClass memberType : memberTypes) { if (!this.isAccessible(memberType, scope)) continue; if (memberType.getDescriptor().endsWith('$' + identifier + ';')) { if (importedType != null) { this.compileError( "Ambiguous static type import: \"" + importedType.toString() + "\" vs. \"" + memberType.toString() + "\"" ); } importedType = memberType; } } } if (importedType != null) return new SimpleType(null, importedType); } // 6.5.2.BL1.B1.B7 Package name return new Package(location, identifier); } private void determineValue(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 ArrayLength( fae.getLocation(), this.toRvalueOrCompileException(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 Rvalue(fae.getLocation()) { @Override public String toString() { return "???"; } @Override public void accept(AtomVisitor visitor) {} @Override public void accept(RvalueVisitor visitor) {} @Override public void accept(ElementValueVisitor visitor) {} }; return; } fae.value = new FieldAccess( fae.getLocation(), fae.lhs, iField ); } fae.value.setEnclosingBlockStatement(fae.getEnclosingBlockStatement()); } /** "super.fld", "Type.super.fld" */ private void determineValue(SuperclassFieldAccessExpression scfae) throws CompileException { if (scfae.value != null) return; Rvalue lhs; { ThisReference tr = new ThisReference(scfae.getLocation()); tr.setEnclosingBlockStatement(scfae.getEnclosingBlockStatement()); IClass type; if (scfae.optionalQualification != null) { type = this.getType(scfae.optionalQualification); } else { type = this.getType(tr); } lhs = new 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 Rvalue(scfae.getLocation()) { @Override public String toString() { return "???"; } @Override public void accept(AtomVisitor visitor) {} @Override public void accept(RvalueVisitor visitor) {} @Override public void accept(ElementValueVisitor visitor) {} }; return; } scfae.value = new 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. *

* Notice that the returned {@link IClass.IMethod} may be declared in an enclosing type. * * @return The selected {@link IClass.IMethod} or {@code null} */ public IClass.IMethod findIMethod(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 ( Scope s = mi.getEnclosingBlockStatement(); !(s instanceof CompilationUnit); s = s.getEnclosingScope() ) { if (s instanceof TypeDeclaration) { TypeDeclaration td = (TypeDeclaration) s; // Find methods with specified name. iMethod = this.findIMethod( this.resolve(td), // targetType mi // invocation ); if (iMethod != null) break FIND_METHOD; } } } else { // Method invocation by "target": "expr.meth(arguments)" -- method must be declared by the target's // type. iMethod = this.findIMethod( this.getType(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 (Object o : l) { 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) { 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 (IClass iClass : this.staticImportsOnDemand) { IMethod im = this.findIMethod( iClass, // targetType mi // invocation ); if (im != null) { if (iMethod != null) { 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 this.fakeIMethod(this.iClassLoader.TYPE_java_lang_Object, mi.methodName, mi.arguments); } this.checkThrownExceptions(mi, iMethod); return iMethod; } /** * Find a {@link IClass.IMethod} in the given {@code targetType}, its superclasses or superinterfaces with the * given {@code name} and for the given {@code arguments}. If more than one such method exists, choose the most * specific one (JLS7 15.11.2). * * @return {@code null} 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); // Interfaces inherit the methods declared in 'Object'. if (targetType.isInterface()) { IClass.IMethod[] oms = this.iClassLoader.TYPE_java_lang_Object.getDeclaredIMethods(invocation.methodName); for (IMethod om : oms) { if (!om.isStatic() && om.getAccess() == Access.PUBLIC) ms.add(om); } } if (ms.size() == 0) return null; // Determine arguments' types, choose the most specific method. return (IClass.IMethod) this.findMostSpecificIInvocable( invocation, // locatable (IClass.IMethod[]) ms.toArray(new IClass.IMethod[ms.size()]), // iInvocables invocation.arguments, // arguments invocation.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() { @Override public String getName() { return name; } @Override public IClass getReturnType() { return IClass.INT; } @Override public boolean isStatic() { return false; } @Override public boolean isAbstract() { return false; } @Override public boolean isVarargs() { return false; } @Override public IClass[] getParameterTypes2() { return pts; } @Override public IClass[] getThrownExceptions2() { return new IClass[0]; } @Override public Access getAccess() { return Access.PUBLIC; } @Override public Annotation[] getAnnotations() { return new Annotation[0]; } }; } /** * Add all methods with the given {@code methodName} that are declared by the {@code type}, its superclasses and * all their superinterfaces to the result list {@code v}. */ public void getIMethods(IClass type, String methodName, List v) throws CompileException { // Check methods declared by this type. { IClass.IMethod[] ims = type.getDeclaredIMethods(methodName); for (IMethod im : ims) v.add(im); } // Check superclass. IClass superclass = type.getSuperclass(); if (superclass != null) this.getIMethods(superclass, methodName, v); // Check superinterfaces. IClass[] interfaces = type.getInterfaces(); for (IClass interfacE : interfaces) this.getIMethods(interfacE, methodName, v); } /** @return The {@link IClass.IMethod} that implements the {@code superclassMethodInvocation} */ public IClass.IMethod findIMethod(SuperclassMethodInvocation superclassMethodInvocation) throws CompileException { ClassDeclaration declaringClass; for (Scope s = superclassMethodInvocation.getEnclosingBlockStatement();; s = s.getEnclosingScope()) { if (s instanceof FunctionDeclarator) { FunctionDeclarator fd = (FunctionDeclarator) s; if (Mod.isStatic(fd.modifiers.flags)) { this.compileError( "Superclass method cannot be invoked in static context", superclassMethodInvocation.getLocation() ); } } if (s instanceof ClassDeclaration) { declaringClass = (ClassDeclaration) s; break; } } IClass superclass = this.resolve(declaringClass).getSuperclass(); IMethod iMethod = this.findIMethod( superclass, // targetType superclassMethodInvocation // invocation ); if (iMethod == null) { this.compileError( "Class \"" + superclass + "\" has no method named \"" + superclassMethodInvocation.methodName + "\"", superclassMethodInvocation.getLocation() ); return this.fakeIMethod( superclass, superclassMethodInvocation.methodName, superclassMethodInvocation.arguments ); } this.checkThrownExceptions(superclassMethodInvocation, iMethod); return iMethod; } /** * Determine the arguments' types, determine the applicable invocables and choose the most specific invocable * and adjust arguments as needed (for varargs case). * * @param iInvocables Length must be greater than zero * @return The selected {@link IClass.IInvocable} */ private IClass.IInvocable findMostSpecificIInvocable( Locatable locatable, final IInvocable[] iInvocables, final Rvalue[] arguments, Scope contextScope ) throws CompileException { // Determine arguments' types. final IClass[] argumentTypes = new IClass[arguments.length]; for (int i = 0; i < arguments.length; ++i) { argumentTypes[i] = this.getType(arguments[i]); } // Determine most specific invocable WITHOUT boxing. IInvocable ii = this.findMostSpecificIInvocable(locatable, iInvocables, argumentTypes, false, contextScope); if (ii != null) return ii; // Determine most specific invocable WITH boxing. ii = this.findMostSpecificIInvocable(locatable, iInvocables, argumentTypes, true, contextScope); if (ii != null) return ii; // Report a nice compile error. StringBuilder sb = new StringBuilder("No applicable constructor/method found for "); if (argumentTypes.length == 0) { sb.append("zero actual parameters"); } else { sb.append("actual parameters \"").append(argumentTypes[0]); for (int i = 1; i < argumentTypes.length; ++i) { sb.append(", ").append(argumentTypes[i]); } sb.append("\""); } sb.append("; candidates are: ").append('"' + iInvocables[0].toString() + '"'); for (int i = 1; i < iInvocables.length; ++i) { sb.append(", ").append('"' + iInvocables[i].toString() + '"'); } this.compileError(sb.toString(), locatable.getLocation()); // Well, returning a "fake" IInvocable is a bit tricky, because the iInvocables can be of different types. if (iInvocables[0] instanceof IClass.IConstructor) { return iInvocables[0].getDeclaringIClass().new IConstructor() { @Override public boolean isVarargs() { return false; } @Override public IClass[] getParameterTypes2() { return argumentTypes; } @Override public Access getAccess() { return Access.PUBLIC; } @Override public IClass[] getThrownExceptions2() { return new IClass[0]; } @Override public Annotation[] getAnnotations() { return new Annotation[0]; } }; } else if (iInvocables[0] instanceof IClass.IMethod) { final String methodName = ((IClass.IMethod) iInvocables[0]).getName(); return iInvocables[0].getDeclaringIClass().new IMethod() { @Override public boolean isStatic() { return true; } @Override public boolean isAbstract() { return false; } @Override public IClass getReturnType() { return IClass.INT; } @Override public String getName() { return methodName; } @Override public Access getAccess() { return Access.PUBLIC; } @Override public boolean isVarargs() { return false; } @Override public IClass[] getParameterTypes2() { return argumentTypes; } @Override public IClass[] getThrownExceptions2() { return new IClass[0]; } @Override public Annotation[] getAnnotations() { return new Annotation[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} if no {@link IClass.IInvocable} is * applicable */ public IClass.IInvocable findMostSpecificIInvocable( Locatable locatable, final IInvocable[] iInvocables, IClass[] argumentTypes, boolean boxingPermitted, Scope contextScope ) throws CompileException { if (UnitCompiler.DEBUG) { System.out.println("Argument types:"); for (IClass argumentType : argumentTypes) System.out.println(argumentType); } // Select applicable methods (15.12.2.1). List applicableIInvocables = new ArrayList(); List varargApplicables = new ArrayList(); NEXT_METHOD: for (IClass.IInvocable ii : iInvocables) { boolean argsNeedAdjust = false; // Ignore inaccessible invocables. if (!this.isAccessible(ii, contextScope)) continue; // Check parameter count. final IClass[] parameterTypes = ii.getParameterTypes(); int formalParamCount = parameterTypes.length; int nUncheckedArg = argumentTypes.length; final boolean isVarargs = ii.isVarargs(); // Match the last formal parameter with all args starting from that index (or none). VARARGS: if (isVarargs) { // Decrement the count to get the index. formalParamCount--; final IClass lastParamType = parameterTypes[formalParamCount].getComponentType(); final int lastActualArg = nUncheckedArg - 1; // If the two have the same argCount and the last actual arg is an array of the same type accept it // (e.g. "void foo(int a, double...b) VS foo(1, new double[0]"). if ( formalParamCount == lastActualArg && argumentTypes[lastActualArg].isArray() && this.isMethodInvocationConvertible( argumentTypes[lastActualArg].getComponentType(), lastParamType, boxingPermitted ) ) { nUncheckedArg--; } else { for (int idx = lastActualArg; idx >= formalParamCount; --idx) { // Is method invocation conversion possible (5.3)? // if (UnitCompiler.DEBUG) System.out.println(lastParamType + " <=> " + argumentTypes[idx]); if (!this.isMethodInvocationConvertible(argumentTypes[idx], lastParamType, boxingPermitted)) { formalParamCount++; break VARARGS; } nUncheckedArg--; } argsNeedAdjust = true; } } if (formalParamCount == nUncheckedArg) { for (int j = 0; j < nUncheckedArg; ++j) { // 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!"); // Varargs has lower priority. if (isVarargs) { ii.setArgsNeedAdjust(argsNeedAdjust); varargApplicables.add(ii); } else { applicableIInvocables.add(ii); } } } // Choose the most specific invocable (15.12.2.2). if (applicableIInvocables.size() == 1) { return (IClass.IInvocable) applicableIInvocables.get(0); } // No method found by previous phase(s). if (applicableIInvocables.size() == 0 && !varargApplicables.isEmpty()) { //TODO: 15.12.2.3 (type-conversion?) // 15.12.2.4 : Phase 3: Identify Applicable Variable Arity Methods applicableIInvocables = varargApplicables; if (applicableIInvocables.size() == 1) { return (IClass.IInvocable) applicableIInvocables.get(0); } } if (applicableIInvocables.size() == 0) return null; // 15.12.2.5. Determine the "maximally specific invocables". List maximallySpecificIInvocables = new ArrayList(); for (IClass.IInvocable applicableIInvocable : applicableIInvocables) { int moreSpecific = 0, lessSpecific = 0; for (IClass.IInvocable mostSpecificIInvocable : maximallySpecificIInvocables) { if (applicableIInvocable.isMoreSpecificThan(mostSpecificIInvocable)) { ++moreSpecific; } else if (applicableIInvocable.isLessSpecificThan(mostSpecificIInvocable)) { ++lessSpecific; } } if (moreSpecific == maximallySpecificIInvocables.size()) { maximallySpecificIInvocables.clear(); maximallySpecificIInvocables.add(applicableIInvocable); } else if (lessSpecific < maximallySpecificIInvocables.size()) { maximallySpecificIInvocables.add(applicableIInvocable); } else { ; } 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 (JLS7 15.12.2.2.BL2.B1). IClass.IMethod theNonAbstractMethod = null; { Iterator it = maximallySpecificIInvocables.iterator(); IClass.IMethod m = (IClass.IMethod) it.next(); final IClass[] parameterTypesOfFirstMethod = m.getParameterTypes(); for (;;) { if (!m.isAbstract()) { 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; } } } // JLS7 15.12.2.2.BL2.B1.B1 if (theNonAbstractMethod != null) return theNonAbstractMethod; // JLS7 15.12.2.2.BL2.B1.B2 // Check "that exception [te1] is declared in the THROWS clause of each of the maximally specific methods". Set s = new HashSet(); { IClass[][] tes = new IClass[maximallySpecificIInvocables.size()][]; Iterator it = maximallySpecificIInvocables.iterator(); for (int i = 0; i < tes.length; ++i) { tes[i] = ((IClass.IMethod) it.next()).getThrownExceptions(); } for (int i = 0; i < tes.length; ++i) { EACH_EXCEPTION: for (IClass te1 : tes[i]) { EACH_METHOD: for (int k = 0; k < tes.length; ++k) { if (k == i) continue; for (IClass te2 : tes[k]) { if (te2.isAssignableFrom(te1)) continue EACH_METHOD; } continue EACH_EXCEPTION; } s.add(te1); } } } // 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() { @Override public String getName() { return im.getName(); } @Override public IClass getReturnType() throws CompileException { return im.getReturnType(); } // SUPPRESS CHECKSTYLE LineLength @Override public boolean isAbstract() { return im.isAbstract(); } @Override public boolean isStatic() { return im.isStatic(); } @Override public Access getAccess() { return im.getAccess(); } @Override public boolean isVarargs() { return im.isVarargs(); } @Override public IClass[] getParameterTypes2() throws CompileException { return im.getParameterTypes(); } // SUPPRESS CHECKSTYLE LineLength @Override public IClass[] getThrownExceptions2() { return tes; } @Override public Annotation[] getAnnotations() { return im.getAnnotations(); } // SUPPRESS CHECKSTYLE LineLength }; } if (!boxingPermitted) return null; // To try again. // JLS7 15.12.2.2.BL2.B2 { StringBuilder sb = new StringBuilder("Invocation of constructor/method with argument type(s) \""); for (int i = 0; i < argumentTypes.length; ++i) { if (i > 0) sb.append(", "); sb.append(Descriptor.toString(argumentTypes[i].getDescriptor())); } sb.append("\" is ambiguous: "); for (int i = 0; i < maximallySpecificIInvocables.size(); ++i) { if (i > 0) sb.append(" vs. "); sb.append("\"" + maximallySpecificIInvocables.get(i) + "\""); } this.compileError(sb.toString(), locatable.getLocation()); } return iInvocables[0]; } /** Checks if "method invocation conversion" (5.3) is possible. */ private boolean isMethodInvocationConvertible( IClass sourceType, IClass targetType, boolean boxingPermitted ) throws CompileException { // 5.3 Identity conversion. if (sourceType == targetType) return true; // 5.3 Widening primitive conversion. if (this.isWideningPrimitiveConvertible(sourceType, targetType)) return true; // 5.3 Widening reference conversion. if (this.isWideningReferenceConvertible(sourceType, targetType)) return true; // JLS7 5.3 A boxing conversion (JLS7 5.1.7) optionally followed by widening reference conversion. if (boxingPermitted) { IClass boxedType = this.isBoxingConvertible(sourceType); if (boxedType != null) { return ( this.isIdentityConvertible(boxedType, targetType) || this.isWideningReferenceConvertible(boxedType, targetType) ); } } // JLS7 5.3 An unboxing conversion (JLS7 5.1.8) optionally followed by a widening primitive conversion. if (boxingPermitted) { IClass unboxedType = this.isUnboxingConvertible(sourceType); if (unboxedType != null) { return ( this.isIdentityConvertible(unboxedType, targetType) || this.isWideningPrimitiveConvertible(unboxedType, targetType) ); } } // 5.3 TODO: FLOAT or DOUBLE value set conversion return false; } /** @throws CompileException if the {@link Invocation} throws exceptions that are disallowed in the given scope */ private void checkThrownExceptions(Invocation in, IMethod iMethod) throws CompileException { IClass[] thrownExceptions = iMethod.getThrownExceptions(); for (IClass thrownException : thrownExceptions) { this.checkThrownException( in, // locatable thrownException, // type in.getEnclosingBlockStatement() // scope ); } } /** * @throws CompileException The exception with the given {@code type} must not be thrown in the given {@code scope} */ private void checkThrownException(Locatable locatable, IClass type, Scope scope) throws CompileException { // Thrown object must be assignable to "Throwable". if (!this.iClassLoader.TYPE_java_lang_Throwable.isAssignableFrom(type)) { this.compileError( "Thrown object of type \"" + type + "\" is not assignable to \"Throwable\"", locatable.getLocation() ); } // "RuntimeException" and "Error" are never checked. if ( this.iClassLoader.TYPE_java_lang_RuntimeException.isAssignableFrom(type) || this.iClassLoader.TYPE_java_lang_Error.isAssignableFrom(type) ) return; for (;; scope = scope.getEnclosingScope()) { // Match against enclosing "try...catch" blocks. if (scope instanceof TryStatement) { TryStatement ts = (TryStatement) scope; for (int i = 0; i < ts.catchClauses.size(); ++i) { CatchClause cc = (CatchClause) ts.catchClauses.get(i); IClass caughtType = this.getType(cc.caughtException.type); if (caughtType.isAssignableFrom(type)) { // This catch clause definitely catches the exception. cc.reachable = true; return; } CATCH_SUBTYPE: if (type.isAssignableFrom(caughtType)) { // This catch clause catches only a subtype of the exception type. for (int j = 0; j < i; ++j) { if (this.getType( ((CatchClause) ts.catchClauses.get(j)).caughtException.type ).isAssignableFrom(caughtType)) { // A preceding catch clause is more general than this catch clause. break CATCH_SUBTYPE; } } // This catch clause catches PART OF the actual exceptions. cc.reachable = true; } } } else // Match against "throws" clause of declaring function. if (scope instanceof FunctionDeclarator) { FunctionDeclarator fd = (FunctionDeclarator) scope; for (Type thrownException : fd.thrownExceptions) { if (this.getType(thrownException).isAssignableFrom(type)) return; } break; } else if (scope instanceof TypeBodyDeclaration) { break; } } this.compileError(( "Thrown exception of type \"" + type + "\" is neither caught by a \"try...catch\" block " + "nor declared in the \"throws\" clause of the declaring function" ), locatable.getLocation()); } private IClass getTargetIClass(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. */ LocalVariable isIntLv(Crement c) throws CompileException { if (!(c.operand instanceof AmbiguousName)) return null; AmbiguousName an = (AmbiguousName) c.operand; Atom rec = this.reclassify(an); if (!(rec instanceof LocalVariableAccess)) return null; LocalVariableAccess lva = (LocalVariableAccess) rec; LocalVariable lv = lva.localVariable; if (lv.finaL) this.compileError("Must not increment or decrement \"final\" local variable", lva.getLocation()); 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 TypeDeclaration td) { final AbstractTypeDeclaration atd = (AbstractTypeDeclaration) td; if (atd.resolvedType == null) atd.resolvedType = new IClass() { // final TypeParameter[] optionalTypeParameters = ( // atd instanceof NamedTypeDeclaration // ? ((NamedTypeDeclaration) atd).getOptionalTypeParameters() // : null // ); @Override protected IClass.IMethod[] getDeclaredIMethods2() { IClass.IMethod[] res = new IClass.IMethod[atd.getMethodDeclarations().size()]; int i = 0; for (MethodDeclarator md : atd.getMethodDeclarations()) { res[i++] = UnitCompiler.this.toIMethod(md); } return res; } private IClass[] declaredClasses; @Override protected IClass[] getDeclaredIClasses2() { if (this.declaredClasses == null) { Collection mtds = td.getMemberTypeDeclarations(); IClass[] mts = new IClass[mtds.size()]; int i = 0; for (MemberTypeDeclaration mtd : mtds) { mts[i++] = UnitCompiler.this.resolve(mtd); } this.declaredClasses = mts; } return this.declaredClasses; } @Override protected IClass getDeclaringIClass2() { Scope s = atd; for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope()) { if (s instanceof CompilationUnit) return null; } return UnitCompiler.this.resolve((AbstractTypeDeclaration) s.getEnclosingScope()); } @Override protected IClass getOuterIClass2() { AbstractTypeDeclaration oc = (AbstractTypeDeclaration) UnitCompiler.getOuterClass(atd); if (oc == null) return null; return UnitCompiler.this.resolve(oc); } @Override protected final String getDescriptor2() { return Descriptor.fromClassName(atd.getClassName()); } @Override public boolean isArray() { return false; } @Override protected IClass getComponentType2() { throw new JaninoRuntimeException("SNO: Non-array type has no component type"); } @Override public boolean isPrimitive() { return false; } @Override public boolean isPrimitiveNumeric() { return false; } @Override protected IConstructor[] getDeclaredIConstructors2() { if (atd instanceof ClassDeclaration) { ConstructorDeclarator[] cs = ((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]; } @Override protected IField[] getDeclaredIFields2() { if (atd instanceof ClassDeclaration) { ClassDeclaration cd = (ClassDeclaration) atd; List l = new ArrayList(); // Determine variable declarators of type declaration. for (BlockStatement vdoi : cd.variableDeclaratorsAndInitializers) { if (vdoi instanceof FieldDeclaration) { FieldDeclaration fd = (FieldDeclaration) vdoi; IClass.IField[] flds = UnitCompiler.this.getIFields(fd); for (IField fld : flds) l.add(fld); } } return (IClass.IField[]) l.toArray(new IClass.IField[l.size()]); } else if (atd instanceof InterfaceDeclaration) { InterfaceDeclaration id = (InterfaceDeclaration) atd; List l = new ArrayList(); // Determine static fields. for (BlockStatement bs : id.constantDeclarations) { if (bs instanceof FieldDeclaration) { FieldDeclaration fd = (FieldDeclaration) bs; IClass.IField[] flds = UnitCompiler.this.getIFields(fd); for (IField fld : flds) l.add(fld); } } return (IClass.IField[]) l.toArray(new IClass.IField[l.size()]); } else { throw new JaninoRuntimeException( "SNO: AbstractTypeDeclaration is neither ClassDeclaration nor InterfaceDeclaration" ); } } @Override public IField[] getSyntheticIFields() { if (atd instanceof ClassDeclaration) { Collection c = ((ClassDeclaration) atd).syntheticFields.values(); return (IField[]) c.toArray(new IField[c.size()]); } return new IField[0]; } @Override protected IClass getSuperclass2() throws CompileException { if (atd instanceof AnonymousClassDeclaration) { IClass bt = UnitCompiler.this.getType(((AnonymousClassDeclaration) atd).baseType); return bt.isInterface() ? UnitCompiler.this.iClassLoader.TYPE_java_lang_Object : bt; } if (atd instanceof NamedClassDeclaration) { NamedClassDeclaration ncd = (NamedClassDeclaration) atd; if (ncd.optionalExtendedType == null) return UnitCompiler.this.iClassLoader.TYPE_java_lang_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; } @Override public Access getAccess() { return UnitCompiler.modifiers2Access(atd.getModifierFlags()); } @Override public boolean isFinal() { return Mod.isFinal(atd.getModifierFlags()); } @Override protected IClass[] getInterfaces2() throws CompileException { if (atd instanceof AnonymousClassDeclaration) { IClass bt = UnitCompiler.this.getType(((AnonymousClassDeclaration) atd).baseType); return bt.isInterface() ? new IClass[] { bt } : new IClass[0]; } else if (atd instanceof NamedClassDeclaration) { NamedClassDeclaration ncd = (NamedClassDeclaration) atd; IClass[] res = new IClass[ncd.implementedTypes.length]; for (int i = 0; i < res.length; ++i) { res[i] = UnitCompiler.this.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 InterfaceDeclaration) { InterfaceDeclaration id = (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" ); } } @Override public boolean isAbstract() { return atd instanceof InterfaceDeclaration || Mod.isAbstract(atd.getModifierFlags()); } @Override public boolean isInterface() { return atd instanceof InterfaceDeclaration; } }; return atd.resolvedType; } private void referenceThis( Locatable locatable, ClassDeclaration declaringClass, TypeBodyDeclaration declaringTypeBodyDeclaration, IClass targetIClass ) throws CompileException { List path = UnitCompiler.getOuterClasses(declaringClass); if (declaringTypeBodyDeclaration.isStatic()) { this.compileError("No current instance available in static context", locatable.getLocation()); } int j; TARGET_FOUND: { for (j = 0; j < path.size(); ++j) { // Notice: JLS7 15.9.2.BL1.B3.B1.B2 seems to be wrong: Obviously, JAVAC does not only allow // // O is the nth lexically enclosing class // // , but also // // O is assignable from the nth lexically enclosing class // // However, this strategy bears the risk of ambiguities, because "O" may be assignable from more than // one enclosing class. if (targetIClass.isAssignableFrom(this.resolve((TypeDeclaration) path.get(j)))) { break TARGET_FOUND; } } this.compileError( "\"" + declaringClass + "\" is not enclosed by \"" + targetIClass + "\"", locatable.getLocation() ); } int i; if (declaringTypeBodyDeclaration instanceof ConstructorDeclarator) { if (j == 0) { this.writeOpcode(locatable, Opcode.ALOAD_0); return; } ConstructorDeclarator constructorDeclarator = ( (ConstructorDeclarator) declaringTypeBodyDeclaration ); String spn = "this$" + (path.size() - 2); LocalVariable syntheticParameter = ( (LocalVariable) constructorDeclarator.syntheticParameters.get(spn) ); if (syntheticParameter == null) { throw new JaninoRuntimeException("SNO: Synthetic parameter \"" + spn + "\" not found"); } this.load(locatable, syntheticParameter); i = 1; } else { this.writeOpcode(locatable, Opcode.ALOAD_0); i = 0; } for (; i < j; ++i) { final InnerClassDeclaration inner = (InnerClassDeclaration) path.get(i); final TypeDeclaration outer = (TypeDeclaration) path.get(i + 1); SimpleIField sf = new SimpleIField( this.resolve(inner), // declaringIClass "this$" + (path.size() - i - 2), // name this.resolve(outer) // type ); inner.defineSyntheticField(sf); this.getfield(locatable, sf); } } /** * Return a list consisting of the given {@code inner} class and all its outer classes. * * @return {@link List} of {@link TypeDeclaration} */ private static List getOuterClasses(TypeDeclaration inner) { List path = new ArrayList(); for (TypeDeclaration ic = inner; ic != null; ic = UnitCompiler.getOuterClass(ic)) path.add(ic); return path; } /** @return The {@link TypeDeclaration} that immediately encloses the {@code typeDeclaration}, or {@code null} */ static TypeDeclaration getOuterClass(TypeDeclaration typeDeclaration) { // Package member class declaration. if (typeDeclaration instanceof PackageMemberClassDeclaration) return null; // Local class declaration. if (typeDeclaration instanceof LocalClassDeclaration) { Scope s = typeDeclaration.getEnclosingScope(); for (; !(s instanceof FunctionDeclarator); s = s.getEnclosingScope()); if ((s instanceof MethodDeclarator) && Mod.isStatic(((FunctionDeclarator) s).modifiers.flags)) return null; for (; !(s instanceof TypeDeclaration); s = s.getEnclosingScope()); TypeDeclaration immediatelyEnclosingTypeDeclaration = (TypeDeclaration) s; return ( immediatelyEnclosingTypeDeclaration instanceof ClassDeclaration ) ? immediatelyEnclosingTypeDeclaration : null; } // Member class declaration. if ( typeDeclaration instanceof MemberClassDeclaration && Mod.isStatic(((MemberClassDeclaration) typeDeclaration).getModifierFlags()) ) return null; // Anonymous class declaration, interface declaration. Scope s = typeDeclaration; for (; !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope()) { if (s instanceof ConstructorInvocation) return null; if (s instanceof CompilationUnit) return null; } //if (!(s instanceof ClassDeclaration)) return null; if (((TypeBodyDeclaration) s).isStatic()) return null; return (AbstractTypeDeclaration) s.getEnclosingScope(); } private IClass getIClass(ThisReference tr) throws CompileException { if (tr.iClass == null) { // Compile error if in static function context. Scope s; for ( s = tr.getEnclosingBlockStatement(); s instanceof Statement || s instanceof CatchClause; s = s.getEnclosingScope() ); if (s instanceof FunctionDeclarator) { FunctionDeclarator function = (FunctionDeclarator) s; if (Mod.isStatic(function.modifiers.flags)) { this.compileError("No current instance available in static method", tr.getLocation()); } } // Determine declaring type. while (!(s instanceof TypeDeclaration)) { s = s.getEnclosingScope(); } if (!(s instanceof ClassDeclaration)) { this.compileError("Only methods of classes can have a current instance", tr.getLocation()); } tr.iClass = this.resolve((ClassDeclaration) s); } return tr.iClass; } private IClass getReturnType(FunctionDeclarator fd) throws CompileException { if (fd.returnType == null) { fd.returnType = this.getType(fd.type); } return fd.returnType; } /** @return the {@link IConstructor} that implements the {@code constructorDeclarator} */ IClass.IConstructor toIConstructor(final ConstructorDeclarator constructorDeclarator) { if (constructorDeclarator.iConstructor != null) return constructorDeclarator.iConstructor; constructorDeclarator.iConstructor = this.resolve(constructorDeclarator.getDeclaringType()).new IConstructor() { // Implement IMember. @Override public Access getAccess() { switch (constructorDeclarator.modifiers.flags & 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"); } } @Override public Annotation[] getAnnotations() { return constructorDeclarator.modifiers.annotations; } // Implement IInvocable. @Override public String getDescriptor2() throws CompileException { if (!(constructorDeclarator.getDeclaringClass() instanceof InnerClassDeclaration)) { return super.getDescriptor2(); } List parameterFds = new ArrayList(); // Convert enclosing instance reference into prepended constructor parameters. IClass outerClass = UnitCompiler.this.resolve( constructorDeclarator.getDeclaringClass() ).getOuterIClass(); if (outerClass != null) parameterFds.add(outerClass.getDescriptor()); // Convert synthetic fields into prepended constructor parameters. for (IField sf : constructorDeclarator.getDeclaringClass().syntheticFields.values()) { if (sf.getName().startsWith("val$")) parameterFds.add(sf.getType().getDescriptor()); } // Process the 'normal' (declared) function parameters. FormalParameter[] parameters = constructorDeclarator.formalParameters.parameters; for (int i = 0; i < parameters.length; ++i) { IClass parameterType = UnitCompiler.this.getType(parameters[i].type); if (i == parameters.length - 1 && constructorDeclarator.formalParameters.variableArity) { parameterType = parameterType.getArrayIClass( UnitCompiler.this.iClassLoader.TYPE_java_lang_Object ); } parameterFds.add(parameterType.getDescriptor()); } return new MethodDescriptor( (String[]) parameterFds.toArray(new String[parameterFds.size()]), // parameterFds Descriptor.VOID // returnFd ).toString(); } @Override public boolean isVarargs() { return Mod.isVarargs(constructorDeclarator.modifiers.flags); } @Override public IClass[] getParameterTypes2() throws CompileException { FormalParameter[] parameters = constructorDeclarator.formalParameters.parameters; IClass[] res = new IClass[parameters.length]; for (int i = 0; i < parameters.length; ++i) { IClass parameterType = UnitCompiler.this.getType(parameters[i].type); if (i == parameters.length - 1 && constructorDeclarator.formalParameters.variableArity) { parameterType = parameterType.getArrayIClass( UnitCompiler.this.iClassLoader.TYPE_java_lang_Object ); } res[i] = parameterType; } return res; } @Override public IClass[] getThrownExceptions2() throws CompileException { IClass[] res = new IClass[constructorDeclarator.thrownExceptions.length]; for (int i = 0; i < res.length; ++i) { res[i] = UnitCompiler.this.getType(constructorDeclarator.thrownExceptions[i]); } return res; } @Override public String toString() { StringBuilder sb = new StringBuilder().append( constructorDeclarator.getDeclaringType().getClassName() ).append('('); FormalParameter[] parameters = constructorDeclarator.formalParameters.parameters; for (int i = 0; i < parameters.length; ++i) { if (i != 0) sb.append(", "); sb.append(parameters[i].toString( i == parameters.length - 1 && constructorDeclarator.formalParameters.variableArity )); } return sb.append(')').toString(); } }; return constructorDeclarator.iConstructor; } /** @return The {@link IMethod} that implements the {@code methodDeclarator} */ public IClass.IMethod toIMethod(final MethodDeclarator methodDeclarator) { if (methodDeclarator.iMethod != null) return methodDeclarator.iMethod; methodDeclarator.iMethod = this.resolve(methodDeclarator.getDeclaringType()).new IMethod() { // Implement IMember. @Override public Access getAccess() { switch (methodDeclarator.modifiers.flags & 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"); } } @Override public Annotation[] getAnnotations() { return methodDeclarator.modifiers.annotations; } // Implement IInvocable. @Override public boolean isVarargs() { return Mod.isVarargs(methodDeclarator.modifiers.flags); } @Override public IClass[] getParameterTypes2() throws CompileException { FormalParameter[] parameters = methodDeclarator.formalParameters.parameters; IClass[] res = new IClass[parameters.length]; for (int i = 0; i < parameters.length; ++i) { IClass parameterType = UnitCompiler.this.getType(parameters[i].type); if (i == parameters.length - 1 && methodDeclarator.formalParameters.variableArity) { parameterType = parameterType.getArrayIClass( UnitCompiler.this.iClassLoader.TYPE_java_lang_Object ); } res[i] = parameterType; } return res; } @Override public IClass[] getThrownExceptions2() throws CompileException { IClass[] res = new IClass[methodDeclarator.thrownExceptions.length]; for (int i = 0; i < res.length; ++i) { res[i] = UnitCompiler.this.getType(methodDeclarator.thrownExceptions[i]); } return res; } // Implement IMethod. @Override public boolean isStatic() { return Mod.isStatic(methodDeclarator.modifiers.flags); } @Override public boolean isAbstract() { return ( (methodDeclarator.getDeclaringType() instanceof InterfaceDeclaration) || Mod.isAbstract(methodDeclarator.modifiers.flags) ); } @Override public IClass getReturnType() throws CompileException { return UnitCompiler.this.getReturnType(methodDeclarator); } @Override public String getName() { return methodDeclarator.name; } }; return methodDeclarator.iMethod; } private IClass.IInvocable toIInvocable(final FunctionDeclarator fd) { final IClass.IInvocable[] result = new IClass.IInvocable[1]; fd.accept(new Visitor.FunctionDeclaratorVisitor() { // CHECKSTYLE LineLength:OFF @Override public void visitMethodDeclarator(MethodDeclarator md) { result[0] = UnitCompiler.this.toIMethod((MethodDeclarator) fd); } @Override public void visitConstructorDeclarator(ConstructorDeclarator cd) { result[0] = UnitCompiler.this.toIConstructor((ConstructorDeclarator) fd); } // CHECKSTYLE LineLength:ON }); return result[0]; } /** 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, location); if (ss == null) return null; IClass iClass = this.findTypeByFullyQualifiedName(location, ss); if (iClass == null) { this.compileError("Imported class \"" + Java.join(ss, ".") + "\" could not be loaded", location); return this.iClassLoader.TYPE_java_lang_Object; } return iClass; } /** * Check if the given simple name was imported through a single type import. * * @param name The simple type name, e.g. {@code Inner} * @return The fully qualified name, e.g. { "pkg", "Outer", "Inner" }, or {@code null} */ public String[] getSingleTypeImport(String name, Location location) throws CompileException { // Resolve all single type imports (if not already done). if (this.singleTypeImports == null) { // Collect all single type import declarations. final List stids = new ArrayList(); for (ImportDeclaration id : this.compilationUnit.importDeclarations) { id.accept(new ImportVisitor() { @Override public void visitSingleTypeImportDeclaration(SingleTypeImportDeclaration stid) { stids.add(stid); } @Override public void visitTypeImportOnDemandDeclaration(TypeImportOnDemandDeclaration tiodd) {} @Override public void visitSingleStaticImportDeclaration(SingleStaticImportDeclaration ssid) {} @Override public void visitStaticImportOnDemandDeclaration(StaticImportOnDemandDeclaration siodd) {} }); } // Resolve all single type imports. this.singleTypeImports = new HashMap(); for (SingleTypeImportDeclaration stid : stids) { String[] ids = stid.identifiers; String simpleName = UnitCompiler.last(ids); // Check for re-import of same simple name. String[] prev = (String[]) this.singleTypeImports.put(simpleName, ids); if (prev != null && !Arrays.equals(prev, ids)) { UnitCompiler.this.compileError(( "Class \"" + simpleName + "\" was previously imported as " + "\"" + Java.join(prev, ".") + "\", now as \"" + Java.join(ids, ".") + "\"" ), stid.getLocation()); } if (this.findTypeByFullyQualifiedName(location, ids) == null) { UnitCompiler.this.compileError( "A class '" + Java.join(ids, ".") + "' could not be found", stid.getLocation() ); } } } return (String[]) this.singleTypeImports.get(name); } /** To be used only by {@link #getSingleTypeImport(String, Location)}; {@code null} means "not yet initialized" */ private Map singleTypeImports; /** * 6.5.2.BL1.B1.B5, 6.5.2.BL1.B1.B6 Type-import-on-demand.
* 6.5.5.1.6 Type-import-on-demand declaration. * * @return {@code null} if the given {@code simpleTypeName} 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... // Compile all import-on-demand declarations (done here as late as possible). if (this.typeImportsOnDemand == null) { this.typeImportsOnDemand = new ArrayList(); this.typeImportsOnDemand.add(new String[] { "java", "lang" }); for (ImportDeclaration id : this.compilationUnit.importDeclarations) { id.accept(new ImportVisitor() { @Override public void visitSingleTypeImportDeclaration(SingleTypeImportDeclaration stid) {} @Override public void visitTypeImportOnDemandDeclaration(TypeImportOnDemandDeclaration tiodd) { UnitCompiler.this.typeImportsOnDemand.add(tiodd.identifiers); } @Override public void visitSingleStaticImportDeclaration(SingleStaticImportDeclaration ssid) {} @Override public void visitStaticImportOnDemandDeclaration(StaticImportOnDemandDeclaration siodd) {} }); } } IClass importedClass = null; for (String[] packageComponents : this.typeImportsOnDemand) { String[] typeComponents = UnitCompiler.concat(packageComponents, simpleTypeName); IClass iClass = this.findTypeByFullyQualifiedName(location, typeComponents); 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; } /** To be used only by {@link #importTypeOnDemand(String, Location)}; {@code null} means "not yet initialized. */ private Collection typeImportsOnDemand; /** To be used only by {@link #importTypeOnDemand(String, Location)}; cache for on-demand-imported types. */ private final Map onDemandImportableTypes = new HashMap(); private void declareClassDollarMethod(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(); AbstractTypeDeclaration declaringType; for (Scope s = cl.getEnclosingBlockStatement();; s = s.getEnclosingScope()) { if (s instanceof AbstractTypeDeclaration) { declaringType = (AbstractTypeDeclaration) s; break; } } // try { // return Class.forName(className); final MethodInvocation mi = new MethodInvocation( loc, // location new SimpleType(loc, this.iClassLoader.TYPE_java_lang_Class), // optionalTarget "forName", // methodName new Rvalue[] { // arguments new 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(), ex); } 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(), ex); } if (noClassDefFoundErrorIClass == null) { throw new JaninoRuntimeException("SNO: Cannot load \"NoClassFoundError\""); } // catch (ClassNotFoundException e) { Block b = new Block(loc); // throw new NoClassDefFoundError(e.getMessage()); b.addStatement(new ThrowStatement(loc, new NewClassInstance( loc, // location (Rvalue) null, // optionalQualification new SimpleType(loc, noClassDefFoundErrorIClass), // type new Rvalue[] { // arguments new MethodInvocation( loc, // location new AmbiguousName(loc, new String[] { "ex" }), // optionalTarget "getMessage", // methodName new Rvalue[0] // arguments ) } ))); List l = new ArrayList(); l.add(new CatchClause( loc, // location new FormalParameter( // caughtException loc, // location true, // finaL new SimpleType(loc, classNotFoundExceptionIClass), // type "ex" // name ), b // body )); TryStatement ts = new TryStatement( loc, // location new ReturnStatement(loc, mi), // body l, // catchClauses null // optionalFinally ); List statements = new ArrayList(); statements.add(ts); // Class class$(String className) FormalParameter parameter = new FormalParameter( loc, // location false, // finaL new SimpleType(loc, this.iClassLoader.TYPE_java_lang_String), // type "className" // name ); MethodDeclarator cdmd = new MethodDeclarator( loc, // location null, // optionalDocComment new Modifiers(Mod.STATIC), // modifiers new SimpleType(loc, this.iClassLoader.TYPE_java_lang_Class), // type "class$", // name new FormalParameters( // parameters loc, new FormalParameter[] { parameter }, false ), new Type[0], // thrownExceptions statements // optionalStatements ); declaringType.addDeclaredMethod(cdmd); declaringType.invalidateMethodCaches(); } /** * @param value A {@link Character}, {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, * {@link Double}, {@link String}, {@link Boolean} or {@code null} */ private IClass pushConstant(Locatable locatable, Object value) throws CompileException { PUSH_INTEGER_CONSTANT: { int iv; if (value instanceof Character) { iv = ((Character) value).charValue(); } else if (value instanceof Byte) { iv = ((Byte) value).intValue(); } else if (value instanceof Short) { iv = ((Short) value).intValue(); } else if (value instanceof Integer) { iv = ((Integer) value).intValue(); } else { break PUSH_INTEGER_CONSTANT; } if (iv >= -1 && iv <= 5) { this.writeOpcode(locatable, Opcode.ICONST_0 + iv); } else if (iv >= Byte.MIN_VALUE && iv <= Byte.MAX_VALUE) { this.writeOpcode(locatable, Opcode.BIPUSH); this.writeByte((byte) iv); } else { this.writeLdc(locatable, this.addConstantIntegerInfo(iv)); } return IClass.INT; } if (value instanceof Long) { long lv = ((Long) value).longValue(); if (lv == 0L) { this.writeOpcode(locatable, Opcode.LCONST_0); } else if (lv == 1L) { this.writeOpcode(locatable, Opcode.LCONST_1); } else { this.writeOpcode(locatable, 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(locatable, Opcode.FCONST_0 + (int) fv); } else { this.writeLdc(locatable, 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(locatable, Opcode.DCONST_0 + (int) dv); } else { this.writeOpcode(locatable, Opcode.LDC2_W); this.writeConstantDoubleInfo(dv); } return IClass.DOUBLE; } if (value instanceof String) { String s = (String) value; String[] ss = UnitCompiler.makeUtf8Able(s); this.writeLdc(locatable, this.addConstantStringInfo(ss[0])); for (int i = 1; i < ss.length; ++i) { this.writeLdc(locatable, this.addConstantStringInfo(ss[i])); this.invoke(locatable, this.iClassLoader.METH_java_lang_String__concat__java_lang_String); } return this.iClassLoader.TYPE_java_lang_String; } if (Boolean.TRUE.equals(value)) { this.writeOpcode(locatable, Opcode.ICONST_1); return IClass.BOOLEAN; } if (Boolean.FALSE.equals(value)) { this.writeOpcode(locatable, Opcode.ICONST_0); return IClass.BOOLEAN; } if (value == null) { this.writeOpcode(locatable, Opcode.ACONST_NULL); return IClass.VOID; } throw new JaninoRuntimeException("Unknown literal '" + value + "'"); } private static int hex2Int(Locatable locatable, String value) throws CompileException { int result = 0; for (int i = 0; i < value.length(); ++i) { if ((result & 0xf0000000) != 0) { throw UnitCompiler.compileException( locatable, "Value of hexadecimal integer literal \"" + value + "\" is out of range" ); } result = (result << 4) + Character.digit(value.charAt(i), 16); } return result; } private static int oct2Int(Locatable locatable, String value) throws CompileException { int result = 0; for (int i = 0; i < value.length(); ++i) { if ((result & 0xe0000000) != 0) { throw UnitCompiler.compileException( locatable, "Value of octal integer literal '" + value + "' is out of range" ); } result = (result << 3) + Character.digit(value.charAt(i), 8); } return result; } private static long hex2Long(Locatable locatable, String value) throws CompileException { long result = 0L; for (int i = 0; i < value.length(); ++i) { if ((result & 0xf000000000000000L) != 0L) { throw UnitCompiler.compileException( locatable, "Value of hexadecimal long literal \"" + value + "\" is out of range" ); } result = (result << 4) + Character.digit(value.charAt(i), 16); } return result; } private static long oct2Long(Locatable locatable, String value) throws CompileException { long result = 0L; for (int i = 0; i < value.length(); ++i) { if ((result & 0xe000000000000000L) != 0) { throw UnitCompiler.compileException( locatable, "Value of octal long literal '" + value + "' is out of range" ); } result = (result << 3) + Character.digit(value.charAt(i), 8); } return result; } /** * 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 locatable, short index) { if (0 <= index && index <= 255) { this.writeOpcode(locatable, Opcode.LDC); this.writeByte((byte) index); } else { this.writeOpcode(locatable, Opcode.LDC_W); this.writeShort(index); } } /** Implements "assignment conversion" (JLS7 5.2). */ private void assignmentConversion( Locatable locatable, IClass sourceType, IClass targetType, Object optionalConstantValue ) throws CompileException { if (!this.tryAssignmentConversion(locatable, sourceType, targetType, optionalConstantValue)) { this.compileError( "Assignment conversion not possible from type \"" + sourceType + "\" to type \"" + targetType + "\"", locatable.getLocation() ); } } private boolean tryAssignmentConversion( Locatable locatable, IClass sourceType, IClass targetType, Object optionalConstantValue ) throws CompileException { if (UnitCompiler.DEBUG) { System.out.println( "assignmentConversion(" + sourceType + ", " + targetType + ", " + optionalConstantValue + ")" ); } // JLS7 5.1.1 Identity conversion. if (this.tryIdentityConversion(sourceType, targetType)) return true; // JLS7 5.1.2 Widening primitive conversion. if (this.tryWideningPrimitiveConversion(locatable, sourceType, targetType)) return true; // JLS7 5.1.4 Widening reference conversion. if (this.isWideningReferenceConvertible(sourceType, targetType)) return true; // A boxing conversion (JLS7 5.1.7) optionally followed by a widening reference conversion. { IClass boxedType = this.isBoxingConvertible(sourceType); if (boxedType != null) { if (this.tryIdentityConversion(boxedType, targetType)) { this.boxingConversion(locatable, sourceType, boxedType); return true; } if (this.isWideningReferenceConvertible(boxedType, targetType)) { this.boxingConversion(locatable, sourceType, boxedType); return true; } } } // An unboxing conversion (JLS7 5.1.8) optionally followed by a widening primitive conversion. { IClass unboxedType = this.isUnboxingConvertible(sourceType); if (unboxedType != null) { if (this.tryIdentityConversion(unboxedType, targetType)) { this.unboxingConversion(locatable, sourceType, unboxedType); return true; } if (this.isWideningPrimitiveConvertible(unboxedType, targetType)) { this.unboxingConversion(locatable, sourceType, unboxedType); this.tryWideningPrimitiveConversion(locatable, unboxedType, targetType); return true; } } } // 5.2 Special narrowing primitive conversion. if (optionalConstantValue != UnitCompiler.NOT_CONSTANT) { if (this.tryConstantAssignmentConversion( locatable, optionalConstantValue, // constantValue targetType // targetType )) return true; } return false; } /** Implements "assignment conversion" (JLS7 5.2) on a constant value. */ private Object assignmentConversion(Locatable locatable, Object value, IClass targetType) throws CompileException { if (targetType == IClass.BOOLEAN) { if (value instanceof Boolean) return value; } else if (targetType == this.iClassLoader.TYPE_java_lang_String) { if (value instanceof String) return value; } else if (targetType == IClass.BYTE) { if (value instanceof Byte) { return value; } else if (value instanceof Short || value instanceof Integer) { int x = ((Number) value).intValue(); if (x >= Byte.MIN_VALUE && x <= Byte.MAX_VALUE) return new Byte((byte) x); } else if (value instanceof Character) { int x = ((Character) value).charValue(); if (x >= Byte.MIN_VALUE && x <= Byte.MAX_VALUE) return new Byte((byte) x); } } else if (targetType == IClass.SHORT) { if (value instanceof Byte) { return new Short(((Number) value).shortValue()); } else if (value instanceof Short) { return value; } else if (value instanceof Character) { int x = ((Character) value).charValue(); if (x >= Short.MIN_VALUE && x <= Short.MAX_VALUE) return new Short((short) x); } else if (value instanceof Integer) { int x = ((Integer) value).intValue(); if (x >= Short.MIN_VALUE && x <= Short.MAX_VALUE) return new Short((short) x); } } else if (targetType == IClass.CHAR) { if (value instanceof Short) { return 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) return new Character((char) x); } } else if (targetType == IClass.INT) { if (value instanceof Integer) { return value; } else if (value instanceof Byte || value instanceof Short) { return new Integer(((Number) value).intValue()); } else if (value instanceof Character) { return new Integer(((Character) value).charValue()); } } else if (targetType == IClass.LONG) { if (value instanceof Long) { return value; } else if (value instanceof Byte || value instanceof Short || value instanceof Integer) { return new Long(((Number) value).longValue()); } else if (value instanceof Character) { return new Long(((Character) value).charValue()); } } else if (targetType == IClass.FLOAT) { if (value instanceof Float) { return value; } else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { return new Float(((Number) value).floatValue()); } else if (value instanceof Character) { return new Float(((Character) value).charValue()); } } else if (targetType == IClass.DOUBLE) { if (value instanceof Double) { return value; } else if ( value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long || value instanceof Float ) { return new Double(((Number) value).doubleValue()); } else if (value instanceof Character) { return new Double(((Character) value).charValue()); } } else if (value == null && !targetType.isPrimitive()) { return null; } if (value == null) { this.compileError(( "Cannot convert 'null' to type '" + targetType.toString() + "'" ), locatable.getLocation()); } else { this.compileError(( "Cannot convert constant of type '" + value.getClass().getName() + "' to type '" + targetType.toString() + "'" ), locatable.getLocation()); } return value; } /** * Implements "unary numeric promotion" (JLS7 5.6.1). * * @return The promoted type */ private IClass unaryNumericPromotion(Locatable locatable, IClass type) throws CompileException { type = this.convertToPrimitiveNumericType(locatable, type); IClass promotedType = this.unaryNumericPromotionType(locatable, type); this.numericPromotion(locatable, type, promotedType); return promotedType; } private void reverseUnaryNumericPromotion(Locatable locatable, IClass sourceType, IClass targetType) throws CompileException { IClass unboxedType = this.isUnboxingConvertible(targetType); IClass pt = unboxedType != null ? unboxedType : targetType; if ( !this.tryIdentityConversion(sourceType, pt) && !this.tryNarrowingPrimitiveConversion( locatable, // locatable sourceType, // sourceType pt // targetType ) ) throw new JaninoRuntimeException("SNO: reverse unary numeric promotion failed"); if (unboxedType != null) this.boxingConversion(locatable, unboxedType, targetType); } /** * If the given type is a primitive type, return that type. If the given type is a primitive wrapper class, unbox * the operand on top of the operand stack and return the primitive type. Otherwise, issue a compile error. */ private IClass convertToPrimitiveNumericType(Locatable locatable, IClass type) throws CompileException { if (type.isPrimitiveNumeric()) return type; IClass unboxedType = this.isUnboxingConvertible(type); if (unboxedType != null) { this.unboxingConversion(locatable, type, unboxedType); return unboxedType; } this.compileError( "Object of type \"" + type.toString() + "\" cannot be converted to a numeric type", locatable.getLocation() ); return type; } private void numericPromotion(Locatable locatable, IClass sourceType, IClass targetType) { if ( !this.tryIdentityConversion(sourceType, targetType) && !this.tryWideningPrimitiveConversion( locatable, // locatable sourceType, // sourceType targetType // targetType ) ) throw new JaninoRuntimeException("SNO: Conversion failed"); } private IClass unaryNumericPromotionType(Locatable locatable, IClass type) throws CompileException { if (!type.isPrimitiveNumeric()) { this.compileError( "Unary numeric promotion not possible on non-numeric-primitive type \"" + type + "\"", locatable.getLocation() ); } return ( type == IClass.DOUBLE ? IClass.DOUBLE : type == IClass.FLOAT ? IClass.FLOAT : type == IClass.LONG ? IClass.LONG : IClass.INT ); } /** * 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), which may perform unboxing conversion. * * @return The promoted type. */ private IClass binaryNumericPromotion( Locatable locatable, 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( locatable, c1 != null ? c1 : type1, c2 != null ? c2 : type2 ); } if (convertInserter1 != null) { this.codeContext.pushInserter(convertInserter1); try { this.numericPromotion(locatable, this.convertToPrimitiveNumericType(locatable, type1), promotedType); } finally { this.codeContext.popInserter(); } } if (convertInserter2 != null) { this.codeContext.pushInserter(convertInserter2); try { this.numericPromotion(locatable, this.convertToPrimitiveNumericType(locatable, type2), promotedType); } finally { this.codeContext.popInserter(); } } return promotedType; } private IClass binaryNumericPromotionType(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 */ @SuppressWarnings("static-method") private boolean isIdentityConvertible(IClass sourceType, IClass targetType) { return sourceType == targetType; } /** * Implements "identity conversion" (5.1.1). * * @return Whether the conversion was possible */ @SuppressWarnings("static-method") private boolean tryIdentityConversion(IClass sourceType, IClass targetType) { return sourceType == targetType; } @SuppressWarnings("static-method") 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 locatable, IClass sourceType, IClass targetType) { byte[] opcodes = (byte[]) UnitCompiler.PRIMITIVE_WIDENING_CONVERSIONS.get( sourceType.getDescriptor() + targetType.getDescriptor() ); if (opcodes != null) { this.writeOpcodes(locatable, opcodes); return true; } return false; } private static final Map 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, Map map) { byte[] opcodes = null; for (Object o : array) { if (o instanceof byte[]) { opcodes = (byte[]) o; } else { map.put((String) o, opcodes); } } } /** * Checks if "widening reference conversion" (5.1.4) is possible. * * @return Whether the conversion is possible */ @SuppressWarnings("static-method") private boolean isWideningReferenceConvertible(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 */ @SuppressWarnings("static-method") private boolean tryWideningReferenceConversion(IClass sourceType, IClass targetType) throws CompileException { if (targetType.isPrimitive() || sourceType == targetType) return false; return targetType.isAssignableFrom(sourceType); } /** Checks whether "narrowing primitive conversion" (JLS7 5.1.3) is possible. */ @SuppressWarnings("static-method") private boolean isNarrowingPrimitiveConvertible(IClass sourceType, IClass targetType) { return UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS.containsKey( sourceType.getDescriptor() + targetType.getDescriptor() ); } /** * Implements "narrowing primitive conversion" (JLS7 5.1.3). * * @return Whether the conversion succeeded */ private boolean tryNarrowingPrimitiveConversion(Locatable locatable, IClass sourceType, IClass targetType) { byte[] opcodes = (byte[]) UnitCompiler.PRIMITIVE_NARROWING_CONVERSIONS.get( sourceType.getDescriptor() + targetType.getDescriptor() ); if (opcodes != null) { this.writeOpcodes(locatable, opcodes); return true; } return false; } private static final Map 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" (JLS7 5.2, paragraph 1) is possible. * * @param constantValue The constant value that is to be converted * @param targetType The type to convert to */ private boolean tryConstantAssignmentConversion(Locatable locatable, 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.TYPE_java_lang_Byte && cv >= Byte.MIN_VALUE && cv <= Byte.MAX_VALUE) { this.boxingConversion(locatable, IClass.BYTE, targetType); return true; } if (targetType == icl.TYPE_java_lang_Short && cv >= Short.MIN_VALUE && cv <= Short.MAX_VALUE) { this.boxingConversion(locatable, IClass.SHORT, targetType); return true; } if (targetType == icl.TYPE_java_lang_Character && cv >= Character.MIN_VALUE && cv <= Character.MAX_VALUE) { this.boxingConversion(locatable, IClass.CHAR, targetType); return true; } return false; } /** Check whether "narrowing reference conversion" (JLS7 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.TYPE_java_lang_Object && targetType.isArray()) return true; // 5.1.5.4 if (sourceType == this.iClassLoader.TYPE_java_lang_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 locatable, IClass sourceType, IClass targetType) throws CompileException { if (!this.isNarrowingReferenceConvertible(sourceType, targetType)) return false; this.writeOpcode(locatable, Opcode.CHECKCAST); this.writeConstantClassInfo(targetType.getDescriptor()); return true; } /** JLS7 5.5 */ private boolean isCastReferenceConvertible(IClass sourceType, IClass targetType) throws CompileException { return ( this.isIdentityConvertible(sourceType, targetType) || this.isWideningReferenceConvertible(sourceType, targetType) || this.isNarrowingReferenceConvertible(sourceType, targetType) ); } /** @return The boxed type or {@code null} */ private IClass isBoxingConvertible(IClass sourceType) { IClassLoader icl = this.iClassLoader; if (sourceType == IClass.BOOLEAN) return icl.TYPE_java_lang_Boolean; if (sourceType == IClass.BYTE) return icl.TYPE_java_lang_Byte; if (sourceType == IClass.CHAR) return icl.TYPE_java_lang_Character; if (sourceType == IClass.SHORT) return icl.TYPE_java_lang_Short; if (sourceType == IClass.INT) return icl.TYPE_java_lang_Integer; if (sourceType == IClass.LONG) return icl.TYPE_java_lang_Long; if (sourceType == IClass.FLOAT) return icl.TYPE_java_lang_Float; if (sourceType == IClass.DOUBLE) return icl.TYPE_java_lang_Double; return null; } private boolean tryBoxingConversion(Locatable locatable, IClass sourceType, IClass targetType) throws CompileException { if (this.isBoxingConvertible(sourceType) == targetType) { this.boxingConversion(locatable, sourceType, targetType); return true; } return false; } /** * @param sourceType a primitive type (except VOID) * @param targetType the corresponding wrapper type */ private void boxingConversion(Locatable locatable, 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(locatable, Opcode.INVOKESTATIC); this.writeConstantMethodrefInfo( targetType.getDescriptor(), // classFD "valueOf", // methodName '(' + sourceType.getDescriptor() + ')' + targetType.getDescriptor() // methodFD ); return; } // new Target(source) this.writeOpcode(locatable, Opcode.NEW); this.writeConstantClassInfo(targetType.getDescriptor()); if (Descriptor.hasSize2(sourceType.getDescriptor())) { this.writeOpcode(locatable, Opcode.DUP_X2); this.writeOpcode(locatable, Opcode.DUP_X2); this.writeOpcode(locatable, Opcode.POP); } else { this.writeOpcode(locatable, Opcode.DUP_X1); this.writeOpcode(locatable, Opcode.SWAP); } this.writeOpcode(locatable, Opcode.INVOKESPECIAL); this.writeConstantMethodrefInfo( targetType.getDescriptor(), // classFd "", // methodName '(' + sourceType.getDescriptor() + ')' + Descriptor.VOID // methodMd ); } /** @return Iff {@code sourceType} is a primitive wrapper type, the unboxed type, otherwise {@code null} */ private IClass isUnboxingConvertible(IClass sourceType) { IClassLoader icl = this.iClassLoader; if (sourceType == icl.TYPE_java_lang_Boolean) return IClass.BOOLEAN; if (sourceType == icl.TYPE_java_lang_Byte) return IClass.BYTE; if (sourceType == icl.TYPE_java_lang_Character) return IClass.CHAR; if (sourceType == icl.TYPE_java_lang_Short) return IClass.SHORT; if (sourceType == icl.TYPE_java_lang_Integer) return IClass.INT; if (sourceType == icl.TYPE_java_lang_Long) return IClass.LONG; if (sourceType == icl.TYPE_java_lang_Float) return IClass.FLOAT; if (sourceType == icl.TYPE_java_lang_Double) return IClass.DOUBLE; return null; } /** * @return Whether the {@code sourceType} is a primitive numeric type, or a wrapper type of a primitive numeric * type */ private boolean isConvertibleToPrimitiveNumeric(IClass sourceType) { if (sourceType.isPrimitiveNumeric()) return true; IClass unboxedType = this.isUnboxingConvertible(sourceType); return unboxedType != null && unboxedType.isPrimitiveNumeric(); } private boolean tryUnboxingConversion(Locatable locatable, IClass sourceType, IClass targetType, Inserter optionalInserter) { if (this.isUnboxingConvertible(sourceType) == targetType) { this.unboxingConversion(locatable, sourceType, targetType, optionalInserter); return true; } return false; } /** * @param targetType a primitive type (except VOID) * @param sourceType the corresponding wrapper type */ private void unboxingConversion(Locatable locatable, IClass sourceType, IClass targetType, Inserter optionalInserter) { if (optionalInserter == null) { this.unboxingConversion(locatable, sourceType, targetType); } else { this.codeContext.pushInserter(optionalInserter); try { this.unboxingConversion(locatable, sourceType, targetType); } finally { this.codeContext.popInserter(); } } } /** * @param targetType a primitive type (except VOID) * @param sourceType the corresponding wrapper type */ private void unboxingConversion(Locatable locatable, IClass sourceType, IClass targetType) { // "source.targetValue()" this.writeOpcode(locatable, Opcode.INVOKEVIRTUAL); this.writeConstantMethodrefInfo( sourceType.getDescriptor(), // classFD targetType.toString() + "Value", // methodName "()" + targetType.getDescriptor() // methodFD ); } /** * Attempts to load an {@link IClass} by fully-qualified name through {@link #iClassLoader}. * @param location TODO * @param identifiers The fully qualified type name, e.g. '{ "pkg", "Outer", "Inner" }' * * @return {@code null} if a class with the given name could not be loaded * @throws CompileException The type exists, but a problem occurred when it was loaded */ private IClass findTypeByFullyQualifiedName(Location location, String[] identifiers) throws CompileException { // Try all 'flavors', i.e. 'a.b.c', 'a.b$c', 'a$b$c'. String className = Java.join(identifiers, "."); for (;;) { IClass iClass = UnitCompiler.this.findTypeByName(location, className); if (iClass != null) return iClass; int idx = className.lastIndexOf('.'); if (idx == -1) break; className = className.substring(0, idx) + '$' + className.substring(idx + 1); } return null; } // Load the value of a local variable onto the stack and return its type. private IClass load(Locatable locatable, LocalVariable localVariable) { this.load(locatable, localVariable.type, localVariable.getSlotIndex()); return localVariable.type; } private void load(Locatable locatable, IClass type, int index) { if (index <= 3) { this.writeOpcode(locatable, Opcode.ILOAD_0 + 4 * UnitCompiler.ilfda(type) + index); } else if (index <= 255) { this.writeOpcode(locatable, Opcode.ILOAD + UnitCompiler.ilfda(type)); this.writeByte(index); } else { this.writeOpcode(locatable, Opcode.WIDE); this.writeOpcode(locatable, Opcode.ILOAD + UnitCompiler.ilfda(type)); this.writeShort(index); } } /** * Assign top stack top value to the given local variable. */ private void store(Locatable locatable, LocalVariable localVariable) { this.store( locatable, // locatable localVariable.type, // lvType localVariable.getSlotIndex() // lvIndex ); } private void store(Locatable locatable, IClass lvType, short lvIndex) { if (lvIndex <= 3) { this.writeOpcode(locatable, Opcode.ISTORE_0 + 4 * UnitCompiler.ilfda(lvType) + lvIndex); } else if (lvIndex <= 255) { this.writeOpcode(locatable, Opcode.ISTORE + UnitCompiler.ilfda(lvType)); this.writeByte(lvIndex); } else { this.writeOpcode(locatable, Opcode.WIDE); this.writeOpcode(locatable, Opcode.ISTORE + UnitCompiler.ilfda(lvType)); this.writeShort(lvIndex); } } private void getfield(Locatable locatable, IClass.IField iField) throws CompileException { this.writeOpcode(locatable, iField.isStatic() ? Opcode.GETSTATIC : Opcode.GETFIELD); this.writeConstantFieldrefInfo( iField.getDeclaringIClass().getDescriptor(), // classFD iField.getName(), // fieldName iField.getDescriptor() // fieldFD ); } private void putfield(Locatable locatable, IField iField) throws CompileException { this.writeOpcode(locatable, iField.isStatic() ? Opcode.PUTSTATIC : Opcode.PUTFIELD); this.writeConstantFieldrefInfo( iField.getDeclaringIClass().getDescriptor(), // classFD iField.getName(), // fieldName iField.getDescriptor() // fieldFD ); } private void dup(Locatable locatable, int n) { switch (n) { case 0: ; break; case 1: this.writeOpcode(locatable, Opcode.DUP); break; case 2: this.writeOpcode(locatable, Opcode.DUP2); break; default: throw new JaninoRuntimeException("dup(" + n + ")"); } } private void dupx(Locatable locatable, 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(locatable, type == IClass.LONG || type == IClass.DOUBLE ? dup2 : dup); } private void pop(Locatable locatable, IClass type) { if (type == IClass.VOID) return; this.writeOpcode(locatable, type == IClass.LONG || type == IClass.DOUBLE ? Opcode.POP2 : Opcode.POP); } private static int ilfd(final 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 + "\""); } private 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 static int ilfda(IClass t) { return !t.isPrimitive() ? 4 : UnitCompiler.ilfd(t); } private static int ilfdabcs(IClass t) { if (t == IClass.INT) return 0; if (t == IClass.LONG) return 1; if (t == IClass.FLOAT) return 2; if (t == IClass.DOUBLE) return 3; if (!t.isPrimitive()) return 4; if (t == IClass.BOOLEAN) return 5; if (t == IClass.BYTE) return 5; if (t == IClass.CHAR) return 6; if (t == IClass.SHORT) return 7; throw new JaninoRuntimeException("Unexpected type \"" + t + "\""); } private void invoke(Locatable locatable, IMethod iMethod) throws CompileException { if (iMethod.getDeclaringIClass().isInterface()) { this.writeOpcode(locatable, Opcode.INVOKEINTERFACE); this.writeConstantInterfaceMethodrefInfo( iMethod.getDeclaringIClass().getDescriptor(), // classFD iMethod.getName(), // methodName iMethod.getDescriptor() // methodMD ); int count = 1; for (IClass pt : iMethod.getParameterTypes()) count += Descriptor.size(pt.getDescriptor()); this.writeByte(count); this.writeByte(0); } else { this.writeOpcode(locatable, iMethod.isStatic() ? Opcode.INVOKESTATIC : Opcode.INVOKEVIRTUAL); this.writeConstantMethodrefInfo( iMethod.getDeclaringIClass().getDescriptor(), // classFD iMethod.getName(), // methodName iMethod.getDescriptor() // methodMD ); } } private void invoke(Locatable locatable, IConstructor iConstructor) throws CompileException { this.writeOpcode(locatable, Opcode.INVOKESPECIAL); this.writeConstantMethodrefInfo( iConstructor.getDeclaringIClass().getDescriptor(), // classFD "", // methodName iConstructor.getDescriptor() // methodMD ); } /** * Finds a named field in the given {@link IClass}. Honors superclasses and interfaces. See JLS7 8.3. * * @return {@code null} if no field is found */ private IClass.IField findIField(IClass iClass, String name, Location location) throws CompileException { // Search for a field with the given name in the current class. IClass.IField f = iClass.getDeclaredIField(name); if (f != null) return f; // Examine superclass. { IClass superclass = iClass.getSuperclass(); if (superclass != null) f = this.findIField(superclass, name, location); } // Examine interfaces. IClass[] ifs = iClass.getInterfaces(); for (IClass iF : ifs) { IClass.IField f2 = this.findIField(iF, name, location); if (f2 != null) { if (f != null) { throw new CompileException(( "Access to field \"" + name + "\" is ambiguous - both \"" + f.getDeclaringIClass() + "\" and \"" + f2.getDeclaringIClass() + "\" declare it" ), location); } f = f2; } } return f; } /** * Finds a named type in the given {@link IClass}. Honors superclasses, interfaces and enclosing type declarations. * * @return {@code null} 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]; StringBuilder sb = new StringBuilder("Type \"").append(name).append("\" is ambiguous: ").append(types[0]); for (int i = 1; i < types.length; ++i) sb.append(" vs. ").append(types[i].toString()); this.compileError(sb.toString(), location); return types[0]; } /** * 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} if a class or an interface 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, "$"); 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(td); } /** Equivalent with {@link #compileError(String, Location)} with a {@code null} 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)}. *

* The {@code handle} 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) throws CompileException { if (this.optionalWarningHandler != null) { this.optionalWarningHandler.handleWarning(handle, message, optionalLocation); } } /** * By default, {@link CompileException}s are thrown on compile errors, but an application my install its own * (thread-local) {@link ErrorHandler}. *

* Be aware that a single problem during compilation often causes a bunch of compile errors, so a good {@link * ErrorHandler} counts errors and throws a {@link CompileException} when a limit is reached. *

* If the given {@link ErrorHandler} does not throw {@link CompileException}s, then {@link * #compileUnit(boolean, boolean, boolean)} 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} 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} to indicate that no warnings be issued */ public void setWarningHandler(WarningHandler optionalWarningHandler) { this.optionalWarningHandler = optionalWarningHandler; } private CodeContext replaceCodeContext(CodeContext newCodeContext) { CodeContext oldCodeContext = this.codeContext; this.codeContext = newCodeContext; return oldCodeContext; } 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(Locatable locatable, int opcode) { this.codeContext.write(locatable.getLocation().getLineNumber(), (byte) opcode); } private void writeOpcodes(Locatable locatable, byte[] opcodes) { this.codeContext.write(locatable.getLocation().getLineNumber(), opcodes); } private void writeBranch(Locatable locatable, int opcode, final CodeContext.Offset dst) { this.codeContext.writeBranch(locatable.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)); } private CodeContext.Offset getWhereToBreak(BreakableStatement bs) { if (bs.whereToBreak == null) { bs.whereToBreak = this.codeContext.new Offset(); } return bs.whereToBreak; } private TypeBodyDeclaration getDeclaringTypeBodyDeclaration(QualifiedThisReference qtr) throws CompileException { if (qtr.declaringTypeBodyDeclaration == null) { // Compile error if in static function context. Scope s; for ( s = qtr.getEnclosingBlockStatement(); !(s instanceof TypeBodyDeclaration); s = s.getEnclosingScope() ); qtr.declaringTypeBodyDeclaration = (TypeBodyDeclaration) s; if (qtr.declaringTypeBodyDeclaration.isStatic()) { this.compileError("No current instance available in static method", qtr.getLocation()); } // Determine declaring type. qtr.declaringClass = (ClassDeclaration) qtr.declaringTypeBodyDeclaration.getDeclaringType(); } return qtr.declaringTypeBodyDeclaration; } private ClassDeclaration getDeclaringClass(QualifiedThisReference qtr) throws CompileException { if (qtr.declaringClass == null) { this.getDeclaringTypeBodyDeclaration(qtr); } return qtr.declaringClass; } private void referenceThis(Locatable locatable) { this.writeOpcode(locatable, Opcode.ALOAD_0); } /** * Expects {@code dimExprCount} values of type {@code int} on the operand stack. Creates an array of {@code * dimExprCount + dims} dimensions of {@code componentType}. * * @return The type of the created array */ private IClass newArray(Locatable locatable, int dimExprCount, int dims, IClass componentType) { if (dimExprCount == 1 && dims == 0 && componentType.isPrimitive()) { // "new []" this.writeOpcode(locatable, 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.TYPE_java_lang_Object); } if (dimExprCount == 1) { IClass at = componentType.getArrayIClass(dims, this.iClassLoader.TYPE_java_lang_Object); // "new []" // "new [][]..." this.writeOpcode(locatable, Opcode.ANEWARRAY); this.writeConstantClassInfo(at.getDescriptor()); return at.getArrayIClass(this.iClassLoader.TYPE_java_lang_Object); } else { IClass at = componentType.getArrayIClass(dimExprCount + dims, this.iClassLoader.TYPE_java_lang_Object); // "new []..." // "new [][]..." // "new [][]...[]..." this.writeOpcode(locatable, 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; } @Override public Object getConstantValue() { return UnitCompiler.NOT_CONSTANT; } @Override public String getName() { return this.name; } @Override public IClass getType() { return this.type; } @Override public boolean isStatic() { return false; } @Override public Access getAccess() { return Access.DEFAULT; } @Override public Annotation[] getAnnotations() { return new Annotation[0]; } } private static Access modifiers2Access(short modifiers) { return ( Mod.isPublicAccess(modifiers) ? Access.PUBLIC : Mod.isProtectedAccess(modifiers) ? Access.PROTECTED : Mod.isPrivateAccess(modifiers) ? 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; } private static CompileException compileException(Locatable locatable, String message) { return new CompileException(message, locatable.getLocation()); } private static String unescape(String s) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length();) { char c = s.charAt(i++); if (c != '\\') { sb.append(c); continue; } c = s.charAt(i++); { int idx = "btnfr\"'\\".indexOf(c); if (idx != -1) { sb.append("\b\t\n\f\r\"'\\".charAt(idx)); continue; } } { int x = Character.digit(c, 8); if (x == -1) throw new JaninoRuntimeException("Invalid escape sequence '\\" + c + "'"); if (i < s.length()) { c = s.charAt(i); int secondDigit = Character.digit(c, 8); if (secondDigit != -1) { x = 8 * x + secondDigit; i++; if (i < s.length() && x <= 037) { int thirdDigit = Character.digit(c, 8); if (thirdDigit != -1) { x = 8 * x + thirdDigit; i++; } } } } sb.append((char) x); } } return sb.toString(); } // Used to write byte code while compiling one constructor/method. private CodeContext codeContext; // Used for elaborate compile error handling. private ErrorHandler optionalCompileErrorHandler; private int compileErrorCount; // Used for elaborate warning handling. private WarningHandler optionalWarningHandler; private final CompilationUnit compilationUnit; private final IClassLoader iClassLoader; private List generatedClassFiles; private boolean debugSource; private boolean debugLines; private boolean debugVars; private final Map> singleStaticImports = new HashMap(); private final Collection staticImportsOnDemand = new ArrayList(); }