/* * xtc - The eXTensible Compiler * Copyright (C) 2006-2011 IBM Corp., Robert Grimm * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package xtc.lang; import java.io.File; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import xtc.Constants; import xtc.tree.Attribute; import xtc.tree.GNode; import xtc.tree.Node; import xtc.tree.Visitor; import xtc.type.AliasT; import xtc.type.AnnotatedT; import xtc.type.ArrayT; import xtc.type.ClassOrInterfaceT; import xtc.type.ClassT; import xtc.type.ErrorT; import xtc.type.IntegerT; import xtc.type.InterfaceT; import xtc.type.LabelT; import xtc.type.MethodT; import xtc.type.NumberT; import xtc.type.PackageT; import xtc.type.Type; import xtc.type.VoidT; import xtc.type.WrappedT; import xtc.util.Runtime; import xtc.util.SymbolTable; /** * A visitor that constructs a symbol table for a Java compilation unit. Assumes * that the AST has been simplified with JavaAstSimplifier. So far, the visitor * doesn't check for errors properly, it only populates the symbol table and * annotates AST nodes with their type. * * <h4>Jacks regression tests</h4> * * You can use JavaDriver to run the "jacks" compiler regression * testing suite for this type checker. * Download jacks by doing<ul> * <li>cvs -z 9 -d :pserver:anoncvs@sources.redhat.com:/cvs/mauve co jacks</ul> * Make sure it works by setting JAVAC in setup_javac, then doing<ul> * <li>./jacks javac "3.2-valid-1 3.2-invalid-1"</ul> * That should report something like<ul> * <li>javac: Total 5011 Passed 2 Skipped 5009 Failed 0</ul> * Create an xtc_setup file: copy javac_setup, then change:<ul> * <li>set JAVA_HOME "" * <li>set JAVAC /usr/bin/java * <li>set JAVAC_FLAGS "-cp /Users/hirzel/jeannie/java/classes xtc.lang.JavaDriver -ast -simplifyAST -analyze" set JAVA "this_variable_intentionally_nonsensical" * <li>set JAVA "this_variable_intentionally_nonsensical"</ul> * or whatever is appropriate for your paths. Yes, we intentionally set JAVAC "java", * since xtc.lang.JavaAnalyzer will run on a Java virtual machine.<br> * Now, try that it works by doing<ul> * <li>./jacks xtc "3.2-valid-1 3.2-invalid-1"</ul> * That should report something like<ul> * <li>xtc: Total 5011 Passed 2 Skipped 5009 Failed 0</ul> * To run the remaining 5009 tests, omit the last command line argument, and be * very patient. * * @author Martin Hirzel */ public class JavaAnalyzer extends Visitor { public static final class JavaContext { /** Handled by enclosing try/catch, or declared as thrown by enclosing method. */ public List<Type> _handledExceptions = new ArrayList<Type>(); /** False if scope should also include parameters of method, catch, or for. */ public boolean _hasScope = true; /** Current expression or array initializer is expected to initialize variable of target type. */ Type _initializing = null; /** Can do "break" or "continue" for enclosing while/do/for loop. */ public boolean _loop = false; /** Can not use "this" or instance member. */ public boolean _static = false; /** Can do "break" for enclosing switch statement. */ public boolean _switch = false; public final void restore(final JavaContext other) { _handledExceptions = other._handledExceptions; _hasScope = other._hasScope; _initializing = other._initializing; _loop = other._loop; _static = other._static; _switch = other._switch; } public final JavaContext save() { final JavaContext result = new JavaContext(); result.restore(this); return result; } } public static Type getRValueNoError(final Type type) { assert null != type; if (type.isVoid()) return type; if (JavaEntities.isExpressionT(type)) { final boolean isL = JavaEntities.isGeneralLValueT(type); final Type result = isL ? JavaEntities.dereference(type) : type; assert JavaEntities.isGeneralRValueT(result); return result; } return null; } protected static boolean hasModifier(final Type t, final String m) { return JavaEntities.hasModifier(t, m); } public static Type setType(final Node n, final Type result) { if (result.isMethod()) assert n.hasName("Arguments") || n.hasName("MethodDeclaration") || n.hasName("DeconstructorDeclaration"); n.setProperty(Constants.TYPE, result); return result; } protected final JavaExternalAnalyzer _externalAnalyzer; public final JavaContext _context; protected final Runtime _runtime; protected final SymbolTable _table; public JavaAnalyzer(final Runtime runtime, final SymbolTable table) { _context = new JavaContext(); _externalAnalyzer = newExternalAnalyzer(runtime, table); _runtime = runtime; _table = table; JavaEntities.addBaseTypes(_table); } public JavaExternalAnalyzer newExternalAnalyzer(final Runtime runtime, final SymbolTable table) { return new JavaExternalAnalyzer(runtime, table); } /** Use this for asserting that the input is typed correctly. */ protected boolean assrt(final Node n, final boolean cond, final String msgFormat, final Object... msgArgs) { return JavaEntities.runtimeAssrt(_runtime, n, cond, msgFormat, msgArgs); } protected void assrtLegalHandledExceptions(final GNode n) { assert null == n || n.hasName("ThrowsClause") : n.getName(); final Type tThrowable = JavaEntities.tThrowable(_table); final List<Type> legal = new ArrayList<Type>(); for (int i=0; i<_context._handledExceptions.size(); i++) { final Type e = resolveIfAlias(_context._handledExceptions.get(i), n.getNode(i)); final boolean ok = e.isError() || JavaTypeConverter.isAssignable(_table, classpath(), tThrowable, e); if (assrt(n.getNode(i), ok, "throwable expected")) legal.add(e); } _context._handledExceptions = legal; } protected void assrtLegalIdentifier(final GNode n, final String id) { assrt(n, !"true".equals(id), "illegal identifier"); assrt(n, !"false".equals(id), "illegal identifier"); assrt(n, !"null".equals(id), "illegal identifier"); } protected void assrtLegalMethod(final GNode n, final MethodT method) { final ClassOrInterfaceT base = JavaEntities.declaringType(_table, method); for (final MethodT m : JavaEntities.methodsOwn(base)) assrt(n, m == method || !JavaEntities.sameMethodSignature(method, m), "duplicate method"); if (!JavaEntities.isConstructor(JavaEntities.resolveToRawClassOrInterfaceT(base), method)) { final List<MethodT> methodsInherited = JavaEntities.methodsInherited(_table, classpath(), base, true); for (final MethodT sup : methodsInherited) if (JavaEntities.isSuperMethod(_table, classpath(), sup, method)) assrtLegalOverride(n, sup, method); } } protected void assrtLegalMethodBody(final GNode n, final Type method) { if (null == n.get(7)) assrt(n, hasModifier(method, "abstract") || hasModifier(method, "native"), "missing method body"); else assrt(n, !hasModifier(method, "abstract") && !hasModifier(method, "native"), "unexpected method body"); } private void assrtLegalOverride(final GNode n, final MethodT sup, final MethodT sub) { assrt(n, !hasModifier(sup, "final"), "cannot override final method"); if (hasModifier(sup, "static")) assrt(n, hasModifier(sub, "static"), "instance method cannot override static method"); else if (JavaEntities.sameMethodSignature(sup, sub)) assrt(n, !hasModifier(sub, "static"), "static method cannot hide instance method"); if (hasModifier(sup, "public")) assrt(n, hasModifier(sub, "public"), "cannot reduce visibility"); else if (hasModifier(sup, "protected")) assrt(n, hasModifier(sub, "public") || hasModifier(sub, "protected"), "cannot reduce visibility"); else assrt(n, !hasModifier(sub, "private"), "cannot reduce visibility"); resolveIfAlias(sup.getResult()); assrt(n, JavaEntities.sameMethodReturnType(sup, sub), "incompatible return type"); for (final Type e1 : sub.getExceptions()) { boolean declared = false; for (final Type e2 : sup.getExceptions()) if (JavaEntities.isSuperClass(_table, classpath(), e2, e1)) { declared = true; break; } assrt(n, declared, "incompatible throws clause in overriding method"); } } public final List<File> classpath() { return JavaEntities.classpath(_runtime); } public Type dispatchRValue(final GNode n) { final Type type = (Type) dispatch(n); if (null == type || type.isPackage()) { _runtime.error("unknown or ambiguous name", n); return JavaEntities.nameToBaseType("int"); } return getRValue(type, n); } private static char escapeSequenceChar(final String s, final int start) { // gosling_et_al_2000 3.3 and 3.10.6 final int len = s.length(); assert start + 1 <= len && '\\' == s.charAt(start); switch (s.charAt(start + 1)) { case 'b': return '\b'; case 't': return '\t'; case 'n': return '\n'; case 'f': return '\f'; case 'r': return '\r'; case '"': return '"'; case '\'': return '\''; case '\\': return '\\'; } int c = 0, i = start + 1; while (i < len && '0' <= s.charAt(i) && s.charAt(i) < '8') { c = 8 * c + s.charAt(i) - '0'; i++; } assert i != start + 1; return (char) c; } private final int escapeSequenceEnd(final String s, final int start) { // gosling_et_al_2000 3.3 and 3.10.6 final int len = s.length(); assert start + 1 <= len && '\\' == s.charAt(start); switch (s.charAt(start + 1)) { case 'b': case 't': case 'n': case 'f': case 'r': case '"': case '\'': case '\\': return start + 2; } int c = 0, i = start + 1; while (i < len && '0' <= s.charAt(i) && s.charAt(i) < '8') { c = 8 * c + s.charAt(i) - '0'; i++; } return i; } private Type getRValue(final Type type, final GNode n) { final Type result = getRValueNoError(type); if (null == result) { _runtime.error("unknown or ambiguous name", n); return ErrorT.TYPE; } return result; } private final boolean isAssignable(final int src, final Type tgt) { // gosling_et_al_2000 5.2 if (JavaEntities.nameToBaseType("byte") == tgt) return (byte) src == src; if (JavaEntities.nameToBaseType("char") == tgt) return (char) src == src; if (JavaEntities.nameToBaseType("short") == tgt) return (short) src == src; return true; } public boolean isHandled(final Type tThrown) { final List<File> classpath = classpath(); for (final Type tCaught : _context._handledExceptions) if (JavaTypeConverter.isAssignable(_table, classpath, tCaught, tThrown)) return true; return false; } private boolean isStringConstant(final Type x) { return x.hasConstant() && JavaEntities.isReferenceT(x) && JavaTypeConverter.isIdentical(x, JavaEntities.tString(_table)); } private Type processBitwiseBinaryExpression(final GNode n, final String operator) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(1)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final Type result; Type tBool = JavaEntities.nameToBaseType("boolean"); final boolean xIsBool = JavaTypeConverter.isIdentical(x, tBool), yIsBool = JavaTypeConverter.isIdentical(y, tBool); assert "&".equals(operator) || "|".equals(operator) || "^".equals(operator); if (xIsBool || yIsBool) { if (!assrt(n, xIsBool && yIsBool, "operator type mismatch")) return setType(n, ErrorT.TYPE); if (x.hasConstant() && y.hasConstant()) { final boolean valX = x.getConstant().isTrue(); final boolean valY = y.getConstant().isTrue(); if ("&".equals(operator)) result = tBool.annotate().constant(valX & valY); else if ("|".equals(operator)) result = tBool.annotate().constant(valX | valY); else result = tBool.annotate().constant(valX ^ valY); } else { result = tBool; } } else { final Type promX = JavaTypeConverter.promoteBinaryNumeric(y, x); final Type promY = JavaTypeConverter.promoteBinaryNumeric(x, y); if (null == promX || !JavaEntities.resolveToRawRValue(promX).isInteger()) { _runtime.error("integral operator expected", n.getNode(0)); result = x; } else if (null == promY || !JavaEntities.resolveToRawRValue(promY).isInteger()) { _runtime.error("integral operator expected", n.getNode(2)); result = x; } else if (!x.hasConstant() || !y.hasConstant()){ result = promX; } else { final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promX); final Number valNumX = (Number) promX.getConstant().getValue(); final Number valNumY = (Number) promY.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valX = valNumX.intValue(), valY = valNumY.intValue(); if ("&".equals(operator)) result = typNum.annotate().constant(new Integer(valX & valY)); else if ("|".equals(operator)) result = typNum.annotate().constant(new Integer(valX | valY)); else result = typNum.annotate().constant(new Integer(valX ^ valY)); break; } case LONG: { final long valX = valNumX.longValue(), valY = valNumY.longValue(); if ("&".equals(operator)) result = typNum.annotate().constant(new Long(valX & valY)); else if ("|".equals(operator)) result = typNum.annotate().constant(new Long(valX | valY)); else result = typNum.annotate().constant(new Long(valX ^ valY)); break; } default: throw new Error(); } } } return setType(n, result); } private Type processTypeName(final GNode n) { if (n.hasName("PrimitiveType")) return (Type) dispatch(n); assert n.hasName("QualifiedIdentifier"); final String typeName = (String) dispatch(n); final ClassOrInterfaceT result = JavaEntities.qualifiedNameToType(_table, classpath(), _table.current().getQualifiedName(), typeName); return result; } protected final Type resolveIfAlias(final Type type) { return resolveIfAlias(type, null); } protected final Type resolveIfAlias(final Type type, final Node n) { final Type resolved = JavaEntities.resolveIfAlias(_table, classpath(), _table.current().getQualifiedName(), type); if (null == resolved || resolved.isAlias() && null == resolved.toAlias().getType()) { if (null != n) _runtime.error("unknown class or interface " + type.toAlias().getName(), n); return ErrorT.TYPE; } return resolved; } /** * Visit an AdditiveExpression = Expression ("+" / "-") Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#15746">§15.18</a>). */ public final Type visitAdditiveExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(2)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final Type result; if ("+".equals(n.getString(1))) { final Type tString = JavaEntities.tString(_table); if (JavaTypeConverter.isIdentical(tString, x) || JavaTypeConverter.isIdentical(tString, y)) { if (x.hasConstant() && y.hasConstant()) { final Type convX = JavaTypeConverter.convertString(_table, x); final Type convY = JavaTypeConverter.convertString(_table, y); final String valX = (String) convX.getConstant().getValue(); final String valY = (String) convY.getConstant().getValue(); result = tString.annotate().constant(valX + valY); } else { result = tString; } } else { final Type promX = JavaTypeConverter.promoteBinaryNumeric(y, x); final Type promY = JavaTypeConverter.promoteBinaryNumeric(x, y); if (null == promX || null == promY) { _runtime.error("String or numeric operands expected", n); result = JavaEntities.nameToBaseType("double"); } else if (!promX.hasConstant() || !promY.hasConstant()) { result = JavaEntities.resolveToRawRValue(promX); } else { final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promX); final Number valNumX = (Number) promX.getConstant().getValue(); final Number valNumY = (Number) promY.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valX = valNumX.intValue(), valY = valNumY.intValue(); result = typNum.annotate().constant(new Integer(valX + valY)); break; } case LONG: { final long valX = valNumX.longValue(), valY = valNumY.longValue(); result = typNum.annotate().constant(new Long(valX + valY)); break; } case FLOAT: { final float valX = valNumX.floatValue(), valY = valNumY .floatValue(); result = typNum.annotate().constant(new Float(valX + valY)); break; } case DOUBLE: { final double valX = valNumX.doubleValue(), valY = valNumY .doubleValue(); result = typNum.annotate().constant(new Double(valX + valY)); break; } default: throw new Error(); } } } } else { assert "-".equals(n.getString(1)); final Type promX = JavaTypeConverter.promoteBinaryNumeric(y, x); final Type promY = JavaTypeConverter.promoteBinaryNumeric(x, y); if (null == promX || null == promY) { _runtime.error("numeric operands expected", n); result = JavaEntities.nameToBaseType("double"); } else if (!promX.hasConstant() || !promY.hasConstant()) { result = JavaEntities.resolveToRawRValue(promX); } else { final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promX); final Number valNumX = (Number) promX.getConstant().getValue(); final Number valNumY = (Number) promY.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valX = valNumX.intValue(), valY = valNumY.intValue(); result = typNum.annotate().constant(new Integer(valX - valY)); break; } case LONG: { final long valX = valNumX.longValue(), valY = valNumY.longValue(); result = typNum.annotate().constant(new Long(valX - valY)); break; } case FLOAT: { final float valX = valNumX.floatValue(), valY = valNumY.floatValue(); result = typNum.annotate().constant(new Float(valX - valY)); break; } case DOUBLE: { final double valX = valNumX.doubleValue(), valY = valNumY .doubleValue(); result = typNum.annotate().constant(new Double(valX - valY)); break; } default: throw new Error(); } } } return setType(n, result); } /** * Visit Arguments = Expression* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#41147">§15.9</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#20448">§15.12</a>). */ public final List<Type> visitArguments(final GNode n) { final List<Type> result = new ArrayList<Type>(n.size()); for (int i = 0; i < n.size(); i++) result.add(dispatchRValue(n.getGeneric(i))); return result; } /** * Visit an ArrayInitializer = VariableInitializer* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/arrays.doc.html#11358">§10.6</a>). * Note: VariableInitializer > ArrayInitializer, Expression. */ public final Type visitArrayInitializer(final GNode n) { if (assrt(n, _context._initializing.isArray(), "array initializer type mismatch")) { final JavaContext savedContext = _context.save(); _context._initializing = JavaEntities.arrayElementType(_context._initializing.toArray()); for (int i = 0; i < n.size(); i++) { final Type src = dispatchRValue(n.getGeneric(i)); assrt(n.getGeneric(i), JavaTypeConverter.isAssignable(_table, classpath(), _context._initializing, src), "array initializer type mismatch"); } _context.restore(savedContext); } return setType(n, _context._initializing); } public final void visitBasicCastExpression(final GNode n) { assert false : "must run JavaAstSimplifier first"; } /** Visit a BasicForControl = VariableModifiers Type Declarators [Expression] [ExpressionList] * / null null [ExpressionList] [Expression] [ExpressionList] * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#24588">§14.13</a>). */ public final void visitBasicForControl(final GNode n) { if (null == n.get(1)) { assert null == n.get(0); } else { @SuppressWarnings("unchecked") final List<Attribute> modifiers = (List<Attribute>) dispatch(n.getNode(0)); final Type type = (Type) dispatch(n.getGeneric(1)); _externalAnalyzer.processDeclarators(modifiers, type, n.getGeneric(2)); } dispatch(n.getGeneric(2)); if (null != n.get(3)) { final Type condition = (Type) dispatch(n.getGeneric(3)); assrt(n.getGeneric(3), JavaEntities.resolveToRawRValue(condition).isBoolean(), "condition must be boolean"); } dispatch(n.getGeneric(4)); } /** * Visit a BitwiseAndExpression = Expression Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5228">§15.22</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitBitwiseAndExpression(final GNode n) { return processBitwiseBinaryExpression(n, "&"); } /** * Visit a BitwiseNegationExpression = Expression * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#4990">§15.15</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitBitwiseNegationExpression(final GNode n) { final Type type = (Type) dispatch(n.getGeneric(0)); if (type.isError()) return setType(n, type); final Type promoted = JavaTypeConverter.promoteUnaryNumeric(getRValue(type, n.getGeneric(0))); if (!assrt(n, null != promoted, "operand must be numeric")) return setType(n, ErrorT.TYPE); if (promoted.hasConstant()) { final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promoted); final Number valNum = (Number) promoted.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valInt = valNum.intValue(); return typNum.annotate().constant(new Integer(~valInt)); } case LONG: { final long valLong = valNum.longValue(); return typNum.annotate().constant(new Long(~valLong)); } default: { assrt(n, false, "operand must be an integral type"); return setType(n, ErrorT.TYPE); } } } else { return setType(n, promoted); } } /** * Visit a BitwiseOrExpression = Expression Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5228">§15.22</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitBitwiseOrExpression(final GNode n) { return processBitwiseBinaryExpression(n, "|"); } /** * Visit a BitwiseXorExpression = Expression Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5228">§15.22</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitBitwiseXorExpression(final GNode n) { return processBitwiseBinaryExpression(n, "^"); } /** * Visit a Block = DeclarationOrStatement* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#246838">§14.2</a>). */ public final Type visitBlock(final GNode n) { final JavaContext savedContext = _context.save(); _context._hasScope = true; if (savedContext._hasScope) { _table.enter(_table.freshName("block")); _table.mark(n); } for (int i = 0; i < n.size(); i++) dispatch(n.getNode(i)); if (savedContext._hasScope) _table.exit(); _context.restore(savedContext); return JavaEntities.nameToBaseType("void"); } /** Visit a BlockDeclaration = ["static"] Block (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#246032">§8.6</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#39245">§8.7</a>). */ public final void visitBlockDeclaration(final GNode n) { final JavaContext savedContext = _context.save(); assert JavaEntities.isScopeForMember(_table.current().getQualifiedName()); final ClassOrInterfaceT enclosingType = JavaEntities.currentType(_table); assert JavaEntities.isWrappedClassT(enclosingType); _context._static = null != n.get(0); if (JavaEntities.isTypeInner(enclosingType)) assrt(n, !_context._static, "inner classes may not declare static initializers"); _context._handledExceptions = new ArrayList<Type>(); final ClassT clazz = (ClassT) JavaEntities.resolveToRawRValue(enclosingType); final boolean isAnonymous = JavaEntities.isTypeAnonymous(clazz); if (isAnonymous) { _context._handledExceptions.add(JavaEntities.tThrowable(_table)); } else if (_context._static) { // no checked exceptions allowed in static initializer } else { boolean firstConstructor = true; for (final Type m : clazz.getMethods()) { final MethodT method = m.toMethod(); if (JavaEntities.isConstructor(clazz, method)) { final List<Type> nuw = method.getExceptions(); if (firstConstructor) { _context._handledExceptions.addAll(nuw); firstConstructor = false; } else { final Set<String> old = new HashSet<String>(); for (final Type t : _context._handledExceptions) old.add(((ClassT) JavaEntities.resolveToRawRValue(t)).getQName()); _context._handledExceptions.clear(); for (final Type t : nuw) { final String q = ((ClassT) JavaEntities.resolveToRawRValue(t)).getQName(); if (old.contains(q)) _context._handledExceptions.add(t); } } } } } dispatch(n.getGeneric(1)); _context.restore(savedContext); } /** * Visit a BooleanLiteral (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#49652">§3.10.3</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#224125">§15.8.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitBooleanLiteral(final GNode n) { final Type tBool = JavaEntities.nameToBaseType("boolean"); final boolean isTrue = "true".equals(n.getString(0)); return setType(n, tBool.annotate().constant(isTrue)); } /** * Visit a BreakStatement = [Identifier] (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#6842">§14.14</a>). */ public final void visitBreakStatement(final GNode n) { if (null == n.get(0)) { assrt(n, _context._loop || _context._switch, "break without label can only be used in loop or switch"); } else { final String simpleName = n.getString(0); final String symbol = SymbolTable.toLabelName(simpleName); final LabelT label = (LabelT) _table.current().lookup(symbol); assrt(n, null != label, "the label " + simpleName + " is missing"); } } /** * Visit a CallExpression = [Expression] null MethodName Arguments * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#20448">§15.12</a>). */ public final Type visitCallExpression(final GNode n) { // TD 21 (15.12) CallExpression = [Expression] MethodName Arguments final Type typeToSearch; final boolean inStaticContext; String identifier = n.getString(2); if ("super".equals(identifier) && n.get(0) == null) { // TD 08 (8.8.5.1) allow explicit constructor invocations via this() and super() typeToSearch = JavaEntities.currentType(_table).toClass().getParent(); identifier = resolveIfAlias(typeToSearch).toClass().getName(); inStaticContext = false; } else if (n.get(0) == null) { final SymbolTable.Scope oldScope = _table.current(); outer: while (true) { final ClassOrInterfaceT t = JavaEntities.currentType(_table); JavaEntities.enterScopeByQualifiedName(_table, JavaEntities.typeToScopeName(t)); for (final MethodT m : JavaEntities.methodsOwnAndInherited(_table, classpath(), t)) if (m.getName().equals(identifier)) { typeToSearch = t; break outer; } if (JavaEntities.isTypeTopLevel(t)) { typeToSearch = t; break outer; } _table.exit(); } _table.setScope(oldScope); inStaticContext = _context._static; } else { final Type t1 = (Type) dispatch(n.getGeneric(0)); if (!assrt(n.getGeneric(0), !t1.isPackage(), "unknown idenfifier")) return setType(n, ErrorT.TYPE); inStaticContext = JavaEntities.isNotAValueT(t1); final Type t2 = inStaticContext ? JavaEntities.resolveToValue(t1.toAnnotated()) : t1; final Type t3 = getRValue(t2, n.getGeneric(0)); final Type t4 = JavaEntities.isConstantT(t3) ? ((AnnotatedT)t3).getType() : t3; typeToSearch = resolveIfAlias(t4); } final List<Type> actuals = JavaEntities.typeList((List)dispatch(n.getNode(3))); for (final Type actual : actuals) if (actual.isError()) return setType(n, ErrorT.TYPE); final MethodT method = JavaEntities.typeDotMethod( _table, classpath(), typeToSearch, true, identifier, actuals); if (!assrt(n, null != method && method.isMethod(), "no such method")) return setType(n, ErrorT.TYPE); assrt(n, hasModifier(method, "static") || !inStaticContext, "static call to non-static method"); JavaEntities.resolveIfAliasMethod(_table, classpath(), method); for (final Type tThrown : method.getExceptions()) if (JavaEntities.isCheckedException(_table, classpath(), tThrown)) assrt(n, isHandled(tThrown), "uncaught exception"); setType(n.getGeneric(3), method); return setType(n, method.getResult()); } /** Visit a CaseClause = Expression DeclarationOrStatement* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#35518">§14.10</a>). */ public final Type visitCaseClause(final GNode n) { final Type key = (Type)dispatch(n.getGeneric(0)); for (int i = 1; i < n.size(); i++) dispatch(n.getGeneric(i)); return key; } /** * Visit a CastExpression = Type Expression (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#238146">§15.16</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitCastExpression(final GNode n) { final Type tgt = (Type) dispatch(n.getGeneric(0)); if (tgt.isError()) return setType(n, ErrorT.TYPE); final Type src = dispatchRValue(n.getGeneric(1)); final Type result = JavaTypeConverter.convertCasting(_table, classpath(), tgt, src); if (!assrt(n, null != result, "illegal cast")) return setType(n, ErrorT.TYPE); return setType(n, result); } /** Visit a CatchClause = FormalParameter Block (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#79311">§14.19</a>). */ public final Type visitCatchClause(final GNode n) { final JavaContext savedContext = _context.save(); _context._hasScope = false; _table.enter(_table.freshName("catchClause")); _table.mark(n); final GNode pNode = n.getGeneric(0); final Type tParameter = dispatchRValue(pNode); final Type tThrowable = JavaEntities.tThrowable(_table); assrt(pNode, JavaEntities.isSuperClass(_table, classpath(), tThrowable, tParameter), "illegal type for exception parameter"); dispatch(n.getGeneric(1)); _table.exit(); _context.restore(savedContext); return setType(n, tParameter); } /** * Visit a CharacterLiteral (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#100960">§3.10.4</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#224125">§15.8.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitCharacterLiteral(final GNode n) { final String s = n.getString(0); final int len = s.length(); assert 2 < len && '\'' == s.charAt(0) && '\'' == s.charAt(len - 1); Character value = null; if (3 == len) { value = new Character(s.charAt(1)); } else { try { value = new Character(escapeSequenceChar(s, 1)); if (!assrt(n, len == 1 + escapeSequenceEnd(s, 1), "illegal escape sequence")) return setType(n, ErrorT.TYPE); } catch (final IllegalArgumentException e) { _runtime.error("illegal escape sequence", n); return setType(n, ErrorT.TYPE); } } if (!assrt(n, 3 < len || '\r' != value.charValue() && '\n' != value.charValue(), "single character must not be line terminator")) return setType(n, ErrorT.TYPE); final Type tChar = JavaEntities.nameToBaseType("char"); return setType(n, tChar.annotate().constant(value)); } /** * Visit a ClassBody = Declaration* (gosling_et_al_2000 * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#18988">§8.1.5</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/interfaces.doc.html#236431">§9.1.3</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#41147">§15.9</a>). */ public final void visitClassBody(final GNode n) { //TD 07 (8.1.5, 9.1.3, 15.9) ClassBody = Declaration* for (int i = 0; i < n.size(); i++) dispatch(n.getNode(i)); } /** * Visit a ClassDeclaration = Modifiers Identifier null [Extension] * [Implementation] ClassBody (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#15372">§8.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#247766">§14.3</a>). */ public final void visitClassDeclaration(final GNode n) { final JavaContext savedContext = _context.save(); _context._handledExceptions = new ArrayList<Type>(); _context._hasScope = true; _context._switch = false; _context._loop = false; final String simpleName = n.getString(1); assrtLegalIdentifier(n, simpleName); ClassT base = (ClassT) _table.current().lookupLocally(SymbolTable.toTagName(simpleName)); if (null == base) base = _externalAnalyzer.visitClassDeclaration(n); else assrt(n, !JavaEntities.isScopeLocal(_table.current().getQualifiedName()), "conflicting classes"); { final Type parent = base.getParent(); if (parent.isAlias()) { final AliasT alias = parent.toAlias(); resolveIfAlias(alias, n.getGeneric(3)); if (null == alias.getType()) alias.setType(JavaEntities.tObject(_table)); } assrt(n.getGeneric(3), JavaEntities.isWrappedClassT(parent), "class expected"); assrt(n.getGeneric(3), !hasModifier(parent, "final"), "can't subclass final class"); assrt(n.getGeneric(3), JavaEntities.isAccessible(_table, classpath(), parent), "inner class not visible"); } final Set<String> seenInterfaces = new HashSet<String>(); for (int i=0; i<base.getInterfaces().size(); i++) { // gosling_et_al_2000 08.1.4 final Type t = resolveIfAlias(base.getInterfaces().get(i), n.getGeneric(4).getNode(i)); if (!t.isError() && assrt(n.getGeneric(4), t.isInterface(), "interface expected")) { final String qname = t.toInterface().getQName(); assrt(n.getGeneric(4), !seenInterfaces.contains(qname), "duplicate superinterfaces"); seenInterfaces.add(qname); assrt(n.getGeneric(4), JavaEntities.isAccessible(_table, classpath(), t), "superinterface not accessible"); } } final boolean isAbstract = hasModifier(base, "abstract"); if (JavaEntities.hasAbstractMethods(_table, classpath(), base)) assrt(n, isAbstract, "must be abstract"); assrt(n, !JavaEntities.hasCircularDependency(_table, classpath(), base), "circular class"); _table.enter(simpleName); _table.mark(n.getNode(5)); dispatch(n.getNode(5)); _table.exit(); if (isAbstract) assrt(n, JavaEntities.couldCreateConcreteSubclass(_table, classpath(), base), "conflicting abstract methods"); _context.restore(savedContext); } /** * Visit a ClassLiteralExpression = Type (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#251530">§15.8.2</a>). */ public final Type visitClassLiteralExpression(final GNode n) { final Type t = (Type)dispatch(n.getNode(0)); assert t.isError() || JavaEntities.isWrappedClassT(t) || JavaEntities.isWrappedInterfaceT(t) || t.isArray() || JavaEntities.isPrimitiveT(t); return setType(n, JavaEntities.tClass(_table)); } /** * Visit a CompilationUnit = [PackageDeclaration] ImportDeclaration* * Declaration* (gosling_et_al_2000 * <a href="http://java.sun.com/docs/books/jls/second_edition/html/packages.doc.html#40031">§7.3</a>). */ public void visitCompilationUnit(final GNode n) { _externalAnalyzer.dispatch(n); if (null == n.get(0)) visitPackageDeclaration(null); else dispatch(n.getNode(0)); _table.enter(JavaEntities.fileNameToScopeName(n.getLocation().file)); _table.mark(n); for (int i = 1; i < n.size(); i++) dispatch(n.getNode(i)); _table.setScope(_table.root()); } /** * Visit a ConcreteDimensions = Expression+ (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#46168">§15.10</a>). */ public final List<Type> visitConcreteDimensions(final GNode n) { final List<Type> result = new ArrayList<Type>(); for (int i = 0; i < n.size(); i++) { final Type r = dispatchRValue(n.getGeneric(i)); result.add(r); final Type pr = null == r ? null : JavaTypeConverter.promoteUnaryNumeric(r); final Type rpr = null == pr ? null : JavaEntities.resolveToRawRValue(pr); assrt(n, null != rpr && rpr instanceof IntegerT && NumberT.Kind.INT == ((IntegerT)rpr).getKind(), "dimension must be integer"); } return result; } /** * Visit a ConditionalExpression = Expression Expression Expression * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#290293">§15.25</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitConditionalExpression(final GNode n) { final Type typCond = dispatchRValue(n.getGeneric(0)); final Type rawCond = JavaEntities.resolveToRawRValue(typCond); final BigInteger valCond; if (rawCond.isBoolean()) { if (typCond.hasConstant()) valCond = typCond.getConstant().bigIntValue(); else valCond = null; } else { assrt(n, rawCond.isError(), "condition must be boolean"); valCond = null; } final Type typX = dispatchRValue(n.getGeneric(1)); final Type rawX = JavaEntities.resolveToRawRValue(typX); final Object valX = typX.hasConstant() ? typX.getConstant().getValue() : null; final Type typY = dispatchRValue(n.getGeneric(2)); final Type rawY = JavaEntities.resolveToRawRValue(typY); final Object valY = typY.hasConstant() ? typY.getConstant().getValue() : null; final Type result; if (JavaTypeConverter.isIdentical(typX, typY)) { if (null == valCond) result = rawX; else result = BigInteger.ONE == valCond ? typX : typY; } else if (rawX.isNumber() && rawY.isNumber()) { final NumberT tByte = (NumberT) JavaEntities.nameToBaseType("byte"); final NumberT tShort = (NumberT) JavaEntities.nameToBaseType("short"); final NumberT tChar = (NumberT) JavaEntities.nameToBaseType("char"); final NumberT tInt = (NumberT) JavaEntities.nameToBaseType("int"); final NumberT rawResult; if (tByte == rawX && tShort == rawY || tShort == rawX && tByte == rawY) { rawResult = tShort; } else if (tByte == rawX && tInt == rawY && null != valY && (byte) ((Integer) valY).intValue() == ((Integer) valY).intValue()) { rawResult = tByte; } else if (tShort == rawX && tInt == rawY && null != valY && (short) ((Integer) valY).intValue() == ((Integer) valY).intValue()) { rawResult = tShort; } else if (tChar == rawX && tInt == rawY && null != valY && (char) ((Integer) valY).intValue() == ((Integer) valY).intValue()) { rawResult = tChar; } else if (tByte == rawY && tInt == rawX && null != valX && (byte) ((Integer) valX).intValue() == ((Integer) valX).intValue()) { rawResult = tByte; } else if (tShort == rawY && tInt == rawX && null != valX && (short) ((Integer) valX).intValue() == ((Integer) valX).intValue()) { rawResult = tShort; } else if (tChar == rawY && tInt == rawX && null != valX && (char) ((Integer) valX).intValue() == ((Integer) valX).intValue()) { rawResult = tChar; } else { final NumberT x = (NumberT) JavaTypeConverter.promoteBinaryNumeric(rawY, rawX); final NumberT y = (NumberT) JavaTypeConverter.promoteBinaryNumeric(rawX, rawY); if (!assrt(n, null != x && null != y, "type mismatch")) return setType(n, ErrorT.TYPE); rawResult = x; } if (BigInteger.ONE == valCond && null != valX) { final Object valResult; if (valX instanceof Number) { final Number numX = (Number) valX; switch (rawResult.getKind()) { case BYTE: valResult = new Byte(numX.byteValue()); break; case SHORT: valResult = new Short(numX.shortValue()); break; case CHAR: valResult = new Character((char) numX.intValue()); break; case INT: valResult = new Integer(numX.intValue()); break; case LONG: valResult = new Long(numX.longValue()); break; case FLOAT: valResult = new Float(numX.floatValue()); break; case DOUBLE: valResult = new Double(numX.doubleValue()); break; default: throw new Error(); } } else { final char charX = ((Character) valX).charValue(); switch (rawResult.getKind()) { case BYTE: valResult = new Byte((byte)charX); break; case SHORT: valResult = new Short((short)charX); break; case CHAR: valResult = new Character(charX); break; case INT: valResult = new Integer(charX); break; case LONG: valResult = new Long(charX); break; case FLOAT: valResult = new Float(charX); break; case DOUBLE: valResult = new Double(charX); break; default: throw new Error(); } } result = rawResult.annotate().constant(valResult); } else if (BigInteger.ONE == valCond && null != valY) { final Number numY = (Number) valY; final Object valResult; switch (rawResult.getKind()) { case BYTE: valResult = new Byte(numY.byteValue()); break; case SHORT: valResult = new Short(numY.shortValue()); break; case CHAR: valResult = new Character((char) numY.intValue()); break; case INT: valResult = new Integer(numY.intValue()); break; case LONG: valResult = new Long(numY.longValue()); break; case FLOAT: valResult = new Float(numY.floatValue()); break; case DOUBLE: valResult = new Double(numY.doubleValue()); break; default: throw new Error(); } result = rawResult.annotate().constant(valResult); } else { result = rawResult; } } else if (!JavaEntities.isPrimitiveT(rawX) && !JavaEntities.isPrimitiveT(rawY)) { if (JavaEntities.isNullT(rawX)) { result = BigInteger.ZERO == valCond ? typY : rawY; } else if (JavaEntities.isNullT(rawY)) { result = BigInteger.ONE == valCond ? typY : rawY; } else { final Type convX = JavaTypeConverter.convertAssigning(_table, classpath(), rawY, rawX); final Type convY = JavaTypeConverter.convertAssigning(_table, classpath(), rawX, rawY); if (!assrt(n, null != convX || null != convY, "mismatched types")) return setType(n, ErrorT.TYPE); result = null != convX ? convX : convY; } } else { throw new Error(); } return setType(n, result); } /** * Visit a ConditionalStatement = Expression Statement [Statement] * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#5991">§14.9</a>). */ public final void visitConditionalStatement(final GNode n) { final Type condition = dispatchRValue(n.getGeneric(0)); if (!JavaEntities.resolveToRawRValue(condition).isBoolean()) _runtime.error("condition must be boolean", n.getGeneric(0)); dispatch(n.getNode(1)); dispatch(n.getNode(2)); } public final void visitConstructorDeclaration(final GNode n) { assert false : "must run JavaAstSimplifier first"; } /** * Visit a ContinueStatement = [Identifier] (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#6122">§14.15</a>). */ public final void visitContinueStatement(final GNode n) { if (null == n.get(0)) { assrt(n, _context._loop, "continue cannot be used outside of a loop"); } else { final String simpleName = n.getString(0); final String symbol = SymbolTable.toLabelName(simpleName); final LabelT label = (LabelT) _table.current().lookup(symbol); if (null == label) _runtime.error("the label " + simpleName + " is missing", n); else assrt(n, label.hasAttribute(Constants.ATT_LOOP), "%s is not a loop label", label.getName()); } } /** * Visit Declarator = Identifier [Dimensions] [VariableInitializer]. * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#5920">§14.4</a>). * Note: used by FieldDeclaration and ForInit, * who are responsible for calling the ExternalAnalyzer first, so * that the TYPE attribute is already set. * Note: VariableInitializer > ArrayInitializer, * Expression. */ public final Type visitDeclarator(final GNode n) { final JavaContext savedContext = _context.save(); final Type result = resolveIfAlias((Type) n.getProperty(Constants.TYPE), n); _context._static = hasModifier(result, "static"); { final Type wrappedRValue = JavaEntities.dereference(result); final Type rValue = JavaEntities.isConstantT(wrappedRValue) ? ((AnnotatedT)wrappedRValue).getType() : wrappedRValue; if (rValue.isAlias()) { final AliasT alias = (AliasT)rValue; if (null == alias.getType()) { _runtime.error("unknown type " + alias.getName(), n); alias.setType(JavaEntities.tObject(_table)); return result; } } } final String id = n.getString(0); assrtLegalIdentifier(n, id); if (JavaEntities.isParameterT(result)) { _runtime.error("duplicate parameter declaration " + id, n); } else { assert JavaEntities.isFieldT(result) || JavaEntities.isLocalT(result); if (JavaEntities.isScopeLocal(_table.current().getQualifiedName())) if (!SymbolTable.isInNameSpace(_table.current().getName(), "method")) { final Type shadowed = (Type) _table.current().getParent().lookup(id); assrt(n, null == shadowed || !JavaEntities.isParameterT(shadowed), "duplicate variable declaration " + id); } } if (null != n.get(2)) { _context._initializing = getRValue(result, n); final Type src = dispatchRValue((GNode) n.getNode(2)); if (!src.isError()) { assert JavaEntities.isGeneralRValueT(src); assrt(n, JavaTypeConverter.isAssignable(_table, classpath(), _context._initializing, src), "initializer type mismatch"); if (src.hasConstant() && !JavaEntities.isNullT(src) && hasModifier(result, "final")) { final WrappedT t = JavaEntities.resolveToRawLValue(result); t.setType(src); } } } if (_context._static && JavaEntities.isFieldT(result)) if (JavaEntities.isTypeInner(JavaEntities.currentType(_table))) assrt(n, hasModifier(result, "final"), "static variables of inner classes must be compile-time constants"); _context.restore(savedContext); return result; } /** * Visit Declarators = Declarator+ * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#5920">§14.4</a>). * Note: used by FieldDeclaration and ForInit, * who are responsible for calling the ExternalAnalyzer first, so * that the TYPE attribute is already set. */ public final List<Type> visitDeclarators(final GNode n) { final List<Type> result = new ArrayList<Type>(); for (final Object nDecl : n) result.add((Type) dispatch((GNode)nDecl)); return result; } /** * Visit a DefaultClause = DeclarationOrStatement* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#35518">§14.10</a>). */ public final void visitDefaultClause(final GNode n) { for (int i = 0; i < n.size(); i++) dispatch(n.getGeneric(i)); } /** * Visit a DoWhileStatement = Statement Expression (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#6045">§14.12</a>). */ public final void visitDoWhileStatement(final GNode n) { final JavaContext savedContext = _context.save(); _context._loop = true; dispatch(n.getNode(0)); final Type condition = dispatchRValue(n.getGeneric(1)); if (!JavaEntities.resolveToRawRValue(condition).isBoolean()) _runtime.error("condition must be boolean", n.getGeneric(0)); _context.restore(savedContext); } /** * Visit a EmptyDeclaration = (no children) (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#5970">§14.6</a>). */ public final void visitEmptyDeclaration(final GNode n) { assert 0 == n.size(); } /** * Visit a EmptyStatement = (no children) (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#5970">§14.6</a>). */ public final void visitEmptyStatement(final GNode n) { assert 0 == n.size(); } /** * Visit a EqualityExpression = Expression ("==" / "!=") Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5192">§15.21</a>). */ public final Type visitEqualityExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(2)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final String o = n.getString(1); final Type result; Type tBool = JavaEntities.nameToBaseType("boolean"); final boolean xIsBool = JavaTypeConverter.isIdentical(x, tBool), yIsBool = JavaTypeConverter.isIdentical(y, tBool); assert "==".equals(o) || "!=".equals(o); final Type promX = JavaTypeConverter.promoteBinaryNumeric(y, x); final Type promY = JavaTypeConverter.promoteBinaryNumeric(x, y); if (null == promX || null == promY) { if (xIsBool) { if (yIsBool) { if (x.hasConstant() && y.hasConstant()) { final boolean valX = x.getConstant().isTrue(); final boolean valY = y.getConstant().isTrue(); if ("==".equals(o)) result = tBool.annotate().constant(valX == valY); else result = tBool.annotate().constant(valX != valY); } else { result = tBool; } } else { _runtime.error("boolean expected", n.getNode(n.size() - 1)); result = tBool; } } else { if (JavaEntities.isNullT(x) || JavaEntities.isNullT(y)) { assrt(n, (JavaEntities.isReferenceT(x) || JavaEntities.isNullT(x)) && (JavaEntities.isReferenceT(y) || JavaEntities.isNullT(y)), "incompatible types"); result = tBool; } else if (isStringConstant(x) && isStringConstant(y)) { final String sx = (String) x.getConstant().getValue(), sy = (String) y.getConstant().getValue(); result = tBool.annotate().constant("==".equals(o) ? sx.equals(sy) : !sx.equals(sy)); } else { final boolean yx = JavaTypeConverter.isCastable(_table, classpath(), y, x); final boolean xy = JavaTypeConverter.isCastable(_table, classpath(), x, y); assrt(n, yx || xy, "incompatible types"); result = tBool; } } } else if (!promX.hasConstant() || !promY.hasConstant()) { result = tBool; } else { final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promX); final Number valNumX = (Number) promX.getConstant().getValue(); final Number valNumY = (Number) promY.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valX = valNumX.intValue(), valY = valNumY.intValue(); if ("==".equals(o)) result = tBool.annotate().constant(valX == valY); else result = tBool.annotate().constant(valX != valY); break; } case LONG: { final long valX = valNumX.longValue(), valY = valNumY.longValue(); if ("==".equals(o)) result = tBool.annotate().constant(valX == valY); else result = tBool.annotate().constant(valX != valY); break; } case FLOAT: { final float valX = valNumX.floatValue(), valY = valNumY.floatValue(); if ("==".equals(o)) result = tBool.annotate().constant(valX == valY); else result = tBool.annotate().constant(valX != valY); break; } case DOUBLE: { final double valX = valNumX.doubleValue(), valY = valNumY.doubleValue(); if ("==".equals(o)) result = tBool.annotate().constant(valX == valY); else result = tBool.annotate().constant(valX != valY); break; } default: throw new Error(); } } return setType(n, result); } /** * Visit a Expression = Expression ("=" / "+=" / "-=" / "*=" / "/=" / "&=" / "|=" / "^=" / "%=" / "<<=" / ">>=" / ">>>=") Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5281">§15.26</a>). */ public final Type visitExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(2)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final String o = n.getString(1); assert "=".equals(o) || "+=".equals(o) || "-=".equals(o) || "*=".equals(o) || "/=".equals(o) || "%=".equals(o) || "&=".equals(o) || "|=".equals(o) || "^=".equals(o) || "<<=".equals(o) || ">>=".equals(o) || ">>>=".equals(o); if (!JavaEntities.isGeneralLValueT(xLvalue)) { _runtime.error("left operand of assignment not l-value", n); } else if (hasModifier(xLvalue, "final")) { _runtime.error("left operand of assignment is final", n); // TD 30 (15.26) should allow assignments to "blank final" variables, but that // would require flow-sensitive analysis, which is quite complicated // for final parameters, assignment should definitely be forbidden } else if ("=".equals(o)) { // gosling_et_al_2000 15.26.1 Simple Assignment Operator = assrt(n.getGeneric(2), JavaTypeConverter.isAssignable(_table, classpath(), x, y), "illegal assignment"); } else { // gosling_et_al_2000 15.26.2 Compound Assignment Operators final char op = o.charAt(0); final boolean string; if ('+' == op) { final Type tString = JavaEntities.tString(_table); string = JavaTypeConverter.isIdentical(x, tString); } else { string = false; } if (!string) { final Type rawX = JavaEntities.resolveToRawRValue(x); final Type rawY = JavaEntities.resolveToRawRValue(y); switch (op) { case '+': case '-': case '*': case '/': case '%': assrt(n, rawX.isNumber() && rawY.isNumber(), "illegal assignment"); break; case '&': case '|': case '^': assrt(n, rawX.isBoolean() && rawY.isBoolean() || rawX.isInteger() && rawY.isInteger(), "illegal assignment"); break; case '<': case '>': assrt(n, rawX.isInteger() && rawY.isInteger(), "illegal assignment"); break; default: assert false; } } } return setType(n, x); } /** * Visit an ExpressionList = Expression* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#24588">§14.13</a>). */ public final List<Type> visitExpressionList(final GNode n) { final List<Type> result = new ArrayList<Type>(); for (int i = 0; i < n.size(); i++) result.add(dispatchRValue(n.getGeneric(i))); return result; } /** * Visit an ExpressionStatement = Expression (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#5984">§14.8</a>). */ public final void visitExpressionStatement(final GNode n) { dispatch(n.getNode(0)); } /** * Visit a FieldDeclaration = Modifiers Type Declarators (gosling_et_al_2000 * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#40898">§8.3</a>). */ public final List<Type> visitFieldDeclaration(final GNode n) { //TD 42 don't treat static initializer like local variable // if (JavaEntities.isScopeLocal(_table.current().getQualifiedName())) if (null == n.getGeneric(2).getGeneric(0).getProperty(Constants.TYPE)) _externalAnalyzer.dispatch(n); return JavaEntities.typeList((List)dispatch(n.getNode(2))); } /** * Visit a FloatingPointLiteral (gosling_et_al <a * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#230798">§3.10.2</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#224125">§15.8.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitFloatingPointLiteral(final GNode n) { final String s = n.getString(0); final boolean isFloat = 'f' == Character.toLowerCase(s.charAt(s.length() - 1)); final Number value = isFloat ? (Number)new Float(s) : new Double(s); if (!assrt(n, isFloat ? !((Float)value).isInfinite() : !((Double)value).isInfinite(), "literal out of range") || !assrt(n, (0.0 == value.doubleValue()) == JavaEntities.zeroLiteral(s), "literal out of range")) return setType(n, ErrorT.TYPE); final Type type = JavaEntities.nameToBaseType(isFloat ? "float" : "double"); return setType(n, type.annotate().constant(value)); } /** * Visit a FormalParameter = [Modifier] Type null Identifier [Dimensions] (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#38698">§8.4.1</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#29488">8.8.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#79311">§14.19</a>). */ public final Type visitFormalParameter(final GNode n) { final boolean isInCatch = SymbolTable.isInNameSpace(_table.current().getName(), "catchClause"); final String id = n.getString(3); final Type result; final Type t = (Type) _table.lookup(id); if (isInCatch) { assrt(n, null == t, "duplicate parameter " + id); result = null == t ? (Type) _externalAnalyzer.visitFormalParameter(n) : t; } else { result = t; } resolveIfAlias(JavaEntities.dereference(result), n.getNode(1)); return setType(n, result); } /** Visit FormalParameters = FormalParameter* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#38698">§8.4.1</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#29488">8.8.1</a>. */ public final List<Type> visitFormalParameters(final GNode n) { final List<Type> result = new ArrayList<Type>(n.size()); for (int i = 0; i < n.size(); i++) result.add((Type)dispatch(n.getNode(i))); return result; } /** * Visit a ForStatement = ForControl Statement * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#24588">§14.13</a>). * Note that ForControl > BasicForControl. */ public final void visitForStatement(final GNode n) { final JavaContext savedContext = _context.save(); _context._loop = true; _context._hasScope = false; _table.enter(_table.freshName("forStatement")); _table.mark(n); dispatch(n.getGeneric(0)); dispatch(n.getGeneric(1)); _table.exit(); _context.restore(savedContext); } /** * Visit a ImportDeclaration = QualifiedIdentifier ["*"] (gosling_et_al_2000 * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/packages.doc.html#70209">§7.5</a>). */ public final void visitImportDeclaration(final GNode n) { _externalAnalyzer.visitImportDeclaration(n); } /** * Visit an InstanceOfExpression = Expression Type (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#40641">§15.20</a>). */ public final Type visitInstanceOfExpression(final GNode n) { final GNode nExpression = n.getGeneric(0); final Type tExpression = dispatchRValue(nExpression); if (!tExpression.isError()) { if (JavaEntities.isReferenceT(tExpression) || JavaEntities.isNullT(tExpression)) { final GNode nType = n.getGeneric(1); final Type tType = (Type) dispatch(nType); if (!tType.isError()) { if (JavaEntities.isReferenceT(tType)) assrt(n, JavaTypeConverter.isCastable(_table, classpath(), tType, tExpression), "not castable"); else _runtime.error("reference type expected", nType); } } else { _runtime.error("reference type expected", nExpression); } } return setType(n, JavaEntities.nameToBaseType("boolean")); } /** * Visit an IntegerLiteral (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#48282">§3.10.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#224125">§15.8.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitIntegerLiteral(final GNode n) { final String s = n.getString(0); final int len = s.length(), radix, digitsStart; final boolean isNeg = '-' == s.charAt(0); final boolean isLong = 'L' == Character.toUpperCase(s.charAt(len - 1)); final int digitsEnd = isLong ? len - 1 : len; if (s.startsWith("0x") || s.startsWith("0X")) { radix = 16; digitsStart = isNeg ? 3 : 2; } else if (1 < digitsEnd && '0' == s.charAt(0)) { radix = 8; digitsStart = isNeg ? 2 : 1; } else { radix = 10; digitsStart = isNeg ? 1 : 0; } final String digits = s.substring(digitsStart, digitsEnd); BigInteger bigInt = new BigInteger(digits, radix); assert bigInt.compareTo(BigInteger.ZERO) >= 0; final BigInteger halfLong = new BigInteger("8000000000000000", 16); final BigInteger halfInt = new BigInteger("80000000", 16); final BigInteger fullLong = halfLong.add(halfLong), fullInt = halfInt.add(halfInt); final BigInteger max; if (10 == radix) if (isLong) max = isNeg ? halfLong : halfLong.subtract(BigInteger.ONE); else max = isNeg ? halfInt : halfInt.subtract(BigInteger.ONE); else max = (isLong ? fullLong : fullInt).subtract(BigInteger.ONE); if (!assrt(n, bigInt.compareTo(max) <= 0, "literal out of range")) return setType(n, ErrorT.TYPE); if (10 != radix && isLong && bigInt.compareTo(halfLong) >= 0) bigInt = bigInt.subtract(fullLong); if (10 != radix && !isLong && bigInt.compareTo(halfInt) >= 0) bigInt = bigInt.subtract(fullInt); if (isNeg) bigInt = BigInteger.ZERO.subtract(bigInt); final Type type = JavaEntities.nameToBaseType(isLong ? "long" : "int"); final Number value = isLong ? (Number)new Long(bigInt.longValue()) : new Integer(bigInt.intValue()); if (!assrt(n, bigInt.equals(BigInteger.valueOf(value.longValue())), "literal out of range")) return setType(n, ErrorT.TYPE); return setType(n, type.annotate().constant(value)); } /** * Visit a InterfaceDeclaration = Modifiers Identifier null [Extension] ClassBody * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/interfaces.doc.html#35470">§9.1</a>). */ public final void visitInterfaceDeclaration(final GNode n) { if (JavaEntities.isScopeLocal(_table.current().getQualifiedName())) { _runtime.error("interface cannot be local", n); return; } final JavaContext savedContext = _context.save(); _context._handledExceptions = new ArrayList<Type>(); _context._hasScope = true; _context._switch = false; _context._loop = false; final String simpleName = n.getString(1); assrtLegalIdentifier(n, simpleName); InterfaceT base = (InterfaceT) _table.current().lookupLocally(SymbolTable.toTagName(simpleName)); if (null == base) base = _externalAnalyzer.visitInterfaceDeclaration(n); final Set<String> seenInterfaces = new HashSet<String>(); for (int i=0; i<base.getInterfaces().size(); i++) { // gosling_et_al_2000 9.1.2 final Type t = resolveIfAlias(base.getInterfaces().get(i), n.getGeneric(3).getNode(i)); if (!t.isError() && assrt(n.getGeneric(3), t.isInterface(), "interface expected")) { final String qname = t.toInterface().getQName(); assrt(n.getGeneric(3), !seenInterfaces.contains(qname), "duplicate superinterfaces"); seenInterfaces.add(qname); assrt(n.getGeneric(3), JavaEntities.isAccessible(_table, classpath(), t), "superinterface not accessible"); } } assrt(n, !JavaEntities.hasCircularDependency(_table, classpath(), base), "circular class"); _table.enter(simpleName); _table.mark(n.getNode(4)); dispatch(n.getNode(4)); _table.exit(); _context.restore(savedContext); } /** * Visit a LabeledStatement = Identifier Statement (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#78993">§14.7</a>). */ public final void visitLabeledStatement(final GNode n) { _table.enter(_table.freshName("labeledStatement")); _table.mark(n); final String simpleName = n.getString(0); final String symbol = SymbolTable.toLabelName(simpleName); final SymbolTable.Scope scope = _table.current(); SymbolTable.Scope s = scope; while (true) { final String q = s.getQualifiedName(); if (JavaEntities.isScopeTopLevel(q) || JavaEntities.isScopeForMember(q)) break; assrt(n, null == s.lookupLocally(symbol), "duplicate label " + simpleName); s = s.getParent(); } final LabelT label = new LabelT(simpleName); scope.define(symbol, label); final String kind = n.getNode(1).getName(); if ("ForStatement".equals(kind) || "WhileStatement".equals(kind) || "DoWhileStatement".equals(kind)) label.addAttribute(Constants.ATT_LOOP); dispatch(n.getNode(1)); _table.exit(); } /** * Visit a LogicalAndExpression = Expression Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5228">§15.22</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitLogicalAndExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(1)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final Type result; final Type tBool = JavaEntities.nameToBaseType("boolean"); final boolean xIsBool = JavaTypeConverter.isIdentical(x, tBool), yIsBool = JavaTypeConverter.isIdentical(y, tBool); assrt(n.getGeneric(0), xIsBool, "operand must be boolean"); assrt(n.getGeneric(1), yIsBool, "operand must be boolean"); if (xIsBool && x.hasConstant() && yIsBool && y.hasConstant()) result = tBool.annotate().constant(x.getConstant().isTrue() && y.getConstant().isTrue()); else result = tBool; return setType(n, result); } /** * Visit a LogicalNegationExpression = Expression * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#4990">§15.15</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitLogicalNegationExpression(final GNode n) { final Type type = (Type) dispatch(n.getGeneric(0)); if (type.isError()) return setType(n, type); final Type tBool = JavaEntities.nameToBaseType("boolean"); if (!assrt(n, JavaTypeConverter.isIdentical(getRValue(type, n), tBool), "operand must be boolean")) return setType(n, ErrorT.TYPE); if (type.hasConstant()) return setType(n, tBool.annotate().constant(!type.getConstant().isTrue())); return setType(n, type); } /** * Visit a LogicalOrExpression = Expression Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5228">§15.22</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitLogicalOrExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(1)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final Type result; final Type tBool = JavaEntities.nameToBaseType("boolean"); final boolean xIsBool = JavaTypeConverter.isIdentical(x, tBool), yIsBool = JavaTypeConverter.isIdentical(y, tBool); assrt(n.getGeneric(0), xIsBool, "operand must be boolean"); assrt(n.getGeneric(1), yIsBool, "operand must be boolean"); if (xIsBool && x.hasConstant() && yIsBool && y.hasConstant()) result = tBool.annotate().constant(x.getConstant().isTrue() || y.getConstant().isTrue()); else result = tBool; return setType(n, result); } /** * Visit a MethodDeclaration = Modifiers null Type Identifier FormalParameters [Dimensions] * [ThrowsClause] [Block] (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#40420">§8.4</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#41652">8.8</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/interfaces.doc.html#78651">9.4</a>). */ public Type visitMethodDeclaration(final GNode n) { assrtLegalIdentifier(n, n.getString(3)); _table.enter(JavaEntities.methodSymbolFromAst(n)); _table.mark(n); final JavaContext savedContext = _context.save(); _context._hasScope = false; final MethodT result = JavaEntities.currentMethod(_table); _context._static = hasModifier(result, "static"); resolveIfAlias(result.getResult(), n.getNode(2)); _context._handledExceptions = null == n.get(6) ? new ArrayList<Type>() : result.getExceptions(); assrtLegalHandledExceptions(n.getGeneric(6)); assrtLegalMethodBody(n, result); assrtLegalMethod(n, result); dispatch(n.getNode(4)); dispatch(n.getNode(7)); _context.restore(savedContext); _table.exit(); return setType(n, result); } /** Visit a Modifiers = Modifier* (gosling_et_al_2000 * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#21613">§8.1.1</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#78091">§8.3.1</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#78188">§8.4.3</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#247581">§8.5.1</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#42018">§8.8.3</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/interfaces.doc.html#235947">§9.1.1</a>). */ public final List<Attribute> visitModifiers(final GNode n) { return _externalAnalyzer.visitModifiers(n); } /** * Visit a MultiplicativeExpression = Expression ("*" / "/" / "%") Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#239829">§15.17</a>). */ public final Type visitMultiplicativeExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(2)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final String o = n.getString(1); assert "*".equals(o) || "/".equals(o) || "%".equals(o); final Type result; final Type promX = JavaTypeConverter.promoteBinaryNumeric(y, x); final Type promY = JavaTypeConverter.promoteBinaryNumeric(x, y); if (null == promX || null == promY) { _runtime.error("numeric operands expected", n); result = JavaEntities.nameToBaseType("double"); } else if (!promX.hasConstant() || !promY.hasConstant()) { result = JavaEntities.resolveToRawRValue(promX); } else { final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promX); final Number valNumX = (Number) promX.getConstant().getValue(); final Number valNumY = (Number) promY.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valX = valNumX.intValue(), valY = valNumY.intValue(); if ("*".equals(o)) result = typNum.annotate().constant(new Integer(valX * valY)); else if (0 == valY) result = typNum; else if ("/".equals(o)) result = typNum.annotate().constant(new Integer(valX / valY)); else result = typNum.annotate().constant(new Integer(valX % valY)); break; } case LONG: { final long valX = valNumX.longValue(), valY = valNumY.longValue(); if ("*".equals(o)) result = typNum.annotate().constant(new Long(valX * valY)); else if (0 == valY) result = typNum; else if ("/".equals(o)) result = typNum.annotate().constant(new Long(valX / valY)); else result = typNum.annotate().constant(new Long(valX % valY)); break; } case FLOAT: { final float valX = valNumX.floatValue(), valY = valNumY.floatValue(); if ("*".equals(o)) result = typNum.annotate().constant(new Float(valX * valY)); else if ("/".equals(o)) result = typNum.annotate().constant(new Float(valX / valY)); else result = typNum.annotate().constant(new Float(valX % valY)); break; } case DOUBLE: { final double valX = valNumX.doubleValue(), valY = valNumY .doubleValue(); if ("*".equals(o)) result = typNum.annotate().constant(new Double(valX * valY)); else if ("/".equals(o)) result = typNum.annotate().constant(new Double(valX / valY)); else result = typNum.annotate().constant(new Double(valX % valY)); break; } default: throw new Error(); } } return setType(n, result); } /** Visit a NewArrayExpression = TypeName ConcreteDimensions [Dimensions] null / TypeName null [Dimensions] ArrayInitializer. * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#46168">§15.10</a>). */ public final Type visitNewArrayExpression(final GNode n) { final Type component = processTypeName(n.getGeneric(0)); if (component.isError()) return setType(n, ErrorT.TYPE); dispatch(n.getNode(1)); final int abstractDim = JavaExternalAnalyzer.countDimensions(n.getGeneric(2)); final int dim = abstractDim + (null == n.get(1) ? 0 : n.getGeneric(1).size()); final Type result = JavaEntities.typeWithDimensions(component, dim); if (null != n.get(3)) { final JavaContext savedContext = _context.save(); _context._initializing = getRValue(result, n.getGeneric(0)); final Type src = dispatchRValue(n.getGeneric(3)); if (!result.isError() && !src.isError()) assrt(n, JavaTypeConverter.isAssignable(_table, classpath(), _context._initializing, src), "initializer type mismatch"); _context.restore(savedContext); } return setType(n, result); } /** * Visit a NewClassExpression = [Expression] Type TypeName Arguments [ClassBody] * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#41147">§15.9</a>). */ public final Type visitNewClassExpression(final GNode n) { assrt(n, null == n.get(4), "anonymous classes not yet implemented"); dispatch(n.getNode(0)); final Type result = processTypeName(n.getGeneric(2)); if (result.isError() || !assrt(n.getGeneric(2), result.isClass(), "can only instantiate class types") || !assrt(n.getGeneric(2), !hasModifier(result, "abstract"), "cannot instantiate abstract type")) return setType(n, ErrorT.TYPE); final List<Type> actuals = JavaEntities.typeList((List)dispatch(n.getNode(3))); final Type constructor = JavaEntities.typeDotMethod( _table, classpath(), result, false, JavaEntities.typeToSimpleName(result), actuals); if (null == constructor) { _runtime.error("could not find constructor", n); setType(n.getGeneric(3), ErrorT.TYPE); } else { setType(n.getGeneric(3), constructor); } return setType(n, result); } /** * Visit a NullLiteral (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#230717">§3.10.7</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#224125">§15.8.1</a>). */ public final Type visitNullLiteral(final GNode n) { return setType(n, JavaEntities.nameToBaseType("null")); } /** * Visit a PackageDeclaration = QualifiedIdentifier (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/packages.doc.html#26619">§7.4</a>). */ public final void visitPackageDeclaration(final GNode n) { _externalAnalyzer.visitPackageDeclaration(n); } /** * Visit a PostfixExpression = Expression ("++" / "--") * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#36254">§15.14</a>). */ public final Type visitPostfixExpression(final GNode n) { final Type lValue = (Type) dispatch(n.getGeneric(0)); if (lValue.isError()) return setType(n, ErrorT.TYPE); if (!assrt(n, JavaEntities.isGeneralLValueT(lValue), "operand of %s must be variable", n.getString(1))) return setType(n, lValue); assrt(n, !hasModifier(lValue, "final"), "operand of %s must not be final", n.getString(1)); final Type result = JavaEntities.dereference(lValue); final Type raw = JavaEntities.resolveToRawRValue(result); assrt(n, raw.isNumber(), "operand of %s must be number", n.getString(1)); return setType(n, result); } /** * Visit a PrimaryIdentifier = Identifier (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/names.doc.html#106941">§6.5</a>). */ public Type visitPrimaryIdentifier(final GNode n) { // TD 02 (6.5.2, 15.11.1) PrimaryIdentifier = Identifier final String id = n.getString(0); final Type t = JavaEntities.simpleNameToPackageOrTypeOrExpression( _table, classpath(), _table.current().getQualifiedName(), id); assert t.isPackage() || JavaEntities.isWrappedClassT(t) || JavaEntities.isWrappedInterfaceT(t) || JavaEntities.isLocalT(t) || JavaEntities.isFieldT(t) || JavaEntities.isParameterT(t); if (JavaEntities.isFieldT(t)) assrt(n, hasModifier(t, "static") || !_context._static, "static use of instance field"); final Type result = JavaEntities.notAValueIfClassOrInterface(t); return setType(n, result); } /** * Visit a PrimitiveType = ("byte" / "short" / "char" / "int" / "long" / "float" / "double" / "boolean") * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/typesValues.doc.html#85587">§4.2</a>). */ public final Type visitPrimitiveType(final GNode n) { return JavaEntities.nameToBaseType(n.getString(0)); } /** * Visit a QualifiedIdentifier = Identifier+ (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/names.doc.html#106941">§6.5</a>). */ public final String visitQualifiedIdentifier(final GNode n) { return _externalAnalyzer.visitQualifiedIdentifier(n); } /** * Visit a RelationalExpression = Expression ("<" / ">" / "<=" / ">=") Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#40641">§15.20</a>). */ public final Type visitRelationalExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(2)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final String o = n.getString(1); final Type result; Type tBool = JavaEntities.nameToBaseType("boolean"); assert "<".equals(o) || ">".equals(o) || "<=".equals(o) || ">=".equals(o); final Type promX = JavaTypeConverter.promoteBinaryNumeric(y, x); final Type promY = JavaTypeConverter.promoteBinaryNumeric(x, y); if (null == promX || null == promY) { _runtime.error("numeric operands expected", n); result = tBool; } else if (!promX.hasConstant() || !promY.hasConstant()) { result = tBool; } else { final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promX); final Number valNumX = (Number) promX.getConstant().getValue(); final Number valNumY = (Number) promY.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valX = valNumX.intValue(), valY = valNumY.intValue(); if ("<".equals(o)) result = tBool.annotate().constant(valX < valY); else if ("<=".equals(o)) result = tBool.annotate().constant(valX <= valY); else if (">".equals(o)) result = tBool.annotate().constant(valX > valY); else result = tBool.annotate().constant(valX >= valY); break; } case LONG: { final long valX = valNumX.longValue(), valY = valNumY.longValue(); if ("<".equals(o)) result = tBool.annotate().constant(valX < valY); else if ("<=".equals(o)) result = tBool.annotate().constant(valX <= valY); else if (">".equals(o)) result = tBool.annotate().constant(valX > valY); else result = tBool.annotate().constant(valX >= valY); break; } case FLOAT: { final float valX = valNumX.floatValue(), valY = valNumY.floatValue(); if ("<".equals(o)) result = tBool.annotate().constant(valX < valY); else if ("<=".equals(o)) result = tBool.annotate().constant(valX <= valY); else if (">".equals(o)) result = tBool.annotate().constant(valX > valY); else result = tBool.annotate().constant(valX >= valY); break; } case DOUBLE: { final double valX = valNumX.doubleValue(), valY = valNumY .doubleValue(); if ("<".equals(o)) result = tBool.annotate().constant(valX < valY); else if ("<=".equals(o)) result = tBool.annotate().constant(valX <= valY); else if (">".equals(o)) result = tBool.annotate().constant(valX > valY); else result = tBool.annotate().constant(valX >= valY); break; } default: throw new Error(); } } return setType(n, result); } /** * Visit a ReturnStatement = [Expression] (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#6767">§14.16</a>). */ public void visitReturnStatement(final GNode n) { final MethodT method = JavaEntities.currentMethod(_table); if (null == method) { _runtime.error("return statement outside method", n); return; } final Type t1 = null == n.get(0) ? VoidT.TYPE : dispatchRValue(n.getGeneric(0)); final Type result = method.getResult(); if (t1.isError() || result.isError()) return; if (result.isVoid()) { assrt(n, t1.isVoid(), "'return' with a value, in method returning void"); } else { if (t1.isVoid()) _runtime.error("'return' with no value, in method returning non-void", n); else assrt(n, JavaTypeConverter.isAssignable(_table, classpath(), result, t1), "return type mismatch"); } } /** * Visit a SelectionExpression = Expression Identifier (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#37055">§15.11.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitSelectionExpression(final GNode n) { //TD 19 (15.11.1, 15.28) SelectionExpression = Expression Identifier final Type basePackageOrTypeOrField = (Type) dispatch(n.getGeneric(0)); final Type baseExpressionType = getRValueNoError(basePackageOrTypeOrField); final String selector = n.getString(1); final Type t; if (null != baseExpressionType) { t = JavaEntities.typeDotTypeOrField(_table, classpath(), baseExpressionType, true, selector); } else if (basePackageOrTypeOrField.isPackage()) { t = JavaEntities.packageDotPackageOrType(_table, classpath(), (PackageT) basePackageOrTypeOrField, selector); } else if (JavaEntities.isNotAValueT(basePackageOrTypeOrField)) { final Type baseType = JavaEntities.resolveToValue(basePackageOrTypeOrField.toAnnotated()); t = JavaEntities.typeDotTypeOrField(_table, classpath(), baseType, true, selector); if (!assrt(n, null != t, "unknown or ambiguous type or field %s", selector) || !assrt(n, null == t || JavaEntities.hasModifier(t, "static"), "static access to non-static field")) return setType(n, ErrorT.TYPE); } else if (JavaEntities.isWrappedClassT(basePackageOrTypeOrField) || JavaEntities.isWrappedInterfaceT(basePackageOrTypeOrField)) { t = JavaEntities.typeDotTypeOrField(_table, classpath(), basePackageOrTypeOrField, true, selector); } else { throw new Error(basePackageOrTypeOrField.getClass().getName()); } if (null == t) return setType(n, ErrorT.TYPE); final Type result = JavaEntities.notAValueIfClassOrInterface(t); return setType(n, result); } /** * Visit a ShiftExpression = Expression ("<<" / ">>" / ">>>") Expression * (gosling_et_al_2000 <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5121">§15.19</a>). */ public final Type visitShiftExpression(final GNode n) { final Type xLvalue = (Type) dispatch(n.getGeneric(0)); final Type x = getRValue(xLvalue, n.getGeneric(0)); final Type y = dispatchRValue(n.getGeneric(2)); if (x.isError() || y.isError()) return setType(n, ErrorT.TYPE); final String o = n.getString(1); final Type result; assert "<<".equals(o) || ">>".equals(o) || ">>>".equals(o); final Type promX = JavaTypeConverter.promoteUnaryNumeric(x); final Type promY = JavaTypeConverter.promoteUnaryNumeric(y); if (null == promX) { _runtime.error("integral operand expected", n.getNode(0)); result = JavaEntities.nameToBaseType("long"); } else if (null == promY) { _runtime.error("integral operand expected", n.getNode(2)); result = promX; } else { final NumberT typNumX = (NumberT) JavaEntities.resolveToRawRValue(promX); final NumberT typNumY = (NumberT) JavaEntities.resolveToRawRValue(promY); if (!typNumX.isInteger()) { _runtime.error("integral operand expected", n.getNode(0)); result = JavaEntities.nameToBaseType("int"); } else if (!typNumY.isInteger()) { _runtime.error("integral operand expected", n.getNode(2)); result = promX; } else if (!promX.hasConstant() || !promY.hasConstant()) { result = promX; } else { final Number valNumX = (Number) promX.getConstant().getValue(); final Number valNumY = (Number) promY.getConstant().getValue(); final long valY = valNumY.longValue(); switch (typNumX.getKind()) { case INT: { final int valX = valNumX.intValue(); if ("<<".equals(o)) result = typNumX.annotate().constant(new Integer(valX << valY)); else if (">>".equals(o)) result = typNumX.annotate().constant(new Integer(valX >> valY)); else result = typNumX.annotate().constant(new Integer(valX >>> valY)); break; } case LONG: { final long valX = valNumX.longValue(); if ("<<".equals(o)) result = typNumX.annotate().constant(new Long(valX << valY)); else if (">>".equals(o)) result = typNumX.annotate().constant(new Long(valX >> valY)); else result = typNumX.annotate().constant(new Long(valX >>> valY)); break; } default: throw new Error(); } } } return setType(n, result); } /** Visit a StringLiteral (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#101083">§3.10.5</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#224125">§15.8.1</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitStringLiteral(final GNode n) { final Type tString = JavaEntities.tString(_table); final String s = n.getString(0); final StringBuffer result = new StringBuffer(); final int len = s.length() - 1; int i = 1; try { while (i < len) { final char c = s.charAt(i); if (c == '\\') { result.append(escapeSequenceChar(s, i)); i = escapeSequenceEnd(s, i); } else { if (!assrt(n, '\r' != c && '\n' != c, "string must not contain line terminator")) return setType(n, ErrorT.TYPE); result.append(c); i++; } } } catch (final IllegalArgumentException e) { _runtime.error("illegal escape sequence", n); return setType(n, ErrorT.TYPE); } return setType(n, tString.annotate().constant(result.toString())); } /** * Visit a SubscriptExpression = Expression Expression (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#239587">§15.13</a>). */ public final Type visitSubscriptExpression(final GNode n) { final Type arrayT = dispatchRValue(n.getGeneric(0)); if (!assrt(n, arrayT.isArray(), "array reference expression expected")) return setType(n, ErrorT.TYPE); final Type indexT = dispatchRValue(n.getGeneric(1)); assrt(n, JavaEntities.isInt(indexT), "integer expression expected"); final AnnotatedT result = (AnnotatedT) ((ArrayT) arrayT).getType(); resolveIfAlias(JavaEntities.dereference(result)); return setType(n, result); } /** * Visit a SuperExpression = [Type] (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#20860">§15.11.2</a>). */ public final Type visitSuperExpression(final GNode n) { // TD 20 (15.11.2) SuperExpression = [Type] assrt(n, !_context._static, "static use of super"); final Type subType = JavaEntities.currentType(_table); final ClassT rawSubType = (ClassT)JavaEntities.resolveToRawClassOrInterfaceT(subType); final Type result = JavaEntities.resolveIfAlias(_table, classpath(), JavaEntities.typeToScopeName(subType), rawSubType.getParent()); return setType(n, result); } /** * Visit a SwitchStatement = Expression SwitchClause* (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#35518">§14.10</a>). */ public final void visitSwitchStatement(final GNode n) { final JavaContext savedContext = _context.save(); _context._switch = true; final Type expression = (Type) dispatch(n.getGeneric(0)); final Type eRaw; { final Type r = JavaEntities.isGeneralLValueT(expression) ? JavaEntities .dereference(expression) : expression; eRaw = JavaEntities.resolveToRawRValue(r); } assrt(n.getGeneric(0), JavaEntities.nameToBaseType("char") == eRaw || JavaEntities.nameToBaseType("byte") == eRaw || JavaEntities.nameToBaseType("short") == eRaw || JavaEntities.nameToBaseType("int") == eRaw, "switch expression must be char, byte, short, or int"); boolean sawDefault = false; final Set<Integer> seenValues = new HashSet<Integer>(); for (int iClause = 1; iClause < n.size(); iClause++) { final GNode clauseNode = n.getGeneric(iClause); if (clauseNode.hasName("DefaultClause")) { dispatch(clauseNode); if (sawDefault) _runtime.error("duplicate default clause", clauseNode); sawDefault = true; } else { //final Type clauseType = dispatchRValue(clauseNode); final Type clauseTypeL = (Type)dispatch(clauseNode); final Type clauseType = getRValue(clauseTypeL, clauseNode); if (JavaTypeConverter.isAssignable(_table, classpath(), eRaw, clauseType)) { if (null == clauseType) throw new Error(); if (JavaEntities.isConstantT(clauseType)) { final Object clauseValue = clauseType.getConstant().getValue(); if (null == clauseValue) throw new Error(); final int i = clauseValue instanceof Character ? ((Character) clauseValue).charValue() : ((Number) clauseValue).intValue(); if (seenValues.contains(new Integer(i))) _runtime.error("duplicate case clause", clauseNode); else seenValues.add(new Integer(i)); assrt(clauseNode, isAssignable(i, eRaw), "invalid case clause"); } else { assrt(n, JavaEntities.isGeneralLValueT(clauseTypeL) && hasModifier(clauseTypeL, "final"), "case expression must be constant"); } } else { _runtime.error("invalid case clause", clauseNode); } } } _context.restore(savedContext); } /** * Visit a SynchronizedStatement = Expression Block (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#255769">§14.18</a>). */ public final void visitSynchronizedStatement(final GNode n) { final GNode nMonitor = n.getGeneric(0); final Type tMonitor = dispatchRValue(nMonitor); assrt(nMonitor, !JavaEntities.isNullT(tMonitor) && JavaEntities.isReferenceT(tMonitor), "invalid type for synchronized statement"); dispatch(n.getNode(1)); } /** * Visit a ThisExpression = [Expression] (gosling_et_al_2000 * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#251519">§15.8.3</a>, * <a href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#251603">§15.8.4</a>). */ public final Type visitThisExpression(final GNode n) { // TD 25 (15.8.3, 15.8.4) ThisExpression = [Expression] assrt(n, !_context._static, "static use of this"); return setType(n, JavaEntities.currentType(_table)); } /** * Visit a ThrowStatement = Expression (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#237350">§14.17</a>). */ public final void visitThrowStatement(final GNode n) { final Type tThrown = dispatchRValue(n.getGeneric(0)); final Type tThrowable = JavaEntities.tThrowable(_table); if (!JavaTypeConverter.isAssignable(_table, classpath(), tThrowable, tThrown)) _runtime.error("exception must be throwable", n.getNode(0)); else if (JavaEntities.isCheckedException(_table, classpath(), tThrown)) assrt(n, isHandled(tThrown), "uncaught exception"); } /** * Visit a TryCatchFinallyStatement = null Block CatchClause* [Block] * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#79311">§14.19</a>). */ public final List<Type> visitTryCatchFinallyStatement(final GNode n) { final List<Type> result = new ArrayList<Type>(); for (int i = 2; i < n.size() - 1; i++) result.add((Type)dispatch(n.getNode(i))); final JavaContext savedContext = _context.save(); _context._handledExceptions = new ArrayList<Type>(); _context._handledExceptions.addAll(savedContext._handledExceptions); _context._handledExceptions.addAll(result); dispatch(n.getNode(1)); _context.restore(savedContext); dispatch(n.getNode(n.size() - 1)); return result; } /** * Visit a Type = TypeName Dimensions * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/typesValues.doc.html#48440">§4</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/arrays.doc.html#25518">§10.1</a>). * Note that TypeName is either PrimitiveType or ClassType, i.e., QualifiedIdentifier. */ public final Type visitType(final GNode n) { //TD 01 (4, 10.1) Type = TypeName Dimensions final Type t = _externalAnalyzer.visitType(n); final Type result = resolveIfAlias(t, n); return setType(n, result); } /** * Visit a UnaryExpression = ("+" / "-" / "++" / "--") Expression * (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#4990">§15.15</a>, * <a * href="http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#5313">§15.28</a>). */ public final Type visitUnaryExpression(final GNode n) { final Type type = (Type) dispatch(n.getGeneric(1)); if (type.isError()) return setType(n, type); String operator = n.getString(0); if ("++".equals(operator) || "--".equals(operator)) { if (JavaEntities.isGeneralLValueT(type)) { assrt(n, !hasModifier(type, "final"), "operand of %s must not be final", operator); final Type rValue = JavaEntities.dereference(type); final Type raw = JavaEntities.resolveToRawRValue(rValue); if (!assrt(n, raw.isNumber(), "operand of %s must be number", operator)) return setType(n, ErrorT.TYPE); return setType(n, rValue); } _runtime.error("operand of " + operator + " must be variable", n); return setType(n, type); } else { assert "+".equals(operator) || "-".equals(operator); final Type promoted = JavaTypeConverter.promoteUnaryNumeric(getRValue(type, n.getGeneric(1))); if (!assrt(n, null != promoted, "operand must be numeric")) return setType(n, ErrorT.TYPE); if (promoted.hasConstant()) { if ("+".equals(operator)) return setType(n, promoted); final NumberT typNum = (NumberT) JavaEntities.resolveToRawRValue(promoted); final Number valNum = (Number) promoted.getConstant().getValue(); switch (typNum.getKind()) { case INT: { final int valInt = valNum.intValue(); return typNum.annotate().constant(new Integer("-".equals(operator) ? -valInt : ~valInt)); } case LONG: { final long valLong = valNum.longValue(); return typNum.annotate().constant(new Long("-".equals(operator) ? -valLong : ~valLong)); } case FLOAT: { if (!assrt(n, "-".equals(operator), "operand must be an integral type")) return setType(n, ErrorT.TYPE); return typNum.annotate().constant(new Float(-valNum.floatValue())); } case DOUBLE: { if (!assrt(n, "-".equals(operator), "operand must be an integral type")) return setType(n, ErrorT.TYPE); return typNum.annotate().constant(new Double(-valNum.doubleValue())); } default: throw new Error(); } } else { return setType(n, promoted); } } } /** * Visit a WhileStatement = Expression Statement (gosling_et_al_2000 <a * href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#237277">§14.11</a>). */ public final void visitWhileStatement(final GNode n) { final JavaContext savedContext = _context.save(); _context._loop = true; final Type condition = dispatchRValue(n.getGeneric(0)); if (!JavaEntities.resolveToRawRValue(condition).isBoolean()) _runtime.error("condition must be boolean", n.getGeneric(0)); dispatch(n.getNode(1)); _context.restore(savedContext); } }