/* * xtc - The eXTensible Compiler * Copyright (C) 2005-2008 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.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import xtc.Constants; import xtc.Limits; import xtc.tree.Attribute; import xtc.tree.GNode; import xtc.tree.Node; import xtc.tree.Token; import xtc.tree.Visitor; import xtc.type.AliasT; import xtc.type.ArrayT; import xtc.type.BooleanT; import xtc.type.C; import xtc.type.CastReference; import xtc.type.Constant; import xtc.type.DynamicReference; import xtc.type.EnumT; import xtc.type.EnumeratorT; import xtc.type.ErrorT; import xtc.type.FieldReference; import xtc.type.FunctionT; import xtc.type.InternalT; import xtc.type.LabelT; import xtc.type.NullReference; import xtc.type.NumberT; import xtc.type.PointerT; import xtc.type.Reference; import xtc.type.StaticReference; import xtc.type.StringReference; import xtc.type.StructOrUnionT; import xtc.type.StructT; import xtc.type.Tagged; import xtc.type.Type; import xtc.type.Type.Tag; import xtc.type.UnionT; import xtc.type.VariableT; import xtc.type.VoidT; import xtc.util.SymbolTable; import xtc.util.SymbolTable.Scope; import xtc.util.Runtime; import xtc.util.SingletonIterator; import xtc.util.Utilities; /** * A visitor to type check C programs. * * @author Robert Grimm * @version $Revision: 1.214 $ */ public class CAnalyzer extends Visitor { /** * The semantic information contained in a sequence of declaration * specifiers. */ public class Specifiers extends Visitor { /** The declaration specifiers. */ protected GNode specifiers; /** The flag for whether a tag reference is a declaration. */ protected boolean refIsDecl; /** The type, if any. */ protected Type type; /** The storage class attribute, if any. */ private Attribute storage; /** The thread-local attribute, if any. */ private Attribute threadlocal; /** The inline attribute, if any. */ private Attribute function; /** Any other attributes. */ private List<Attribute> attributes; // The internal state for tracking type specifiers. private boolean seenSigned; private boolean seenUnsigned; private boolean seenBool; private boolean seenChar; private boolean seenShort; private boolean seenInt; private int longCount; private boolean seenFloat; private boolean seenDouble; private boolean seenComplex; /** * Create a new sequence of specifiers. This constructor analyzes * the specified AST node and may print error messages as a * result. * * @param specifiers The node holding the declaration specifiers * (which may be <code>null</code>). * @param refIsDecl The flag for whether a struct/union reference * also is a struct/union declaration. */ public Specifiers(GNode specifiers, boolean refIsDecl) { // Remember the arguments. this.specifiers = specifiers; this.refIsDecl = refIsDecl; // Process the individual specifiers. if (null != specifiers) { for (Object o : specifiers) dispatch((Node)o); } // Fill in the resulting type. if (null != type) { // Nothing to do. } else if (seenBool) { type = BooleanT.TYPE; } else if (seenChar) { if (seenUnsigned) { type = NumberT.U_CHAR; } else if (seenSigned) { type = NumberT.S_CHAR; } else { type = NumberT.CHAR; } } else if (seenShort) { if (seenUnsigned) { type = NumberT.U_SHORT; } else { type = NumberT.SHORT; } } else if (seenFloat) { if (seenComplex) { type = NumberT.FLOAT_COMPLEX; } else { type = NumberT.FLOAT; } } else if (seenDouble) { if (0 < longCount) { if (seenComplex) { type = NumberT.LONG_DOUBLE_COMPLEX; } else { type = NumberT.LONG_DOUBLE; } } else { if (seenComplex) { type = NumberT.DOUBLE_COMPLEX; } else { type = NumberT.DOUBLE; } } } else if (1 == longCount) { if (seenUnsigned) { type = NumberT.U_LONG; } else { type = NumberT.LONG; } } else if (1 < longCount) { if (seenUnsigned) { type = NumberT.U_LONG_LONG; } else { type = NumberT.LONG_LONG; } } else if (seenUnsigned) { type = NumberT.U_INT; } else if (seenSigned) { type = NumberT.S_INT; } else if (seenInt) { type = NumberT.INT; } else { type = C.IMPLICIT; } // Annotate the type. if ((! type.hasError()) && (null != attributes)) { type = type.annotate().attribute(attributes); } // Seal the type. type.seal(); } /** * Get the base type. The base type has been annotated with all * attributes besides the storage class and function specifier * attributes. * * @return The base type. */ public Type getBaseType() { return type; } /** * Determine whether the base type is the default type. * * @return <code>true</code> if the base type is the default type. */ public boolean isDefault() { return type.hasTag(Tag.INTEGER) && type.resolve().hasAttribute(Constants.ATT_IMPLICIT); } /** * Determine whether the specifiers contain the specified * attribute. * * @param att The attribute. * @return <code>true</code> if the specifiers contain the * specified attribute. */ public boolean contains(Attribute att) { if ((null != attributes) && attributes.contains(att)) return true; if (att.equals(storage)) return true; if (att.equals(threadlocal)) return true; return att.equals(function); } /** * Determine whether the specifiers include any attributes besides * a function specifier, storage class, and thread-local * specifier. * * @return <code>true</code> if the specifiers include any * attributes besides a function specifier, storage class, and * thread-local specifier. */ public boolean hasBaseAttributes() { return null != attributes; } /** * Determine whether the specifiers include a function specifier. * * @return <code>true</code> if the specifiers include a function * specifier. */ public boolean hasInline() { return null != function; } /** * Determine whether the specifiers include a thread-local * specifier. * * @return <code>true</code> if the specifiers include a * thread-local specifier. */ public boolean hasThreadLocal() { return null != threadlocal; } /** * Get the storage class. * * @return The storage class or <code>null</code> if the * specifiers do contain one. */ public Attribute getStorageClass() { return storage; } /** * Annotate the specified base type. This method annotates the * specified type with all attributes besides the storage class, * thread-local specifier, and function specifier attributes. * * @param base The base type. * @return The annnotated base type. */ public Type annotateBase(Type base) { return (null != attributes)? base.attribute(attributes) : base; } /** * Annotate the specified full type. This method annotates the * specified type with any storage class, thread-local specifier, * and function specifier attributes. * * @param full The full type. * @return The annotated full type. */ public Type annotateFull(Type full) { // If the full type is the same type as the base type, wrap the // type with an annotated pseudo-type to prevent changes to the // base type across several declarations. if (null != storage || null != threadlocal || null != function) { if (type == full) full = full.annotate(); if (null != storage) full = full.attribute(storage); if (null != threadlocal) full = full.attribute(threadlocal); if (null != function) full = full.attribute(function); } return full; } /** Add the specified attribute. */ protected void add(Attribute att) { if (null == attributes) { attributes = new ArrayList<Attribute>(); attributes.add(att); } else if (! attributes.contains(att)) { attributes.add(att); } } /** Test for previous storage class specifier and report error. */ protected boolean testStorageClass() { if (null == storage) { return false; } else { runtime.error("multiple storage classes in declaration specifiers", specifiers); return true; } } /** Test for previous type. */ protected boolean hasType() { return (seenBool || seenChar || seenShort || seenInt || (0 < longCount) || seenFloat || seenDouble || seenComplex || (null != type)); } /** Report error indicating multiple types. */ protected void multipleTypes() { runtime.error("multiple data types in declaration specifiers", specifiers); type = ErrorT.TYPE; } /** Process the auto specifier. */ public void visitAutoSpecifier(GNode n) { if (Constants.ATT_STORAGE_AUTO.equals(storage)) { runtime.error("duplicate 'auto'", n); } else if (! testStorageClass()) { storage = Constants.ATT_STORAGE_AUTO; } } /** Process the extern specifier. */ public void visitExternSpecifier(GNode n) { if (Constants.ATT_STORAGE_EXTERN.equals(storage)) { runtime.error("duplicate 'extern'", n); } else if (! testStorageClass()) { storage = Constants.ATT_STORAGE_EXTERN; if (null != threadlocal) { runtime.error("'__thread' before 'extern'", specifiers); } } } /** Process the register specifier. */ public void visitRegisterSpecifier(GNode n) { if (Constants.ATT_STORAGE_REGISTER.equals(storage)) { runtime.error("duplicate 'register'", n); } else if (! testStorageClass()) { storage = Constants.ATT_STORAGE_REGISTER; } } /** Process the static specifier. */ public void visitStaticSpecifier(GNode n) { if (Constants.ATT_STORAGE_STATIC.equals(storage)) { runtime.error("duplicate 'static'", n); } else if (! testStorageClass()) { storage = Constants.ATT_STORAGE_STATIC; if (null != threadlocal) { runtime.error("'__thread' before 'static'", specifiers); } } } /** Process the typedef specifier. */ public void visitTypedefSpecifier(GNode n) { if (Constants.ATT_STORAGE_TYPEDEF.equals(storage)) { runtime.error("duplicate 'typedef'", n); } else if (! testStorageClass()) { storage = Constants.ATT_STORAGE_TYPEDEF; } } /** Process the thread-local specifier. */ public void visitThreadSpecifier(GNode n) { if (null != threadlocal) { runtime.error("duplicate '__thread'", n); } else { threadlocal = Constants.ATT_THREAD_LOCAL; } } /** Process the typeof specifier. */ public void visitTypeofSpecifier(GNode n) { if (hasType()) { multipleTypes(); } else { type = processExpression(n.getNode(0)); // Strip any annotations from the type, but do keep the // qualifiers. if (type.hasEnum()) { type = c().qualify(type.toEnum(), type); } else { type = c().qualify(type.resolve(), type); } } } /** Process the volatile qualifier. */ public void visitVolatileQualifier(GNode n) { add(Constants.ATT_VOLATILE); } /** Process the constant qualifier. */ public void visitConstantQualifier(GNode n) { add(Constants.ATT_CONSTANT); } /** Process the restrict qualifier. */ public void visitRestrictQualifier(GNode n) { add(Constants.ATT_RESTRICT); } /** Process the function specifier. */ public void visitFunctionSpecifier(GNode n) { if (null == function) { function = Constants.ATT_INLINE; } } /** Process the signed type specifier. */ public void visitSigned(GNode n) { if (seenUnsigned) { seenSigned = true; runtime.error("both 'signed' and 'unsigned' in declaration " + "specifiers", specifiers); } else if (seenSigned) { runtime.error("duplicate 'signed'", n); } else { seenSigned = true; } } /** Process the unsigned type specifier. */ public void visitUnsigned(GNode n) { if (seenSigned) { seenUnsigned = true; runtime.error("both 'signed' and 'unsigned' in declaration " + "specifiers", specifiers); } else if (seenUnsigned) { runtime.error("duplicate 'unsigned'", n); } else { seenUnsigned = true; } } /** Process the boolean type specifier. */ public void visitBool(GNode n) { if (hasType()) { multipleTypes(); } else { seenBool = true; } } /** Process the char type specifier. */ public void visitChar(GNode n) { if (hasType()) { multipleTypes(); } else { seenChar = true; } } /** Process the short specifier. */ public void visitShort(GNode n) { if (seenBool || seenChar || seenShort || (0 < longCount) || seenFloat || seenDouble || seenComplex || (null != type)) { multipleTypes(); } else { seenShort = true; } } /** Process the int type specifier. */ public void visitInt(GNode n) { if (seenBool || seenChar || seenInt || seenFloat || seenDouble || seenComplex || (null != type)) { multipleTypes(); } else { seenInt = true; } } /** Process the long type specifier. */ public void visitLong(GNode n) { if (seenBool || seenChar || seenShort || (1 < longCount) || seenFloat || ((seenDouble || seenComplex) && (0 < longCount)) || (null != type)) { multipleTypes(); } else { longCount++; } } /** Process the float type specifier. */ public void visitFloat(GNode n) { if (seenBool || seenChar || seenShort || seenInt || (0 < longCount) || seenDouble || (null != type)) { multipleTypes(); } else { seenFloat = true; } } /** Process the double type specifier. */ public void visitDouble(GNode n) { if (seenBool || seenChar || seenShort || seenInt || (1 < longCount) || seenFloat || (null != type)) { multipleTypes(); } else { seenDouble = true; } } /** Process the complex type specifier. */ public void visitComplex(GNode n) { if (seenBool || seenChar || seenShort || seenInt || (1 < longCount) || (null != type)) { multipleTypes(); } else { seenComplex = true; } } /** * Check that the tag declaration is not located within a * parameter list. If the declaration is located within a * parameter list, this method prints the appropriate warning. * * @param node The node. * @param kind The kind of tag. */ private void checkNotParameter(Node node, String kind) { if (TMP_SCOPE.equals(table.current().getName())) { final String tag = node.getString(1); final String msg; if (null == tag) { msg = "anonymous " + kind + " declared inside parameter list"; } else { msg = "'" + kind + " " + tag + "' declared inside parameter list"; } runtime.warning(msg, node); } } /** * Process the structure declaration list. Note that this * method assumes that the current symbol table scope is the * corresponding struct's or union's scope. */ private List<VariableT> processMembers(GNode declarationList, boolean isStruct) { final int size = declarationList.size() - 1; List<VariableT> members = new ArrayList<VariableT>(size); HashSet<String> names = new HashSet<String>(); int count = 0; for (int i=0; i<size; i++) { final GNode declaration = declarationList.getGeneric(i); final GNode specifiers = declaration.getGeneric(1); final Specifiers spec = newSpecifiers(specifiers, false); if (null == declaration.get(2)) { // We expect an unnamed struct/union member. final Type type = spec.annotateFull(spec.getBaseType()); if (type.hasStructOrUnion() && type.toTagged().isUnnamed() && ! pedantic) { // Check for incomplete types (C99 6.7.2.1). if (c().isIncomplete(type)) { final String kind = type.hasTag(Tag.STRUCT) ? "struct" : "union"; runtime.error("unnamed " + kind + " has incomplete type", declaration); } else if (c().hasTrailingArray(type)) { runtime.error("unnamed struct ends with flexible array member", declaration); } members.add(VariableT.newField(type, null)); } else { runtime.warning("declaration does not declare anything", declaration); } } else { Iterator<Object> iter = declaration.getGeneric(2).iterator(); while (iter.hasNext()) { GNode declarator = GNode.cast(iter.next()); final GNode original = declarator; final boolean isBitField = declarator.hasName("BitField"); GNode widthExpr = null; int width = -1; if (isBitField) { widthExpr = declarator.getGeneric(2); declarator = declarator.getGeneric(1); } final GNode identifier = getDeclaredId(declarator); String name = null; if (null != identifier) { count++; name = identifier.getString(0); } Type type = getDeclaredType(spec.getBaseType(), declarator); type = spec.annotateFull(type); if (isBitField) { final Type resolved = type.resolve(); final NumberT.Kind kind = resolved.isInteger() ? resolved.toInteger().getKind() : null; // Make sure the base type is valid. Consistent with // GCC, we allow any integer type, not just __Bool, // (signed) int, and unsigned int (unless, of course, we // are running in pedantic mode). if ((! resolved.isBoolean()) && ((! resolved.isInteger()) || (resolved.isInteger() && pedantic && (! NumberT.equal(NumberT.Kind.INT, kind)) && (! NumberT.equal(NumberT.Kind.U_INT, kind))))) { if (null == identifier) { runtime.error("bit-field has invalid type", original); } else { runtime.error("bit-field '" + name + "' has invalid type", original); } } // Process the width expression. final Type widthType = processExpression(widthExpr); if (widthType.hasError()) { // Ignore to avoid cascading error messages // but patch in width. width = 0; } else if ((! widthType.hasConstant()) || (! c().isIntegral(widthType))) { if (null == identifier) { runtime.error("bit-field width not an integer constant", widthExpr); } else { runtime.error("bit-field '" + name + "' width not an integer constant", widthExpr); } // Patch in width. width = 0; } else if (c().isIntegral(resolved)) { final int typeWidth = (int)c().getWidth(resolved); BigInteger widthValue; try { widthValue = widthType.getConstant().bigIntValue(); } catch (IllegalStateException x) { if (null == identifier) { runtime.error("can't compute width in bit-field", widthExpr); } else { runtime.error("can't compute width in bit-field '" + name + "'", widthExpr); } widthValue = BigInteger.ZERO; } // Test: widthValue > typeWidth if (widthValue.compareTo(BigInteger.valueOf(typeWidth)) > 0) { if (null == identifier) { runtime.error("bit-field width exceeds its type", widthExpr); } else { runtime.error("bit-field '" + name + "' width exceeds its type", widthExpr); } // Patch in width. width = typeWidth; // Test: widthValue < 0 } else if (widthValue.compareTo(BigInteger.ZERO) < 0) { if (null == identifier) { runtime.error("negative width in bit-field", widthExpr); } else { runtime.error("negative width in bit-field '" + name + "'", widthExpr); } // Patch in width. width = 0; } else if (widthValue.compareTo(BigInteger.ZERO) == 0) { // C99 6.7.2.1-3: Zero width bit-fields have no declarator. if (null != identifier) { runtime.error("zero width for bit-field '" + name + "'", widthExpr); } width = 0; } else { // Use specified value. width = widthValue.intValue(); } } else { // Width does not matter b/c the base type is invalid. width = 0; } } else { // Enforce constraints on incomplete types (C99 6.7.2.1) // and on fields not being functions. final Type resolved = type.resolve(); if (! checkType(declaration, name, type)) { // Error has already been reported. } else if (resolved.isFunction()) { runtime.error("field '" + name + "' declared as a function", declaration); } else if (resolved.isArray()) { ArrayT array = resolved.toArray(); if (c().isIncomplete(array.getType()) || c().hasTrailingArray(array.getType())) { runtime.error("field '" + name + "' has array with incomplete element type", declaration); } else if (array.isVarLength()) { // GCC allows variable length arrays in structs and // unions outside external declarations. if (pedantic) { runtime.error("field '" + name + "' has variable length " + "array type", declaration); } else if (isTopLevel && (! TMP_SCOPE.equals(table.current().getName()))) { runtime.error("variable length array type declared " + "outside of any function", declaration); } } else if (! array.hasLength()) { if (! isStruct) { runtime.error("flexible array member '" + name + "' in union", declaration); } else if ((i < size-1) || iter.hasNext()) { runtime.error("flexible array member '" + name + "' not at end of struct", declaration); } else if (1 >= count) { runtime.error("flexible array member '" + name + "' in otherwise empty struct", declaration); } } } else if (c().isIncomplete(type)) { if (resolved.isVoid()) { runtime.error("field '" + name + "' declared void", declaration); } else { runtime.error("field '" + name + "' has incomplete type", declaration); } } else if (c().hasTrailingArray(type)) { // GCC allows struct fileds with flexible array // members. if (pedantic) { runtime.error("field '" + name + "' has struct with flexible array member", declaration); } } else if (c().isVariablyModified(type)) { // GCC allows variably modified types in structs and // unions outside external declarations. if (pedantic) { runtime.error("field '" + name + "' has variably modified " + "type", declaration); } else if (isTopLevel && (! TMP_SCOPE.equals(table.current().getName()))) { runtime.error("variably modified type declared " + "outside of any function", declaration); } } } if (names.contains(name)) { runtime.error("duplicate member '" + name + "'", original); } else { if (null != name) names.add(name); if (-1 == width) { members.add(VariableT.newField(type, name)); } else { members.add(VariableT.newBitfield(type, name, width)); } } } } } return members; } /** Process the structure type definition. */ public void visitStructureTypeDefinition(GNode n) { if (hasType()) { multipleTypes(); } else { String tag = n.getString(1); String name; if (null == tag) { tag = table.freshName("tag"); name = tag; } else { name = SymbolTable.toTagName(tag); } if (table.current().isDefinedLocally(name)) { final Type t = (Type)table.current().lookupLocally(name); if (! t.isStruct()) { runtime.error("'" + tag + "' defined as wrong kind of tag", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else if (null != t.toTagged().getMembers()) { runtime.error("redefinition of 'struct " + tag + "'", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else if (t.hasAttribute(Constants.ATT_DEFINED)) { runtime.error("nested redefinition of 'struct " + tag + "'", n); type = ErrorT.TYPE; return; } else { type = t; } } else { checkNotParameter(n, "struct"); // Declare the struct so that members can reference it. type = new StructT(tag); table.current().define(name, type); } // Update the location. type.setLocation(n); // Update the GCC attributes. for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } for (Attribute a : toAttributeList(n.getGeneric(3))) { type.addAttribute(a); } // Process the members and update the struct declaration. Use // defined attribute to protect against nested redefinition. type.addAttribute(Constants.ATT_DEFINED); List<VariableT> members = processMembers(n.getGeneric(2), true); type.toStruct().setMembers(members); type.removeAttribute(Constants.ATT_DEFINED); // Seal the struct. type.seal(); // Mark the node. mark(n, type); } } /** Process the structure type reference. */ public void visitStructureTypeReference(GNode n) { if (hasType()) { multipleTypes(); } else { final String tag = n.getString(1); final String name = SymbolTable.toTagName(tag); if ((refIsDecl && table.current().isDefinedLocally(name)) || ((! refIsDecl) && table.isDefined(name))) { final Type t = (Type)table.lookup(name); if (! t.isStruct()) { runtime.error("'" + tag + "' defined as wrong kind of tag", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else { type = t; // If the struct has not yet been defined, update the // location and GCC attributes. if (refIsDecl && (null == type.toStruct().getMembers())) { type.setLocation(n); for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } } } } else { checkNotParameter(n, "struct"); type = new StructT(tag).locate(n); for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } table.current().define(name, type); } // Mark the node. mark(n, type); } } /** Process the union type definition. */ public void visitUnionTypeDefinition(GNode n) { if (hasType()) { multipleTypes(); } else { String tag = n.getString(1); String name; if (null == tag) { tag = table.freshName("tag"); name = tag; } else { name = SymbolTable.toTagName(tag); } if (table.current().isDefinedLocally(name)) { final Type t = (Type)table.current().lookupLocally(name); if (! t.isUnion()) { runtime.error("'" + tag + "' defined as wrong kind of tag", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else if (null != t.toTagged().getMembers()) { runtime.error("redefinition of 'union " + tag + "'", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else if (t.hasAttribute(Constants.ATT_DEFINED)) { runtime.error("nested redefinition of 'union " + tag + "'", n); type = ErrorT.TYPE; return; } else { type = t; } } else { checkNotParameter(n, "union"); // Declare the union so that members can reference it. type = new UnionT(tag); table.current().define(name, type); } // Update the location. type.setLocation(n); // Update the GCC attributes. for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } for (Attribute a : toAttributeList(n.getGeneric(3))) { type.addAttribute(a); } // Process the members and update the union declaration. Use // defined attribute to protected against nested redefinition. type.addAttribute(Constants.ATT_DEFINED); List<VariableT> members = processMembers(n.getGeneric(2), false); type.toUnion().setMembers(members); type.removeAttribute(Constants.ATT_DEFINED); // Seal the union. type.seal(); // Mark the node. mark(n, type); } } /** Process the union type reference. */ public void visitUnionTypeReference(GNode n) { if (hasType()) { multipleTypes(); } else { final String tag = n.getString(1); final String name = SymbolTable.toTagName(tag); if ((refIsDecl && table.current().isDefinedLocally(name)) || ((! refIsDecl) && table.isDefined(name))) { final Type t = (Type)table.lookup(name); if (! t.isUnion()) { runtime.error("'" + tag + "' defined as wrong kind of tag", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else { type = t; // If the union has not yet been defined, update the // location and GCC attributes. if (refIsDecl && (null == type.toUnion().getMembers())) { type.setLocation(n); for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } } } } else { checkNotParameter(n, "union"); type = new UnionT(tag).locate(n); for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } table.current().define(name, type); } // Mark the node. mark(n, type); } } /** Process the enumeration type definition. */ public void visitEnumerationTypeDefinition(GNode n) { if (hasType()) { multipleTypes(); } else { String tag = n.getString(1); String name; if (null == tag) { tag = table.freshName("tag"); name = tag; } else { name = SymbolTable.toTagName(tag); } if (table.current().isDefinedLocally(name)) { final Type t = (Type)table.current().lookupLocally(name); if (! t.isEnum()) { runtime.error("'" + tag + "' defined as wrong kind of tag", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else if (null != t.toTagged().getMembers()) { runtime.error("redefinition of 'enum " + tag + "'", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else { type = t; } } else { checkNotParameter(n, "enum"); type = new EnumT(tag); } // Process the enumerators. final GNode enumerators = n.getGeneric(2); final List<EnumeratorT> types = new ArrayList<EnumeratorT>(enumerators.size()); BigInteger lastValue = BigInteger.ONE.negate(); for (Object o : enumerators) { final GNode rator = GNode.cast(o); final String rname = rator.getString(0); final Node vnode = rator.getNode(1); // The value node. BigInteger value = null; if (null != vnode) { // The enumerator has an explicitly specified value. final Type vtype = processExpression(vnode); if (vtype.hasError()) { // Ignore to avoid cascading error messages. } else if ((! vtype.hasConstant()) || (! c().isIntegral(vtype))) { runtime.error("enumerator value for '" + rname + "' is not an integer constant", vnode); } else { try { value = vtype.getConstant().bigIntValue(); lastValue = value; } catch (IllegalStateException x) { runtime.warning("can't compute value for '" + rname + "'", vnode); value = lastValue.add(BigInteger.ONE); lastValue = value; } } } if (null == value) { // If the value has not been specified or is erroneous, // fall back on increment-by-one. value = lastValue.add(BigInteger.ONE); lastValue = value; } // Record the enumerator in the symbol table. final EnumeratorT ratorT = new EnumeratorT(c().fit(value), rname, value); if (table.current().isDefinedLocally(rname)) { runtime.error("redefinition of '" + rname + "'", rator); } else { table.current().define(rname, ratorT); } // Add the enumerator to the list of enumerators. types.add(ratorT); } // Determine the minimum and maximum value. BigInteger min = BigInteger.ZERO; BigInteger max = BigInteger.ZERO; for (EnumeratorT r : types) { final BigInteger value = r.getConstant().bigIntValue(); // Test: value < min if (value.compareTo(min) < 0) min = value; // Test: value > max if (value.compareTo(max) > 0) max = value; } // Find a fitting overall type. final Type baseT; if (Limits.fitsInt(min) && Limits.fitsInt(max)) { baseT = NumberT.INT; } else if (Limits.fitsUnsignedInt(min) && Limits.fitsUnsignedInt(max)) { baseT = NumberT.U_INT; } else if (Limits.fitsLong(min) && Limits.fitsLong(max)) { baseT = NumberT.LONG; } else if (Limits.fitsUnsignedLong(min) && Limits.fitsUnsignedLong(max)) { baseT = NumberT.U_LONG; } else if (Limits.fitsLongLong(min) && Limits.fitsLongLong(max)) { baseT = NumberT.LONG_LONG; } else if (Limits.fitsUnsignedLongLong(min) && Limits.fitsUnsignedLongLong(max)) { baseT = NumberT.U_LONG_LONG; } else { runtime.error("enumeration values exceed range of largest integer", n); baseT = ErrorT.TYPE; } // Done. ((EnumT)type).setMembers(types); ((EnumT)type).setType(baseT); type.setLocation(n); for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } for (Attribute a : toAttributeList(n.getGeneric(3))) { type.addAttribute(a); } type.seal(); table.current().define(name, type); // Mark the node. mark(n, type); } } /** Process the enumeration type reference. */ public void visitEnumerationTypeReference(GNode n) { if (hasType()) { multipleTypes(); } else { final String tag = n.getString(1); final String name = SymbolTable.toTagName(tag); if (table.isDefined(name)) { final Type t = (Type)table.lookup(name); if (! t.isEnum()) { runtime.error("'" + tag + "' defined as wrong kind of tag", n); reportPreviousTag(t); type = ErrorT.TYPE; return; } else { type = t; // If the enum has not yet been defined, update the // location and GCC attributes. if (null == type.toEnum().getMembers()) { type.setLocation(n); for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } } } } else { checkNotParameter(n, "enum"); type = new EnumT(tag).locate(n); for (Attribute a : toAttributeList(n.getGeneric(0))) { type.addAttribute(a); } table.current().define(name, type); } // Mark the node. mark(n, type); } } /** Process the void type specifier. */ public void visitVoidTypeSpecifier(GNode n) { if (hasType()) { multipleTypes(); } else { type = VoidT.TYPE; } } /** Process the variable argument list specifier. */ public void visitVarArgListSpecifier(GNode n) { if (hasType()) { multipleTypes(); } else { type = InternalT.VA_LIST; } } /** Process the typedef name. */ public void visitTypedefName(GNode n) { if (hasType()) { multipleTypes(); } else { final String name = n.getString(0); final Type value = (Type)table.current().lookup(name); if ((null != value) && value.isAlias()) { type = value; } else { runtime.error("typedef name '" + name + "' undefined", n); type = ErrorT.TYPE; } } } /** Process the attribute specifier. */ public void visitAttributeSpecifier(GNode n) { final List<Attribute> atts = toAttributeList(n); if (! atts.isEmpty()) { if (null == attributes) { attributes = atts; } else { attributes.addAll(atts); } } } } // ======================================================================== /** The saved state of the initializer. */ static class State { public Type base; public Type element; public boolean top; public long index; public long size; public State(Type base, Type element, boolean top, long index, long size) { this.base = base; this.element = element; this.top = top; this.index = index; this.size = size; } } /** The semantic information contained in an initializer. */ public class Initializer { /** The debug level. */ private static final int DEBUG = 0; /** The overall initializer list. */ private GNode node; /** The overall type. */ private Type type; /** The flag for whether the type has automatic storage. */ private boolean auto; /** The current base type. */ private Type base; /** The current element type. */ private Type element; /** The flag for whether the initializer is top-level. */ private boolean top; /** The current index into aggregate types. */ private long index; /** The size of aggregate types. */ private long size; /** The count of a top-level array's elements. */ private long count; /** The stack of processing states. */ private List<State> states; /** * Create a new initializer. The specified node must represent an * initializer list. * * @param node The node. * @param type The type. * @param auto The flag for automatic storage. */ public Initializer(GNode node, Type type, boolean auto) { this.node = node; this.type = type; this.auto = auto; this.base = type.resolve(); switch (this.base.tag()) { case ARRAY: this.element = base.toArray().getType().resolve(); this.top = true; this.size = getSize(base); break; case STRUCT: case UNION: this.element = null; this.top = false; this.size = getSize(base); break; default: this.element = base; this.top = true; this.size = 1; } this.index = -1; this.count = 0; states = new ArrayList<State>(); } /** * Create a new nested initializer. The specified node must * represent an initializer list. Both types must have been * resolved. The element type may be <code>null</code>. * * @param node The node. * @param base The base type. * @param element The element type. * @param auto The flag for automatic storage. */ Initializer(GNode node, Type base, Type element, boolean auto) { this.node = node; this.type = base; this.auto = auto; this.base = base; this.element = element; this.top = false; this.size = (base == element) ? 1 : getSize(base); this.index = -1; this.count = 0; states = new ArrayList<State>(); } /** * Process the initializer. This method processes this * initializer, reporting any errors and returning the processed * type. The processed type generally is the same as the type * provided to this class' constructor. However, if this * intializer's type is a top-level incomplete array, it is * updated with the actual size. * * @return The processed type. */ public Type process() { // DEBUG: Open brace. if (1 <= DEBUG) { runtime.console().pln('{').incr().flush(); } // Iterate over the initializer list entries. final int num = node.size(); for (int cursor = 0; cursor < num; cursor++) { // Get the entry and its children. final GNode entry = node.getGeneric(cursor); final GNode designation = entry.getGeneric(0); final GNode initializer = entry.getGeneric(1); // Process the designation. if (! designation(designation)) { // DEBUG: Close brace. if (1 <= DEBUG) runtime.console().decr().indent().pln("};").flush(); return getResult(); } // Process the intializer. if (initializer.hasName("InitializerList")) { // DEBUG: Print the designation. if (1 <= DEBUG) { runtime.console().indent().p(toString()).p(" = ").flush(); } switch (element.tag()) { case BOOLEAN: case INTEGER: case FLOAT: case POINTER: { runtime.warning("braces around scalar initializer", initializer); new Initializer(initializer, element, element, auto).process(); } break; case ARRAY: { new Initializer(initializer, element, element.toArray().getType(). resolve(), auto).process(); } break; default: new Initializer(initializer, element, null, auto).process(); } } else { // Determine the right hand type. final Type right; if (runtime.test("optionMarkAST") && initializer.getBooleanProperty(MARKED)) { right = (Type)initializer.getProperty(Constants.TYPE); } else { right = processExpression(initializer); } // Top-level initializer may only contain constant. if ((! auto) && (! right.hasConstant()) && (! c().hasConstRef(right))) { runtime.error("initializer element is not constant", initializer); } // Try to initialize the left-hand type with the right-hand // type. loop: while (true) { if (isInitializable(element, right)) { // DEBUG: Print the assignment. if (1 <= DEBUG) { runtime.console().indent().p(toString()).p(" = "); if (right.hasConstant()) { runtime.console().p(right.getConstant().getValue().toString()); } else if (c().hasConstRef(right)) { runtime.console().p(c().getConstRef(right).toString()); } else { runtime.console().p("<val>"); } runtime.console().pln(';').flush(); } // Process the assignment and string size for any warnings. processAssignment(true,"initializer",initializer,element,right); processStringSize(initializer,"initializer",true,element,right); break; } switch (element.tag()) { case ARRAY: { // Initialize the array's elements. push(element); } break; case STRUCT: case UNION: { if (0 == element.toTagged().getMemberCount()) { // Continue with the next subobject. if (! designation(null)) { // DEBUG: Close brace. if (1 <= DEBUG) { runtime.console().decr().indent().pln("};").flush(); } return getResult(); } } else { // Initialize the struct/union members. push(element); } } break; default: // The assignment fails. processAssignment(true,"initializer",initializer,element,right); break loop; } } } } // Check for empty scalar initializers. if ((0 == num) && c().isScalar(type)) { runtime.error("empty scalar initializer", node); } // DEBUG: Close brace. if (1 <= DEBUG) runtime.console().decr().indent().pln("};").flush(); return getResult(); } /** * Process the specified designation. This method updates the * internal state to reflect the designation. * * @param designation The designation, which may be * <code>null</code>. * @return <code>false</code> if the designation contains an * error. */ private boolean designation(GNode designation) { if (null == designation) { while (true) { index++; if ((-1 == size) || (index < size)) { // We have an object to initialize. if (base.hasStructOrUnion()) { // Set the element type for struct/union types. element = base.toTagged().getMember((int)index).resolve(); } else if ((! hasSize(type)) && (0 == states.size()) && top) { // We are processing an initializer list that is not // nested and has an incomplete array as its type. // Therefore, we record the maximum size. count = Math.max(count, index+1); } // Done. return true; } else { // We are done with the base type. if (0 == states.size()) { // There are no more objects to initialize. Get the // first excess element. final GNode excess = node.getGeneric((int)index); // Report excess elements first. if (pedantic) { runtime.error("excess elements in " + c().toDesignation(base) + " initializer", excess); } else { runtime.warning("excess elements in " + c().toDesignation(base) + " initializer", excess); } // Report any extra brace groups. for (int i=(int)index; i<node.size(); i++) { final GNode el = node.getGeneric(i); if (el.getGeneric(1).hasName("InitializerList")) { runtime.error("extra brace group at end of initializer", el); } } // Done. return false; } else { // Continue with the encapsulating type. pop(); } } } } else { // Clear the saved states. We are starting with the overall // type. if (0 != states.size()) { final State state = states.get(0); base = state.base; element = state.element; top = state.top; index = state.index; size = state.size; states.clear(); } if (base.isArray()) push(base); // Process the designators. final Iterator<Object> iter; if (designation.hasName("Designation")) { iter = designation.iterator(); } else { iter = new SingletonIterator<Object>(designation); } while (iter.hasNext()) { final GNode designator = GNode.cast(iter.next()); if (designator.hasName("ObsoleteFieldDesignation") || ".".equals(designator.getString(0))) { // A struct/union field. if (! base.hasStructOrUnion()) { runtime.error("field name not in struct or union initializer", designator); return false; } // Extract the field name. String name = designator.hasName("ObsoleteFieldDesignation") ? designator.getString(0) : designator.getGeneric(1).getString(0); // Find the field. if (! lookup(name)) { runtime.error("unknown field '" + name + "' in initializer", designator); return false; } } else { // An array index. Make sure the base type is an array. if (! base.isArray()) { runtime.error("array index in non-array initializer", designator); return false; } // Determine the index types. final Type t1 = processExpression(designator.getNode(1)); final Type t2 = (3 == designator.size()) ? processExpression(designator.getNode(2)) : null; // Make sure that the indices are constant integers. if ((! c().isIntegral(t1)) || ((null != t2) && (! c().isIntegral(t2)))) { runtime.error("array index in initializer not of integer type", designator); return false; } else if ((! t1.hasConstant()) || ((null != t2) && (! t2.hasConstant()))) { runtime.error("nonconstant array index in initializer", designator); return false; } // Make sure that the indices are neither too small nor // too large and that the range is not empty. final BigInteger i1 = t1.getConstant().bigIntValue(); final BigInteger i2 = (null == t2) ? null : t2.getConstant().bigIntValue(); // Test: i1 < 0, i2 < 0 if ((i1.compareTo(BigInteger.ZERO) < 0) || ((null != i2) && (i2.compareTo(BigInteger.ZERO) < 0))) { runtime.error("negative array index in initializer", designator); return false; // Test: i1 > ARRAY_MAX, i2 > ARRAY_MAX } else if ((i1.compareTo(Limits.ARRAY_MAX) > 0) || ((null != i2) && (i2.compareTo(Limits.ARRAY_MAX) > 0))) { runtime.error("array index in initializer is too large", designator); return false; // Test: i2 < i1 } else if ((null != i2) && (i2.compareTo(i1) < 0)) { runtime.error("empty index range in initializer", designator); return false; } // Make sure that the array index is within the array // bounds. final long max = (null == i2) ? i1.longValue() : i2.longValue(); if ((-1 < size) && (max >= size)) { runtime.error("array index in initializer exceeds array bounds", designator); return false; } if ((! hasSize(type)) && (0 == states.size()) && top) { // We are processing an initializer list that is not // nested and has an incomplete array as its type. // Therefore, we record the maximum size. count = Math.max(count, max+1); } // Update the current index. index = max; } // Prepare for the next designator. if (iter.hasNext()) push(element); } // Done. return true; } } /** * Look up the specified field. The current base type must be a * struct or union type. * * @return <code>true</code> if the field was found. */ private boolean lookup(String name) { index = -1; for (VariableT member : base.toStructOrUnion().getMembers()) { if (member.hasName()) { index++; if (member.hasName(name)) { element = member.resolve(); return true; } } else if (! member.hasWidth()) { index++; element = member.resolve(); push(element); if (lookup(name)) return true; pop(); } } return false; } /** * Make the specified type the current type. The type must have * been resolved. * * @param type The type. */ private void push(Type type) { // DEBUG: Print pushed type. if (2 <= DEBUG) { runtime.console().indent().p("push "); type.trace(runtime); runtime.console().flush(); } State state = new State(base, element, top, index, size); states.add(state); base = type; switch (type.tag()) { case ARRAY: element = type.toArray().getType().resolve(); break; case STRUCT: case UNION: element = type.toTagged().getMember(0).resolve(); break; default: element = type; } top = false; index = 0; size = getSize(type); } /** Restore the previous type and its processing state. */ private void pop() { // DEBUG: Pop. if (2 <= DEBUG) runtime.console().indent().pln("pop").flush(); assert 0 != states.size() : "Empty initializer type stack"; final State state = states.remove(states.size()-1); base = state.base; element = state.element; top = state.top; index = state.index; size = state.size; } /** * Get the result type. If this initializer list is not nested * and has an incomplete array as its type, this method patches * the array's size. Otherwise, it returns the overall type. * * @return The result type. */ private Type getResult() { if ((! hasSize(type)) && (((0 == states.size()) && top) || ((0 < states.size()) && states.get(0).top))) { type = type.copy(); type.resolve().toArray().setLength(count); } return type; } /** * Convert this initializer as a string. This method returns a * string representing the array/struct/union designation of the * current initializer state. * * @return This initializer as a string. */ public String toString() { final StringBuilder buf = new StringBuilder(); for (State state : states) { if (state.base != state.element) { if (state.base.isArray()) { buf.append('['); buf.append(state.index); buf.append(']'); } else if (state.base.hasStructOrUnion()) { final VariableT m = state.base.toTagged(). getMember((int)state.index).toVariable(); if (m.hasName()) { buf.append('.'); buf.append(m.getName()); } else { buf.append(".<anon>"); } } } } if ((base != element) && (-1 != index)) { if (base.isArray()) { buf.append('['); buf.append(index); buf.append(']'); } else if (base.hasStructOrUnion()) { final VariableT m = base.toTagged(). getMember((int)index).toVariable(); if (m.hasName()) { buf.append('.'); buf.append(m.getName()); } else { buf.append(".<anon>"); } } } // Cover the base case. if ((base == element) && (0 == states.size())) buf.append("<obj>"); return buf.toString(); } /** * Determine whether the specified type has a size. Only arrays * without a length do not have a size. This method is * effectively static. * * @param type The type. * @return <code>true</code> if the type has a size. */ private boolean hasSize(Type type) { return (! type.hasTag(Tag.ARRAY)) || type.resolve().toArray().hasLength(); } /** * Determine the size of the specified type. For scalars, this * method returns 1. For unions, this method returns 1 if the * union has any accessible members and 0 otherwise. For structs, * this method returns the number of accessible members. For * arrays, it returns the size if known and -1 otherwise. This * method is effectively static. * * @param type The type. * @return The type's size. */ private long getSize(Type type) { switch (type.tag()) { case ARRAY: return type.toArray().getLength(); case STRUCT: return type.toTagged().getMemberCount(); case UNION: return 0 < type.toTagged().getMemberCount() ? 1 : 0; default: return 1; } } } // ======================================================================== /** The state for checking incomplete types. */ public static class CompletenessCheck { /** The type. */ public Type type; /** The identifier. */ public String name; /** The node. */ public Node node; /** * Create a new completeness check. * * @param type The type. * @param name The name. * @param node The node. */ public CompletenessCheck(Type type, String name, Node node) { this.type = type; this.name = name; this.node = node; } } // ======================================================================== /** * The name of the node property indicating that a node has already * been marked with its type during initializer processing. */ protected static final String MARKED = "marked"; /** The relative name of the scope for external declarations. */ protected static final String EXTERN_SCOPE = "extern"; /** The relative name of the temporary scope for parameter declarations. */ protected static final String TMP_SCOPE = "tmp"; /** The fully qualified name of the scope for external declarations. */ protected static final String EXTERN_PATH = Constants.QUALIFIER + EXTERN_SCOPE; /** The common type operations for C. */ protected final C cops; /** The runtime. */ protected final Runtime runtime; /** The flag for pedantic mode. */ protected final boolean pedantic; /** The symbol table. */ protected SymbolTable table; /** * The flag for whether the current declaration is top-level, that * is, an external declaration. */ protected boolean isTopLevel; /** * The flag for whether the immediately nested compound statement * has a scope. */ protected boolean hasScope; /** * The list of loop statement flags. For each nested loop * statement, this list contains exactly one boolean. */ protected List<Boolean> loops; /** * The list of switch statement flags. For each nested switch * statement, this list contains exactly one boolean, which is * <code>true</code> if a default label has been seen. */ protected List<Boolean> switches; /** * The flag for a statement as expression node. This flag * is <code>true</code> if the current compound statement * is nested within a statement as expression node. */ protected boolean isStmtAsExpr; /** * The list of completeness checks to perform at the end of a * translation unit. */ protected List<CompletenessCheck> checks; /** * Create a new C analyzer. The newly created analyzer uses {@link * C} for common type operations. * * @param runtime The runtime. */ public CAnalyzer(Runtime runtime) { this(new C(), runtime); } /** * Create a new C analyzer. * * @param cops The common type operations for C. * @param runtime The runtime. */ public CAnalyzer(C cops, Runtime runtime) { this.cops = cops; this.runtime = runtime; pedantic = runtime.test("optionPedantic"); loops = new ArrayList<Boolean>(); switches = new ArrayList<Boolean>(); checks = new ArrayList<CompletenessCheck>(); } /** * Analyze the specified translation unit. * * @param unit The translation unit. * @return The corresponding symbol table. */ public SymbolTable analyze(Node unit) { return analyze(unit, new SymbolTable()); } /** * Process the specified translation unit. * * @param unit The translation unit. * @param table The symbol table. * @return The symbol table. */ public SymbolTable analyze(Node unit, SymbolTable table) { this.table = table; isTopLevel = true; isStmtAsExpr = false; loops.clear(); switches.clear(); checks.clear(); dispatch(unit); return table; } /** * Get this analyzer's common type operations for the C language. * * @return The common type operations. */ public C c() { return cops; } /** Visit the specified translation unit. */ public void visitTranslationUnit(GNode n) { for (Object o : n) dispatch((Node)o); // Process any tagged checks. for (CompletenessCheck check : checks) { if (c().isIncomplete(check.type)) { runtime.error("storage size of '" + check.name + "' isn't known", check.node); } } } /** Visit the specified declaration. */ public void visitDeclaration(GNode n) { final GNode specifiers = n.getGeneric(1); final Specifiers spec = newSpecifiers(specifiers, null == n.get(2)); final GNode initDeclList = n.getGeneric(2); if (null == initDeclList) { // ---------------------------------------------------------------------- // Empty declarations // ---------------------------------------------------------------------- // Check for useless attributes. if (spec.contains(Constants.ATT_INLINE)) { runtime.error("'inline' in empty declaration", n); } if (null != spec.getStorageClass()) { runtime.warning("useless storage class specifier in empty declaration", n); } if (spec.contains(Constants.ATT_VOLATILE) || spec.contains(Constants.ATT_CONSTANT) || spec.contains(Constants.ATT_RESTRICT)) { runtime.warning("useless type qualifier in empty declaration", n); } // Make sure the type is tagged. if ((! spec.getBaseType().hasError()) && (! spec.getBaseType().hasTagged())) { runtime.warning("empty declaration", n); } } else { // Iterate over the initialized declarator list. for (Object o : initDeclList) { final GNode initDecl = GNode.cast(o); final GNode declarator = initDecl.getGeneric(1); final GNode identifier = getDeclaredId(declarator); final String name = identifier.getString(0); Type type = getDeclaredType(spec.getBaseType(), declarator); final GNode initializer = initDecl.getGeneric(4); dispatch(initDecl.getGeneric(2)); // Process assembly expression. if (spec.contains(Constants.ATT_STORAGE_TYPEDEF)) { // ------------------------------------------------------------------ // Typedef declarations // ------------------------------------------------------------------ // Check for invalid attributes. if (spec.contains(Constants.ATT_INLINE)) { runtime.error("typedef '"+name+"' is declared 'inline'", getSpecifier("FunctionSpecifier", specifiers)); } if (spec.contains(Constants.ATT_THREAD_LOCAL)) { runtime.error("'__thread' used with 'typedef'", initDecl); } // Check for initializers. if (null != initializer) { runtime.error("typedef '" + name + "' is initialized", initDecl); } // Check that the type is well-formed. checkType(initDecl, name, type); // Check for previous definitions. if (table.current().isDefinedLocally(name)) { Type previous = (Type)table.current().lookupLocally(name); if (previous.isAlias()) { runtime.error("redefinition of typedef '" + name + "'", initDecl); } else { runtime.error("'"+name+"' redeclared as different kind of symbol", initDecl); } reportPrevious(name, previous); } else { table.current(). define(name, new AliasT(name, type).locate(n).seal()); } } else { // ------------------------------------------------------------------ // Object declarations/definitions // ------------------------------------------------------------------ // The flags for whether to enter the definition into the // symbol table at the local, at the global, and the extern // level, respectively. boolean define = true; boolean defineGlobal = false; boolean defineExtern = false; // Annotate the type. type = spec.annotateFull(type.annotate()); type = type.attribute(toAttributeList(initDecl.getGeneric(0))); type = type.attribute(toAttributeList(initDecl.getGeneric(3))); // Check that the type is well-formed. checkType(initDecl, name, type); Type resolved = type.resolve(); // Check function types for invalid storage class, // parameters, and initializer. if (resolved.isFunction()) { FunctionT function = resolved.toFunction(); if (type.hasAttribute(Constants.ATT_STORAGE_AUTO) || type.hasAttribute(Constants.ATT_STORAGE_REGISTER) || type.hasAttribute(Constants.ATT_THREAD_LOCAL)) { runtime.error("invalid storage class for function '" + name + "'", initDecl); } if (type.hasAttribute(Constants.ATT_STYLE_OLD) && (! function.getParameters().isEmpty())) { runtime.warning("parameter names (without types) in function " + "declaration", initDecl); function.getParameters().clear(); } if (null != initializer) { runtime.error("function '" + name + "' is initialized like a " + "variable", initDecl); } } else { // Check non-function types for inline specifier. if (type.hasAttribute(Constants.ATT_INLINE)) { runtime.warning("variable '" + name + "' declared 'inline'", getSpecifier("FunctionSpecifier", specifiers)); type.removeAttribute(Constants.ATT_INLINE); } // Check non-function types for thread-local specifier. if (type.hasAttribute(Constants.ATT_THREAD_LOCAL)) { if (type.hasAttribute(Constants.ATT_STORAGE_AUTO)) { runtime.error("'__thread' used with 'auto'", specifiers); } else if (type.hasAttribute(Constants.ATT_STORAGE_REGISTER)) { runtime.error("'__thread' used with 'register'", specifiers); } else if (! type.hasAttribute(Constants.NAME_STORAGE) && ! isTopLevel) { runtime.error("function-scope '" + name + "' implicitly auto " + "and declared '__thread'", initDecl); } else if (! c().hasThreadLocals()) { runtime.error("thread-local storage not supported for this " + "target", initDecl); } } } // Check for compatibility with prevous declarations. if (isTopLevel) { // An external declaration/definition. // Check for invalid storage class specifiers. if (! resolved.isFunction()) { if (type.hasAttribute(Constants.ATT_STORAGE_AUTO)) { runtime.error("file-scope declaration of '" + name + "' specifies 'auto'", getSpecifier("AutoSpecifier", specifiers)); } else if (type.hasAttribute(Constants.ATT_STORAGE_REGISTER)) { // As a GCC extension, the register specifier may // appear in global register variable declarations. // Such a declaration, however, requires a simple // assembly expression. if (pedantic || null == initDecl.get(2)) { runtime.error("file-scope declaration of '" + name + "' specifies 'register'", getSpecifier("RegisterSpecifier", specifiers)); } } } final Type extern = lookupExtern(name); final boolean global = table.current().isDefinedLocally(name); if (null != extern || global) { final Type previous = global ? (Type)table.current().lookupLocally(name) : extern; // Check for same symbol kind and type compatibility. Type composite = compose(initDecl, name, type, previous, false); // Check for redefinitions and storage class compatibility. if (composite.isError()) { define = (null != extern); } else if ((null != initializer) && previous.hasAttribute(Constants.ATT_DEFINED)) { runtime.error("redefinition of '" + name + "'", initDecl); reportPrevious(name, previous); define = (null != extern); } else if ((! resolved.isFunction()) && previous.hasAttribute(Constants.ATT_STORAGE_STATIC) && (! type.hasAttribute(Constants.ATT_STORAGE_STATIC)) && (! type.hasAttribute(Constants.ATT_STORAGE_EXTERN))) { runtime.error("non-static declaration of '" + name + "' follows static declaration", initDecl); reportPrevious(name, previous); define = (null != extern); } else if ((! previous.hasAttribute(Constants.ATT_MACRO)) && (! previous.hasAttribute(Constants.ATT_STORAGE_STATIC)) && type.hasAttribute(Constants.ATT_STORAGE_STATIC)) { runtime.error("static declaration of '" + name + "' follows non-static declaration", initDecl); reportPrevious(name, previous); define = (null != extern); } else if (previous.hasAttribute(Constants.ATT_MACRO) || previous.hasAttribute(Constants.ATT_DEFINED)) { // There's nothing to add. define = (null != extern); } else { // We have a winner. composite = composite.annotate(); // C99 6.7.4: inline only needs to appear // once. Similarly for static. if (resolved.isFunction()) { if ((previous.hasAttribute(Constants.ATT_INLINE) || type.hasAttribute(Constants.ATT_INLINE)) && (! composite.hasAttribute(Constants.ATT_INLINE))) { composite = composite.attribute(Constants.ATT_INLINE); } if ((previous.hasAttribute(Constants.ATT_STORAGE_STATIC) || type.hasAttribute(Constants.ATT_STORAGE_STATIC)) && (! composite.hasAttribute(Constants.ATT_STORAGE_STATIC))) { composite = composite. attribute(Constants.ATT_STORAGE_STATIC); } } // Preserve the lvalue. if (! composite.hasShape()) { composite = composite.shape(previous.getShape()); } // Update the location. composite = composite.locate(n); // Continue with the composite type. type = composite; resolved = type.resolve(); } } else { // The name has not been declared before. // Create an lvalue. type = type.shape(true, name); // Remember the location. type = type.locate(n); } } else { // A block-level declaration/definition. if (type.hasAttribute(Constants.ATT_STORAGE_EXTERN) || resolved.isFunction()) { // Extern and function declarations at the block-level // are effectively external declarations. // Process the function type's storage class. if (resolved.isFunction()) { if (type.hasAttribute(Constants.ATT_STORAGE_AUTO) || type.hasAttribute(Constants.ATT_STORAGE_REGISTER) || type.hasAttribute(Constants.ATT_STORAGE_STATIC) || type.hasAttribute(Constants.ATT_THREAD_LOCAL)) { // C99 6.7.1-5 runtime.error("invalid storage class for function '" + name + "'", initDecl); type. removeAttribute(type.getAttribute(Constants.NAME_STORAGE)); } // C99 6.2.2 type = type.attribute(Constants.ATT_STORAGE_EXTERN); } final Type local = (Type)table.current().lookupLocally(name); final Type global = (Type)table.root().lookupLocally(name); final Type extern = lookupExtern(name); if (null != local || null != global || null != extern) { final Type previous; if (null != local) { previous = local; } else if (null != global) { previous = global; } else { previous = extern; } Type composite = compose(initDecl, name, type, previous, false); if (composite.isError()) { define = false; } else if (null != local && ! previous.resolve().isFunction() && ! previous. hasAttribute(Constants.ATT_STORAGE_EXTERN)) { runtime.error("extern declaration of '" + name + "' follows " + "declaration with no linkage", initDecl); reportPrevious(name, previous); define = false; } else if (previous.hasAttribute(Constants.ATT_STORAGE_STATIC) && previous != table.current().getParent().lookup(name)) { // C99 6.2.2-4: If the previous declaration has // internal linkage (i.e., is declared as static) // and is not visible in the surrounding scope, then // this declaration tries to change to external // linkage, which is a program error. // Aside: It is extremely clever language design // practice to make a declaration's properties // dependent on an identifier of the same name in // the surrounding scope. runtime.error("variable previously declared 'static' " + "redeclared 'extern'", initDecl); reportPrevious(name, previous); } else if (previous.hasAttribute(Constants.ATT_MACRO) || previous.hasAttribute(Constants.ATT_DEFINED)) { // There's nothing to add. type = previous; resolved = type.resolve(); define = (null == local); } else { // We have a winner. composite = composite.annotate(); // Preserve inline and static attributes for functions. if (resolved.isFunction()) { if ((previous.hasAttribute(Constants.ATT_INLINE) || type.hasAttribute(Constants.ATT_INLINE)) && (! composite.hasAttribute(Constants.ATT_INLINE))) { composite = composite.attribute(Constants.ATT_INLINE); } if ((previous.hasAttribute(Constants.ATT_STORAGE_STATIC) || type.hasAttribute(Constants.ATT_STORAGE_STATIC)) && (! composite. hasAttribute(Constants.ATT_STORAGE_STATIC))) { composite = composite. attribute(Constants.ATT_STORAGE_STATIC); } } // Update the lvalue. if (! composite.hasShape()) { composite = composite.shape(previous.getShape()); } // Update the locaton. composite = composite.locate(n); // Continue with the composite type. type = composite; resolved = type.resolve(); defineGlobal = (null != global); defineExtern = (null != extern); } } else { // The name has not been declared before. // Create an lvalue. type = type.shape(true, name); // Remember the location. type = type.locate(n); // Enter into extern scope. defineExtern = true; } } else if (table.current().isDefinedLocally(name)) { // Non-extern redeclarations at the block-level are // always errors. We just play along to determine what // kind of error. final Type previous = (Type)table.current().lookupLocally(name); final Type composite = compose(initDecl,name,type,previous,false); if (composite.isError()) { define = false; } else if (previous.hasAttribute(Constants.ATT_STORAGE_EXTERN)) { runtime.error("declaration of '" + name + "' with no " + "linkage follows extern declaration", initDecl); reportPrevious(name, previous); define = false; } else { runtime.error("redeclaration of '" + name + "' with no linkage", initDecl); reportPrevious(name, previous); define = false; } } else { // The name has not been declared before. // Add the storage class if missing. if (! type.hasAttribute(Constants.NAME_STORAGE)) { type = type.attribute(Constants.ATT_STORAGE_AUTO); } // Create an lvalue. type = type. shape(type.hasAttribute(Constants.ATT_STORAGE_STATIC), name); // Remember the location. type = type.locate(n); } } // Check for incomplete types. boolean incomplete = false; if ((! type.hasAttribute(Constants.ATT_STORAGE_EXTERN)) || (null != initializer)) { if (resolved.isArray()) { final Type element = resolved.toArray().getType(); if (c().isIncomplete(element) || (pedantic && c().hasTrailingArray(element))) { runtime.error("array type has incomplete element type", initDecl); incomplete = true; } } else if (c().isIncomplete(type)) { if (null == initializer) { // Per C99 6.9.2-2, as a special case, top-level // tagged types without initializers are logically // moved to the end of the translation unit and // automatically initialized to zero. We simply redo // the completeness check at the end of the // translation unit. if (isTopLevel && type.hasTagged() && (null == type.toTagged().getMembers())) { checks.add(new CompletenessCheck(type, name, initDecl)); } else if (resolved.isVoid()) { runtime.error("variable '" + name + "' declared void", initDecl); } else { runtime.error("storage size of '" + name + "' isn't known", initDecl); } } else { runtime.error("variable '" + name + "' has initializer but " + "incomplete type", initDecl); } incomplete = true; } } // Process the initializer. if ((null != initializer) && (! resolved.isFunction())) { if (type.hasAttribute(Constants.ATT_STORAGE_EXTERN)) { if (isTopLevel) { runtime.warning("'"+name+"' initialized and declared 'extern'", initDecl); type = type.attribute(Constants.ATT_DEFINED); } else { runtime.error("'"+name+"' has both 'extern' and initializer", initDecl); } } else { type = type.attribute(Constants.ATT_DEFINED); } if (! incomplete) { // C99 6.2.1-4: An identifiers scope starts with its // declarator. Consequently, we "pre-define" the // identifier before processing the initializer. if (define) { table.current().define(name, type); } // Do the actual processing. type = processInitializer(initDecl, name, type, initializer); } } // (Re)define the name and type. if (define) { table.current().define(name, type); } if (defineGlobal) { table.root().define(name, type); } if (defineExtern) { defineExtern(name, type); } } } } } /** * Process the specified initializer. This method checks that the * specified initializer is consistent with the specified type. It * also completes the type based on the literal. * * @param expr The overall expression node. * @param name The name of the variable being initialized, which may * be <code>null</code>. * @param left The left-hand type. * @param literal The literal node. */ protected Type processInitializer(GNode expr, String name, Type left, GNode literal) { if (left.hasError()) return left; // Fix the description for error reporting. name = (null == name) ? "initializer" : "initializer for '" + name + "'"; // Determine whether the left-hand size has automatic storage. final boolean auto = (left.hasAttribute(Constants.ATT_STORAGE_AUTO) || left.hasAttribute(Constants.ATT_STORAGE_REGISTER)); // Check the actual initializer. if (literal.hasName("InitializerList")) { // Take care of braced string literals here. if ((c().isString(left) || c().isWideString(left)) && (0 < literal.size()) && (null == literal.getGeneric(0).get(0)) && (! literal.getGeneric(0).getGeneric(1).hasName("InitializerList"))) { final Type right = (Type)dispatch(literal.getGeneric(0).getGeneric(1)); if (c().isStringLiteral(right)) { if (1 < literal.size()) { if (c().isString(left)) { runtime.error("excess elements in char array initializer", literal); } else { runtime.error("excess elements in wchar_t array initializer", literal); } return left; } else { processAssignment(true, name, literal, left, right); return processStringSize(literal, name, false, left, right); } } // Unmark the already processed node again b/c it will be // reprocessed below. if (runtime.test("optionMarkAST")) { literal.getGeneric(0).getGeneric(1).setProperty(MARKED, Boolean.TRUE); } } // Otherwise, just process the initializer list. return new Initializer(literal, left, auto).process(); } else { final Type right = (Type)dispatch(literal); // A non-automatic initializer must be constant. if ((! auto) && (! right.hasConstant()) && (! c().hasConstRef(right))) { runtime.error(name + " is not constant", expr); } // Are the two types compatible? processAssignment(true, name, literal, left, right); return processStringSize(literal, name, false, left, right); } } /** * Process the string sizes for the specified types. If the * specified types are not compatible string types or the right-hand * type does no represent a string constant, this method simply * returns the specified left-hand type. Otherwise, if the * left-hand type is an incomplete array and the expression is not * nested, this method updates a copy of the left-hand type and * returns that copy. If the left-hand type is a complete array and * the right-hand type is longer, it emits a warning. * * @param expr The overall expression node. * @param desc The description. * @param nested The flag for nested initializers. * @param left The left-hand type. * @param right The right-hand type. * @return The updated type. */ protected Type processStringSize(GNode expr, String desc, boolean nested, Type left, Type right) { if (((c().isString(left) && c().isString(right)) || (c().isWideString(left) && c().isWideString(right))) && right.hasConstant()) { final ArrayT array = left.resolve().toArray(); if ((! array.isVarLength()) && (! array.hasLength()) && (! nested)) { left = left.copy(); left.resolve().toArray(). setLength(right.resolve().toArray().getLength()); } else if (array.hasLength() && (array.getLength() < right.resolve().toArray().getLength())) { runtime.warning("string literal in " + desc + " is too long", expr); } } return left; } /** Visit the specified function definition. */ public void visitFunctionDefinition(GNode n) { final GNode specifiers = n.getGeneric(1); final GNode declarator = n.getGeneric(2); final GNode identifier = getDeclaredId(declarator); final String name = identifier.getString(0); final Specifiers spec = newSpecifiers(specifiers, false); Type type = getDeclaredType(spec.getBaseType(),declarator); type = spec.annotateFull(type); // Make sure the type actually is a function. if (! type.resolve().isFunction()) { runtime.error("function definition without function declarator", declarator); return; } // Check the storage class. if (type.hasAttribute(Constants.ATT_STORAGE_AUTO)) { runtime.error("function definition declared 'auto'", n); } else if (type.hasAttribute(Constants.ATT_STORAGE_REGISTER)) { runtime.error("function definition declared 'register'", n); } else if (type.hasAttribute(Constants.ATT_STORAGE_TYPEDEF)) { runtime.error("function definition declared 'typedef'", n); } else if (type.hasAttribute(Constants.ATT_THREAD_LOCAL)) { runtime.error("function definition declared '__thread'", n); } // Check the function's result type. checkType(n, name, type); // The flag for a GCC inline extern function, i.e., macro. final boolean isMacro = (! pedantic && type.hasAttribute(Constants.ATT_STORAGE_EXTERN) && type.hasAttribute(Constants.ATT_INLINE)); // The flag for whether to enter the definition into the symbol table. boolean define = true; // Check previous declarations/definitions. final Type extern = lookupExtern(name); final boolean global = table.current().isDefinedLocally(name); if (null != extern || global) { final Type previous = global ? (Type)table.current().lookupLocally(name) : extern; final Type composite = compose(n, name, type, previous, true); if (composite.isError()) { define = (null != extern); } else if (previous.hasAttribute(Constants.ATT_DEFINED) || (isMacro && previous.hasAttribute(Constants.ATT_MACRO))) { runtime.error("redefinition of '" + name + "'", n); reportPrevious(name, previous); define = (null != extern); } else if ((! previous.hasAttribute(Constants.ATT_MACRO)) && (! previous.hasAttribute(Constants.ATT_STORAGE_STATIC)) && type.hasAttribute(Constants.ATT_STORAGE_STATIC)) { runtime.error("static declaration of '" + name + "' follows non-static declaration", n); reportPrevious(name, previous); define = (null != extern); } else { // Continue with the composite type. type = composite; // Preserve inline and static attributes. if ((! previous.hasAttribute(Constants.ATT_MACRO)) && previous.hasAttribute(Constants.ATT_INLINE) && (! type.hasAttribute(Constants.ATT_INLINE))) { type = type.annotate().attribute(Constants.ATT_INLINE); } if (previous.hasAttribute(Constants.ATT_STORAGE_STATIC) && (! type.hasAttribute(Constants.ATT_STORAGE_STATIC))) { type = type.annotate().attribute(Constants.ATT_STORAGE_STATIC); } } type = type.annotate(); // Mark the type as defined. if (type.resolve().isSealed()) { type = isMacro ? type.attribute(Constants.ATT_MACRO) : type.attribute(Constants.ATT_DEFINED); } else if (isMacro) { type.resolve().addAttribute(Constants.ATT_MACRO); } else { type.resolve().addAttribute(Constants.ATT_DEFINED); } // Ensure the type is marked as an lvalue. if (! type.hasShape()) { type = type.shape(true, name); } // Update the type's location. type = type.locate(n); } else { // The function has not been declared before. Mark it's type as // a defined lvalue and remember the location. if (isMacro) { type.resolve().addAttribute(Constants.ATT_MACRO); } else { type.resolve().addAttribute(Constants.ATT_DEFINED); } type = type.annotate().shape(true, name).locate(n); } // Update the symbol table. if (define) { // Note that processParameters() below may modify the type's // parameters. table.current().define(name, type); } // Check that main's return type is int. if ("main".equals(name) && type.resolve().isFunction() && ! NumberT.INT.equals(type.resolve().toFunction().getResult())) { runtime.warning("return type of 'main' is not 'int'", n); } // Enter function scope. final String scopeName = isMacro ? SymbolTable.toMacroScopeName(name) : SymbolTable.toFunctionScopeName(name); table.enter(scopeName); table.mark(n); // C99 6.4.2.2: Declare the function name. table.current().define("__func__", toFuncType(name)); // Process the parameters. processParameters(n, type.resolve().toFunction()); // Process the body. final boolean top = isTopLevel; isTopLevel = false; final boolean scope = hasScope; hasScope = false; dispatch(n.getNode(4)); isTopLevel = top; hasScope = scope; table.exit(); // Check labels. if (isTopLevel) { checkUsedLabels(n); checkDefinedLabels(n); } } /** * Create <code>__func__</code>'s type for the function with the * specified name. * * @param name The function name. * @return The corresponding type. */ public static Type toFuncType(String name) { final Type elem = NumberT.CHAR.annotate(). attribute(Constants.ATT_CONSTANT); final Type array = new ArrayT(elem, name.length()).annotate(). attribute(Constants.ATT_STORAGE_STATIC).shape(true, "__func__").seal(); return array; } /** * Process the specified function's parameters. This method checks * that all parameters are named and have valid and complete types. * Furthermore, for old-style function definitions, it checks that * the parameter types are consistent with any previous new-style * declaration and, for previous old-style declarations, updates the * function's type with the parameter types. Finally, it adds the * appropriate definitions to the current symbol table scope. * * <p />Note that the specified type is expected to be the result of * composing the defined type with any previous declarations. For * old-style definitions, this type contains the parameter types of * any previous new-style declaration or is old-style as well, if * any previous declarations are also old-style. * * @param node The node representing the function. * @param function The function's type. */ protected void processParameters(GNode node, FunctionT function) { // Extract the parameter list. GNode parameters = getFunctionDeclarator(node.getGeneric(2)).getGeneric(1); if ((null == parameters) || parameters.hasName("IdentifierList")) { // An old-style definition: We first process any identifiers. final Set<String> names = new HashSet<String>(); if (null != parameters) { for (Object o : parameters) names.add((String)o); } // Next, we process any declarations. final GNode declarations = node.getGeneric(3); if (null != declarations) { for (Object o : declarations) { final GNode declaration = GNode.cast(o); final GNode specifiers = declaration.getGeneric(1); final Specifiers spec = newSpecifiers(specifiers, null==declaration.get(2)); if (null == declaration.get(2)) { runtime.warning("empty declaration", declaration); } else { for (Object o2 : declaration.getGeneric(2)) { final GNode initDecl = GNode.cast(o2); final GNode declarator = initDecl.getGeneric(1); final GNode identifier = getDeclaredId(declarator); final String name = identifier.getString(0); Type type = getDeclaredType(true, spec.getBaseType(), declarator); // Check that the type is well-formed. checkType(declaration, name, type); // Pointerize array and function parameter types. switch (type.tag()) { case ARRAY: type = c().qualify(new PointerT(type.resolve().toArray().getType()), type); break; case FUNCTION: type = c().qualify(new PointerT(type.resolve()), type); break; } // Annotate the type. type = spec.annotateFull(VariableT.newParam(type, name)). attribute(toAttributeList(initDecl.getGeneric(0))). attribute(toAttributeList(initDecl.getGeneric(3))). shape(false, name); // Check for storage class specifiers and initializers. if ((type.hasAttribute(Constants.NAME_STORAGE) && (! type.hasAttribute(Constants.ATT_STORAGE_REGISTER))) || type.hasAttribute(Constants.ATT_THREAD_LOCAL)) { runtime.error("storage class specified for parameter '"+name+ "'", declaration); } else if (! type.hasAttribute(Constants.NAME_STORAGE)) { type = type.attribute(Constants.ATT_STORAGE_AUTO); } if (null != initDecl.get(4)) { runtime.error("parameter '" + name + "' is initialized", initDecl.getNode(4)); } // Check for incomplete types. if (c().isIncomplete(type)) { if (type.resolve().isVoid()) { runtime.error("parameter '" + name + "' declared void", declaration); } else { runtime.error("parameter '"+ name + "' has incomplete type", declaration); } } // Check parameter name and previous definitions. if (! names.contains(name)) { runtime.error("declaration for parameter '" + name + "' but no such parameter", declaration); } else if (table.current().isDefinedLocally(name)) { runtime.error("redefinition of parameter '" + name + "'", declaration); } else { table.current().define(name, type); } } } } } // Next, we process any undeclared identifiers. for (String name : names) { if (! table.current().isDefinedLocally(name)) { final Type type = VariableT.newParam(C.IMPLICIT, name). attribute(Constants.ATT_STORAGE_AUTO).shape(false, name); table.current().define(name, type); if (pedantic) { runtime.warning("type of '" + name + "' defaults to 'int'", node); } } } // Finally, for old-style declarations, we patch the function // type and, for new-style declarations, we compare the // parameter types. if (function.hasAttribute(Constants.ATT_STYLE_OLD)) { // An old-style declaration (or none). List<Type> types = function.getParameters(); final int size = types.size(); for (int i=0; i<size; i++) { final Type t = types.get(i); // Old-style declarations have parameter types even if they // contain errors, since they always have a name. if (! t.hasError()) { final String name = t.toVariable().getName(); if (table.current().isDefinedLocally(name)) { types.set(i, (Type)table.current().lookupLocally(name)); } } } } else { // A new-style declaration. // Fix parameters. if (null == parameters) { parameters = GNode.create("IdentifierList", false); } if (parameters.size() != function.getParameters().size()) { runtime.error("number of arguments doesn't match prototype", node); } else { final int size = parameters.size(); for (int i=0; i<size; i++) { final Type t1 = (Type)table.current(). lookupLocally((String)parameters.get(i)); final Type t2 = function.getParameters().get(i); if (t1.hasError() || t2.hasError()) { // Ignore. } else if (c().compose(t2, c().promoteArgument(t1), pedantic). isError()) { runtime.error("argument '" + t1.toVariable().getName() + "' doesn't match prototype", node); } else if (pedantic && ! c().hasSameQualifiers(t2, t1)) { // C99 6.7.5.3 15 runtime.error("type qualifiers of argument '" + t1.toVariable().getName() + "' don't match prototype", node); } } } } } else { // A new-style definition. if (null != node.get(3)) { runtime.error("old-style parameter declarations in prototyped " + "function definition", node.getNode(3)); } // Note that void parameter lists are checked for // well-formedness by getParameterTypes(), which is invoked by // getDeclaredType(), which, in turn, is invoked by // visitFunctionDefinition(). if (! isVoidParameterTypeList(parameters)) { parameters = parameters.getGeneric(0); Iterator<Object> iter1 = parameters.iterator(); Iterator<Type> iter2 = function.getParameters().iterator(); while (iter1.hasNext()) { final GNode decl = GNode.cast(iter1.next()); final Type type = iter2.next(); final String name = type.hasVariable() ? type.toVariable().getName() : null; // Check for incomplete types. if (c().isIncomplete(type)) { if (null == name) { runtime.error("unnamed parameter has incomplete type", decl); } else { runtime.error("parameter '"+name+"' has incomplete type", decl); } } // Add to symbol table. if (null == name) { if (! type.hasError()) { runtime.error("parameter name omitted", decl); } } else if (! table.current().isDefinedLocally(name)) { table.current().define(name, type); } } } } } /** * Check that all labels used in the specified top-level function * are also defined. This method requires that all relevant AST * nodes have been associated with their symbol table {@link * Constants#SCOPE scopes}. * * @param function The function to check. */ protected void checkUsedLabels(GNode function) { checkUsedLabelsVisitor.dispatch(function); } /** The actual implementation of {@link #checkUsedLabels(GNode)}. */ @SuppressWarnings("unused") private Visitor checkUsedLabelsVisitor = new Visitor() { private void check(String id, GNode labelRef) { final String name = SymbolTable.toLabelName(id); // Start looking for a definition in the current scope. Scope scope = table.current(); // If there is a local declaration or definition, use that // one. while (! isFunctionScope(scope.getName())) { final Type type = (Type)scope.lookupLocally(name); if (null != type) { assert type.resolve().isLabel() : "Malformed label type"; if (type.hasAttribute(Constants.ATT_UNINITIALIZED)) { runtime.error("label '"+id+"' used but not defined", labelRef); } type.addAttribute(Constants.ATT_USED); return; } scope = scope.getParent(); } // Otherwise, check the function's scope. final Type type = (Type)scope.lookupLocally(name); if (null == type) { runtime.error("label '" + id + "' used but not defined", labelRef); } else { if (type.hasAttribute(Constants.ATT_UNINITIALIZED)) { runtime.error("label '" + id + "' used but not defined", labelRef); } type.addAttribute(Constants.ATT_USED); } } public void visitGotoStatement(GNode n) { if (null == n.get(0)) { check(n.getGeneric(1).getString(0), n); } } public void visitLabelAddressExpression(GNode n) { check(n.getString(0), n); } public void visit(Node n) { table.enter(n); for (Object o : n) { if (o instanceof Node) dispatch((Node)o); } table.exit(n); } }; /** * Check that all labels defined in the specified top-level function * are also used. This method requires that all relevant AST nodes * have been associated with their symbol table {@link * Constants#SCOPE scopes} and that all used labels have been * annotated as {@link Constants#ATT_USED used}. * * @param function The function to check. */ protected void checkDefinedLabels(GNode function) { @SuppressWarnings("unused") final Visitor v = new Visitor() { final Map<Type, Type> checkedDefined = new IdentityHashMap<Type, Type>(); final Map<Type, Type> checkedUsed = new IdentityHashMap<Type, Type>(); public void visitNamedLabel(GNode n) { final String id = n.getString(0); final String name = SymbolTable.toLabelName(id); final Type type = (Type)table.current().lookup(name); if (null != type) { if (! checkedUsed.containsKey(type)) { checkedUsed.put(type, type); if (! type.hasAttribute(Constants.ATT_USED)) { runtime.warning("label '" + id + "' defined but not used", n); } } } } public void visitLocalLabelDeclaration(GNode n) { for (Object o : n) { final String id = Token.cast(o); final String name = SymbolTable.toLabelName(id); final Type type = (Type)table.current().lookup(name); if (null != type) { if (! checkedDefined.containsKey(type)) { checkedDefined.put(type, type); if (type.hasAttribute(Constants.ATT_UNINITIALIZED) && ! type.hasAttribute(Constants.ATT_USED)) { runtime.warning("label '" + id + "' declared but not defined",n); } } } } } public void visit(Node n) { table.enter(n); for (Object o : n) { if (o instanceof Node) dispatch((Node)o); } table.exit(n); } }; v.dispatch(function); } /** Visit the specified labeled statement. */ public Type visitLabeledStatement(GNode n) { // Process the label. dispatch(n.getNode(0)); // Process the statement. If the statement has a type, capture // that type. final Object o = dispatch(n.getNode(1)); final Type result = (o instanceof Type) ? (Type)o : VoidT.TYPE; mark(n, result); return result; } /** Visit the specified named label. */ public void visitNamedLabel(GNode n) { final String id = n.getString(0); final String name = SymbolTable.toLabelName(id); final List<Attribute> atts = toAttributeList(n.getGeneric(1)); // Start looking for a suitable scope with the current scope. Scope scope = table.current(); // If there is a local label declaration, define the label in the // declaration's scope. while (! isFunctionScope(scope.getName())) { final Type type = (Type)scope.lookupLocally(name); if (null != type) { assert type.isLabel() : "Malformed label type"; if (type.hasAttribute(Constants.ATT_UNINITIALIZED)) { scope.define(name, new LabelT(id).locate(n). attribute(Constants.ATT_DEFINED).attribute(atts)); } else { runtime.error("duplicate label '" + id + "'", n); reportPrevious(id, type); } return; } scope = scope.getParent(); } // Define the label in the function's scope. final Type type = (Type)scope.lookupLocally(name); if (null != type && ! type.hasAttribute(Constants.ATT_UNINITIALIZED)) { runtime.error("duplicate label '" + id + "'", n); reportPrevious(id, (Type)scope.lookupLocally(name)); } else { scope.define(name, new LabelT(id).locate(n). attribute(Constants.ATT_DEFINED).attribute(atts)); } } /** Visit the specified case label. */ public void visitCaseLabel(GNode n) { if (0 == switches.size()) { runtime.error("case label not within a switch statement", n); return; } final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); final Node n2 = (2 == n.size()) ? n.getNode(1) : null; final Type t2 = (Type)dispatch(n2); if (t1.isError() || (null != t2 && t2.isError())) { return; } else if ((! c().isIntegral(t1)) || ((null != t2) && (! c().isIntegral(t2)))) { runtime.error("case label not of integer type", n); return; } else if ((! t1.hasConstant()) || ((null != t2) && (! t2.hasConstant()))) { runtime.error("case label not constant", n); return; } if (null != t2) { // Test: i2 < i1 try { if (t2.getConstant().bigIntValue(). compareTo(t2.getConstant().bigIntValue()) < 0) { runtime.error("empty range in case label", n); } } catch (IllegalStateException x) { runtime.warning("can't compute range in case label", n); } } } /** Visit the specified default label. */ public void visitDefaultLabel(GNode n) { if (0 == switches.size()) { runtime.error("'default' label not within a switch statement", n); } else if (switches.get(switches.size()-1)) { runtime.error("multiple default labels in one switch", n); } else { switches.set(switches.size()-1, Boolean.TRUE); } } /** Visit the specified local label declaration. */ public void visitLocalLabelDeclaration(GNode n) { for (Object o : n) { final String id = Token.cast(o); final String name = SymbolTable.toLabelName(id); if (table.current().isDefinedLocally(name)) { runtime.error("duplicate label declaration '" + id + "'", n); reportPrevious(id, (Type)table.current().lookupLocally(name)); } else { table.current().define(name, new LabelT(id).locate(n). attribute(Constants.ATT_UNINITIALIZED)); } } } /** Visit the specified compound statement. */ public Type visitCompoundStatement(GNode n) { final boolean scope = hasScope; hasScope = true; final boolean stmtexpr = isStmtAsExpr; isStmtAsExpr = false; if (scope || stmtexpr) { String name = table.freshName("block"); table.enter(name); table.mark(n); } Type result = VoidT.TYPE; final int size = n.size(); for (int i=0; i<size; i++) { Object o = dispatch((Node)n.get(i)); if ((size-2 == i) && (o instanceof Type)) { // If the last statement (i.e., the child before the trailing // annotations) is an expression statement, capture that // expression's type. result = (Type)o; } } if (scope || stmtexpr) { table.exit(); } hasScope = scope; return stmtexpr ? result : VoidT.TYPE; } /** Visit the specified if else statement. */ public void visitIfElseStatement(GNode n) { final Node n1 = n.getNode(0); ensureScalar(n1, c().pointerize((Type)dispatch(n1))); dispatch(n.getNode(1)); dispatch(n.getNode(2)); } /** Visit the specified if else statement. */ public void visitIfStatement(GNode n) { final Node n1 = n.getNode(0); ensureScalar(n1, c().pointerize((Type)dispatch(n1))); dispatch(n.getNode(1)); } /** Visit the specified while statement. */ public void visitWhileStatement(GNode n) { final Node n1 = n.getNode(0); ensureScalar(n1, c().pointerize((Type)dispatch(n1))); loops.add(Boolean.TRUE); dispatch(n.getNode(1)); loops.remove(loops.size()-1); } /** Visit the specified do statement. */ public void visitDoStatement(GNode n) { loops.add(Boolean.TRUE); dispatch(n.getNode(0)); loops.remove(loops.size()-1); final Node n2 = n.getNode(1); ensureScalar(n2, c().pointerize((Type)dispatch(n2))); } /** Visit the specified for statement. */ public void visitForStatement(GNode n) { final boolean scope = hasScope; hasScope = false; final String name = table.freshName("forloop"); table.enter(name); table.mark(n); dispatch(n.getNode(0)); if (null != n.get(1)) { Node n2 = n.getNode(1); ensureScalar(n2, c().pointerize((Type)dispatch(n2))); } dispatch(n.getNode(2)); loops.add(Boolean.TRUE); dispatch(n.getNode(3)); loops.remove(loops.size()-1); table.exit(); hasScope = scope; } /** Visit the specified switch statement. */ public void visitSwitchStatement(GNode n) { final Node n1 = n.getNode(0); ensureInteger(n1, c().pointerize((Type)dispatch(n1))); switches.add(Boolean.FALSE); dispatch(n.getNode(1)); switches.remove(switches.size()-1); } /** Visit the specified break statement. */ public void visitBreakStatement(GNode n) { if ((0 == switches.size()) && (0 == loops.size())) { runtime.error("break statement not within loop or switch", n); } } /** Visit the specified continue statement. */ public void visitContinueStatement(GNode n) { if (0 == loops.size()) { runtime.error("continue statement not within a loop", n); } } /** Visit the specified return statement. */ public Type visitReturnStatement(GNode n) { final Type t1 = (Type)dispatch(n.getNode(0)); // Find the enclosing function scope. Scope scope = table.current(); while (! isFunctionScope(scope.getName())) { scope = scope.getParent(); } // Determine the function's name, type, and result type. final String name = SymbolTable.fromNameSpace(scope.getName()); final Type function = (Type)scope.getParent().lookupLocally(name); final Type result = function.resolve().toFunction().getResult(); // Check for consistenty. if (result.hasTag(Tag.VOID)) { if ((null != t1) && ! t1.hasTag(Tag.VOID)) { runtime.warning("'return' with a value, in function returning void", n); } } else { if (null != t1) { processAssignment(false, "return", n, result, t1); } else if (pedantic) { runtime.warning("'return' with no value, in function returning " + "non-void", n); } } // Done. mark(n, result); return result; } /** Visit the specified goto statement. */ public void visitGotoStatement(GNode n) { // Regular goto labels are checked by checkLabels(). if (null == n.get(0)) return; // Computed goto statements are checked right here. final Node n1 = n.getNode(1); final Type t1 = (Type)dispatch(n1); if (ensureScalar(n1, t1) && t1.resolve().isFloat()) { runtime.error("cannot convert to pointer type", n); } } /** Visit the specified expression statement. */ public Type visitExpressionStatement(GNode n) { final Type t = (Type)dispatch(n.getNode(0)); // The resulting type is not an lvalue. final Type result = c().toRValue(t); mark(n, result); return result; } /** Visit the specified expression list. */ public List visitExpressionList(GNode n) { // Create a list of expression types and return it. final List<Type> result = new ArrayList<Type>(n.size()); for (Object o : n) result.add((Type)dispatch((Node)o)); return result; } /** * Process the specified expression node. This method simply * dispatches this visitor on the specified node and returns the * resulting type. It is typically used from other visitors nested * in this class. * * @param n The expression node. * @return The corresponding type. */ public Type processExpression(Node n) { return (Type)dispatch(n); } /** Visit the specified comma expression. */ public Type visitCommaExpression(GNode n) { // C99 6.5.17 dispatch(n.getNode(0)); final Type t2 = (Type)dispatch(n.getNode(1)); final Type r2 = c().pointerize(t2); // GCC performs pointer decay; so do we. // C99 6.5.17, footnote 95: the result is not an lvalue. final Type result = c().toRValue(r2); mark(n, result); return result; } /** Visit the specified assignment expression. */ public Type visitAssignmentExpression(GNode n) { // C99 6.5.16 final Node n1 = n.getNode(0); final String op = n.getString(1); final Node n2 = n.getNode(2); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; if (t1.hasError() || t2.hasError()) { // Nothing to see here. Move on. result = ErrorT.TYPE; } else if ("=".equals(op)) { final boolean cond1 = ensureLValue(n1, t1); result = processAssignment(false, "assignment", n, t1, t2); // Patch in error type. if (! cond1) result = ErrorT.TYPE; } else if ("+=".equals(op) || "-=".equals(op)) { // Addition and subtraction require either arithmetic operands // or the left side to be a pointer and the right side to be an // integer. final Type r1 = t1.resolve(); switch (r1.tag()) { case BOOLEAN: case INTEGER: case FLOAT: { final boolean cond1 = ensureLValue(n1, t1); final boolean cond2 = ensureArithmetic(n2, t2); result = cond1 && cond2 ? r1 : ErrorT.TYPE; } break; case POINTER: { final boolean cond1 = ensureLValue(n1, t1); final boolean cond2 = ensureInteger(n2, t2); result = cond1 && cond2 ? r1 : ErrorT.TYPE; } break; default: runtime.error("invalid "+toDescription(n1)+" where scalar required", n1); result = ErrorT.TYPE; } } else if ("*=".equals(op) || "/=".equals(op)) { // Multiplication and division require arithmetic operands. final boolean cond1 = ensureArithmetic(n1, t1) && ensureLValue(n1, t1); final boolean cond2 = ensureArithmetic(n2, t2); result = cond1 && cond2 ? t1.resolve() : ErrorT.TYPE; } else { // Modulo, shift, bitwise and, bitwise xor, and bitwise or // require integer operands. final boolean cond1 = ensureInteger(n1, t1) && ensureLValue(n1, t1); final boolean cond2 = ensureInteger(n2, t2); result = cond1 && cond2 ? t1.resolve() : ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified conditional expression. */ public Type visitConditionalExpression(GNode n) { // C99 6.5.15 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Node n3 = n.getNode(2); final Type t1 = (Type)dispatch(n1); Type t2 = (Type)dispatch(n2); final Type t3 = (Type)dispatch(n3); if (null == t2) t2 = t1; // Allow for GCC's omitted middle operand. final Type r2 = c().pointerize(t2); final Type r3 = c().pointerize(t3); Type result; final boolean cond1 = ensureScalar(n1, c().pointerize(t1)); if (r2.isError() || r3.isError()) { result = ErrorT.TYPE; } else if (c().isArithmetic(t2) && c().isArithmetic(t3)) { result = cond1 ? c().convert(t2, t3) : ErrorT.TYPE; result = valueConditional(result, t1, t2, t3); } else if ((r2.isStruct() && r3.isStruct() && c().equal(t2, t3)) || (r2.isUnion() && r3.isUnion() && c().equal(t2, t3))) { result = cond1 ? c().qualify(r2, t2) : ErrorT.TYPE; } else if (r2.isVoid() && r3.isVoid()) { result = cond1 ? (Type)VoidT.TYPE : ErrorT.TYPE; } else if (r2.isPointer() && t3.hasConstant() && t3.getConstant().isNull()) { result = cond1 ? c().qualify(r2, t2) : ErrorT.TYPE; result = valueConditional(result, t1, t2, t3); } else if (t2.hasConstant() && t2.getConstant().isNull() && r3.isPointer()) { result = cond1 ? c().qualify(r3, t3) : ErrorT.TYPE; result = valueConditional(result, t1, t2, t3); } else if (r2.isPointer() && r3.isPointer()) { final Type pt2 = r2.toPointer().getType(); // PointedTo, PointedToResolved final Type pt3 = r3.toPointer().getType(); final Type ptr2 = pt2.resolve(); final Type ptr3 = pt3.resolve(); if (ptr2.isError() || ptr3.isError()) { result = ErrorT.TYPE; } else if (c().equal(ptr2, ptr3)) { if (cond1) { result = new PointerT(c().qualify(c().qualify(ptr2, pt2), pt3)); result = c().qualify(c().qualify(result, t2), t3); result = valueConditional(result, t1, t2, t3); } else { result = ErrorT.TYPE; } } else if (ptr2.isVoid() || ptr3.isVoid()) { if (cond1) { result = new PointerT(c().qualify(c().qualify(VoidT.TYPE, pt2), pt3)); result = c().qualify(c().qualify(result, t2), t3); result = valueConditional(result, t1, t2, t3); } else { result = ErrorT.TYPE; } } else { runtime.error("pointer type mismatch in conditional expression", n); result = ErrorT.TYPE; } } else if ((c().isIntegral(t2) && r3.isPointer()) || (r2.isPointer() && c().isIntegral(t3))) { runtime.error("pointer/integer type mismatch in conditional expression",n); result = ErrorT.TYPE; } else { runtime.error("type mismatch in conditional expression", n); result = ErrorT.TYPE; } mark(n, result); return result; } /** * Determine a conditional expression's value. If a conditional * expression's value can be statically determined, this method * annotates the expression's result with that value. * * @param result The result type. * @param t1 The conditional's type. * @param t2 The consequence's type. * @param t3 The alternative's type. * @return The annotated result type. */ protected Type valueConditional(Type result, Type t1, Type t2, Type t3) { if (result.isError()) return result; if (t1.hasConstant()) { final Type type = t1.getConstant().isTrue() ? t2 : t3; if (type.hasConstant()) { result = result.annotate().constant(type.getConstant().getValue()); } } return result; } /** Visit the specified logical or expression. */ public Type visitLogicalOrExpression(GNode n) { // C99 6.5.14 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; final boolean cond1 = ensureScalar(n1, c().pointerize(t1)); final boolean cond2 = ensureScalar(n2, c().pointerize(t2)); if (cond1 && cond2) { result = NumberT.INT; if (t1.hasConstant()) { if (t1.getConstant().isTrue()) { result = result.annotate().constant(true); } else if (t2.hasConstant()) { result = result.annotate().constant(t2.getConstant().isTrue()); } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified logical and expression. */ public Type visitLogicalAndExpression(GNode n) { // C99 6.5.13 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; final boolean cond1 = ensureScalar(n1, c().pointerize(t1)); final boolean cond2 = ensureScalar(n2, c().pointerize(t2)); if (cond1 && cond2) { result = NumberT.INT; if (t1.hasConstant()) { if (! t1.getConstant().isTrue()) { result = result.annotate().constant(false); } else if (t2.hasConstant()) { result = result.annotate().constant(t2.getConstant().isTrue()); } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified bitwise or expression. */ public Type visitBitwiseOrExpression(GNode n) { // C99 6.5.12 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; final boolean cond1 = ensureInteger(n1, t1); final boolean cond2 = ensureInteger(n2, t2); if (cond1 && cond2) { result = c().convert(t1, t2); if (t1.hasConstant() && t2.hasConstant()) { result = result.annotate(); try { result = result. constant(c().mask(t1.getConstant().bigIntValue(), result). or(c().mask(t2.getConstant().bigIntValue(), result))); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified bitwise xor expression. */ public Type visitBitwiseXorExpression(GNode n) { // C99 6.5.11 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; final boolean cond1 = ensureInteger(n1, t1); final boolean cond2 = ensureInteger(n2, t2); if (cond1 && cond2) { result = c().convert(t1, t2); if (t1.hasConstant() && t2.hasConstant()) { result = result.annotate(); try { result = result. constant(c().mask(t1.getConstant().bigIntValue(), result). xor(c().mask(t2.getConstant().bigIntValue(), result))); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified bitwise and expression. */ public Type visitBitwiseAndExpression(GNode n) { // C99 6.5.10 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; final boolean cond1 = ensureInteger(n1, t1); final boolean cond2 = ensureInteger(n2, t2); if (cond1 && cond2) { result = c().convert(t1, t2); if (t1.hasConstant() && t2.hasConstant()) { result = result.annotate(); try { result = result. constant(c().mask(t1.getConstant().bigIntValue(), result). and(c().mask(t2.getConstant().bigIntValue(), result))); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified equality expression. */ public Type visitEqualityExpression(GNode n) { // C99 6.5.9 final Node n1 = n.getNode(0); final String op = n.getString(1); final Node n2 = n.getNode(2); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); final Type r1 = c().pointerize(t1); final Type r2 = c().pointerize(t2); Type result; if (r1.isError() || r2.isError()) { // Nothing to see here. Move on. result = ErrorT.TYPE; } else if (c().isArithmetic(t1) && c().isArithmetic(t2)) { result = NumberT.INT; if (t1.hasConstant() && t2.hasConstant()) { result = result.annotate(); try { if (c().isIntegral(r1) && c().isIntegral(r2)) { final BigInteger i1 = t1.getConstant().bigIntValue(); final BigInteger i2 = t2.getConstant().bigIntValue(); result = result.constant("==".equals(op) ? i1.compareTo(i2) == 0 : i1.compareTo(i2) != 0); } else { final double d1 = t1.getConstant().doubleValue(); final double d2 = t2.getConstant().doubleValue(); result = result.constant("==".equals(op) ? d1 == d2 : d1 != d2); } } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else if (r1.isPointer() && t2.hasConstant() && t2.getConstant().isNull()) { result = NumberT.INT; if (t1.hasConstant()) { result = result.annotate().constant(! t1.getConstant().isTrue()); } } else if (t1.hasConstant() && t1.getConstant().isNull() && r2.isPointer()) { result = NumberT.INT; if (t2.hasConstant()) { result = result.annotate().constant(! t2.getConstant().isTrue()); } } else if (! pedantic && ((r1.isPointer() && c().isIntegral(r2)) || (c().isIntegral(r1) && r2.isPointer()))) { runtime.warning("comparison between pointer and integer", n); result = NumberT.INT; if (t1.hasConstant() && t2.hasConstant()) { final boolean equal = t1.getConstant().getValue(). equals(t2.getConstant().getValue()); result = result.annotate().constant("==".equals(op) ? equal : ! equal); } } else if (r1.isPointer() && r2.isPointer()) { final Type ptr1 = r1.toPointer().getType().resolve(); // PointedToResolved final Type ptr2 = r2.toPointer().getType().resolve(); if (ptr1.isError() || ptr2.isError()) { result = ErrorT.TYPE; } else if (c().equal(ptr1, ptr2) || ptr1.isVoid() || ptr2.isVoid()) { result = NumberT.INT; if (t1.hasConstant() && t2.hasConstant()) { final boolean equal = t1.getConstant().getValue(). equals(t2.getConstant().getValue()); result = result.annotate().constant("==".equals(op)? equal : ! equal); } } else { runtime.error("comparison of distinct pointer types lacks a cast", n); result = ErrorT.TYPE; } } else { runtime.error("invalid operands to 'binary " + op + "'", n); result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified relational expression. */ public Type visitRelationalExpression(GNode n) { // C99 6.5.8 final Node n1 = n.getNode(0); final String op = n.getString(1); final Node n2 = n.getNode(2); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); final Type r1 = c().pointerize(t1); final Type r2 = c().pointerize(t2); Type result; if (r1.isError() || r2.isError()) { // Nothing to see here. Move on. result = ErrorT.TYPE; } else if (c().isReal(t1) && c().isReal(t2)) { result = NumberT.INT; if (t1.hasConstant() && t2.hasConstant()) { result = result.annotate(); try { if (c().isIntegral(r1) && c().isIntegral(r2)) { final BigInteger i1 = t1.getConstant().bigIntValue(); final BigInteger i2 = t2.getConstant().bigIntValue(); if ("<".equals(op)) { result = result.constant(i1.compareTo(i2) < 0); } else if (">".equals(op)) { result = result.constant(i1.compareTo(i2) > 0); } else if ("<=".equals(op)) { result = result.constant(i1.compareTo(i2) <= 0); } else { result = result.constant(i1.compareTo(i2) >= 0); } } else { final double d1 = t1.getConstant().doubleValue(); final double d2 = t2.getConstant().doubleValue(); if ("<".equals(op)) { result = result.constant(d1 < d2); } else if (">".equals(op)) { result = result.constant(d1 > d2); } else if ("<=".equals(op)) { result = result.constant(d1 <= d2); } else { result = result.constant(d1 >= d2); } } } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else if (r1.isPointer() && r2.isPointer()) { final Type ptr1 = r1.toPointer().getType().resolve(); // PointedToResolved final Type ptr2 = r2.toPointer().getType().resolve(); if (ptr1.isError() || ptr2.isError()) { result = ErrorT.TYPE; } else if (c().equal(ptr1, ptr2)) { result = NumberT.INT; if (t1.hasConstant() && t2.hasConstant()) { result = result.annotate().constant(new StaticReference(result)); } } else { runtime.error("comparison of distinct pointer types lacks a cast", n); result = ErrorT.TYPE; } } else { runtime.error("invalid operands to 'binary " + op + "'", n); result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified shift expression. */ public Type visitShiftExpression(GNode n) { // C99 6.5.7 final Node n1 = n.getNode(0); final String op = n.getString(1); final Node n2 = n.getNode(2); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; final boolean cond1 = ensureInteger(n1, t1); final boolean cond2 = ensureInteger(n2, t2); if (cond1 && cond2) { result = c().promote(t1); if (t2.hasConstant()) { BigInteger distance; try { distance = t2.getConstant().bigIntValue(); } catch (IllegalStateException x) { runtime.warning("can't compute shift count", n2); distance = BigInteger.ZERO; } final BigInteger width = BigInteger.valueOf(c().getWidth(result)); // Test: distance >= width if (distance.compareTo(width) >= 0) { if ("<<".equals(op)) { runtime.warning("left shift count >= width of type", n2); } else { runtime.warning("right shift count >= width of type", n2); } // Test: distance < 0 } else if (distance.compareTo(BigInteger.ZERO) < 0) { if ("<<".equals(op)) { runtime.warning("left shift count is negative", n2); } else { runtime.warning("right shift count is negative", n2); } } else if (t1.hasConstant()) { result = result.annotate(); try { final BigInteger value = t1.getConstant().bigIntValue(); if ("<<".equals(op)) { result = result.constant(value.shiftLeft(distance.intValue())); result = c().qualify(result, t1); } else { result = result.constant(value.shiftRight(distance.intValue())); result = c().qualify(result, t1); } } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); result = c().qualify(result, t1); } } else { result = c().qualify(result, t1); } } else { result = c().qualify(result, t1); } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified additive expression. */ public Type visitAdditiveExpression(GNode n) { // C99 6.5.6 final Node n1 = n.getNode(0); final String op = n.getString(1); final Node n2 = n.getNode(2); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); final Type r1 = c().pointerize(t1); final Type r2 = c().pointerize(t2); Type result; if (r1.isError() || r2.isError()) { result = ErrorT.TYPE; } else if (c().isArithmetic(t1) && c().isArithmetic(t2)) { result = c().convert(t1, t2); if (t1.hasConstant() && t2.hasConstant()) { result = result.annotate(); if (c().isIntegral(result)) { // If one of the terms has a reference value, try to // preserve a meaningful value. if (t1.getConstant().isReference() && (! t2.getConstant().isReference())) { if ("+".equals(op)) { // ref + int result = result. constant(t1.getConstant().refValue(). add(t2.getConstant().bigIntValue())); } else { // ref - int result = result. constant(t1.getConstant().refValue(). subtract(t2.getConstant().bigIntValue())); } } else if ((! t1.getConstant().isReference()) && "+".equals(op) && t2.getConstant().isReference()) { // int + ref result = result. constant(t2.getConstant().refValue(). add(t1.getConstant().bigIntValue())); } else if (t1.getConstant().isReference() && "-".equals(op) && t2.getConstant().isReference()) { // ref - ref final BigInteger diff = t1.getConstant().refValue(). difference(t2.getConstant().refValue()); if (null != diff) { result = result.constant(diff); } else { result = result.constant(new StaticReference(result)); } } else { try { final BigInteger i1 = t1.getConstant().bigIntValue(); final BigInteger i2 = t2.getConstant().bigIntValue(); // int + int result = result.constant("+".equals(op) ? i1.add(i2) : i1.subtract(i2)); } catch(IllegalStateException x) { // ref + ref result = result.constant(new StaticReference(result)); } } } else { try { final double d1 = t1.getConstant().doubleValue(); final double d2 = t2.getConstant().doubleValue(); result = result.constant("+".equals(op) ? d1 + d2 : d1 - d2); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } } else if ("+".equals(op)) { if (r1.isPointer() && c().isIntegral(r2)) { if (ensurePointerArithmetic(n, r1)) { result = r1; if (c().hasConstRef(t1) && t2.hasConstant()) { result = result.annotate(); try { result = result. constant(c().getConstRef(t1). add(t2.getConstant().bigIntValue())); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } } else if (c().isIntegral(r1) && r2.isPointer()) { if (ensurePointerArithmetic(n, r2)) { result = r2; if (t1.hasConstant() && c().hasConstRef(t2)) { result = result.annotate(); try { result = result. constant(c().getConstRef(t2). add(t1.getConstant().bigIntValue())); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } } else { runtime.error("invalid operands to 'binary +'" , n); result = ErrorT.TYPE; } } else if (r1.isPointer() && r2.isPointer()) { final Type ptr1 = r1.toPointer().getType().resolve(); final Type ptr2 = r2.toPointer().getType().resolve(); if (ptr1.isError() || ptr2.isError()) { result = ErrorT.TYPE; } else if (c().equal(ptr1, ptr2)) { result = C.PTR_DIFF; if (c().hasConstRef(t1) && c().hasConstRef(t2)) { BigInteger diff = c().getConstRef(t1).difference(c().getConstRef(t2)); result = result.annotate(); if (null == diff) { result = result.constant(new StaticReference(result)); } else { result = result.constant(diff); } } } else { runtime.error("invalid operands to 'binary -'", n); result = ErrorT.TYPE; } } else if (r1.isPointer() && c().isIntegral(r2)) { if (ensurePointerArithmetic(n, r1)) { result = r1; if (c().hasConstRef(t1) && t2.hasConstant()) { result = result.annotate(); try { result = result.constant(c().getConstRef(t1). subtract(t2.getConstant().bigIntValue())); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } } else { runtime.error("invalid operands to 'binary -'", n); result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified multiplicative expression. */ public Type visitMultiplicativeExpression(GNode n) { // C99 6.5.5 final Node n1 = n.getNode(0); final String op = n.getString(1); final Node n2 = n.getNode(2); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); Type result; final boolean cond1 = (("%".equals(op) && ensureInteger(n1, t1)) || ((! "%".equals(op)) && ensureArithmetic(n1, t1))); final boolean cond2 = (("%".equals(op) && ensureInteger(n2, t2)) || ((! "%".equals(op)) && ensureArithmetic(n2, t2))); if (cond1 && cond2) { result = c().convert(t1, t2); if (t2.hasConstant()) { Constant c2 = t2.getConstant(); if ((! "*".equals(op)) && (! c2.isTrue())) { runtime.warning("division by zero", n2); } else if (t1.hasConstant()) { Constant c1 = t1.getConstant(); result = result.annotate(); try { if (c().isIntegral(result)) { BigInteger i1 = c1.bigIntValue(); BigInteger i2 = c2.bigIntValue(); if ("*".equals(op)) { result = result.constant(i1.multiply(i2)); } else if ("/".equals(op)) { result = result.constant(i1.divide(i2)); } else { result = result.constant(i1.remainder(i2)); } } else { double d1 = c1.doubleValue(); double d2 = c2.doubleValue(); result = result.constant("*".equals(op) ? d1 * d2 : d1 / d2); } } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified cast expression. */ public Type visitCastExpression(GNode n) { // C99 6.5.4, 6.3.2.3 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); final Type r1 = t1.resolve(); final Type r2 = c().pointerize(t2); // As an exception, GCC allows a struct or union cast to itself, // ignoring qualifiers. final boolean gcc = (! pedantic && r1.hasStructOrUnion() && r2.hasStructOrUnion() && c().equal(r1, r2)); Type result; final boolean cond2 = r1.isVoid() || gcc || ensureScalar(n2, r2); switch (r1.tag()) { case ERROR: { result = ErrorT.TYPE; } break; case VOID: { // Cast to void. result = t1; } break; case POINTER: { // Cast to a pointer. if (! cond2) { result = ErrorT.TYPE; } else if (r2.isFloat()) { runtime.error("cannot convert to pointer type", n); result = ErrorT.TYPE; } else if (c().isIntegral(r2)) { // Cast from an integer. result = t1; if (t2.hasConstant()) { Type pt1 = r1.toPointer().getType(); // Capture the memory location. Reference ref; try { ref = NullReference.NULL.add(t2.getConstant().bigIntValue()); } catch (IllegalStateException x) { ref = new StaticReference(pt1); } result = result.annotate(); if (pt1.hasTag(Tag.VOID) || c().isChar(pt1) || ref.isStatic()) { result = result.constant(ref); } else { result = result.constant(new CastReference(pt1, ref)); } } } else { // Cast from another pointer. result = r1; if (c().hasConstRef(t2)) { Type pt1 = r1.toPointer().getType(); Type pt2 = r2.toPointer().getType(); result = result.annotate(); if (pt1.equals(pt2)) { // The types have the same memory shape. result = result.constant(c().getConstRef(t2)); } else { // The types have different shapes. result = result.constant(new CastReference(pt1, c().getConstRef(t2))); } } } } break; case BOOLEAN: case INTEGER: case FLOAT: { // Cast to a number. if (! cond2) { result = ErrorT.TYPE; } else if (c().isArithmetic(r2)) { // Cast from another number. result = t1; if (t2.hasConstant()) { result = result.annotate(); if (c().isIntegral(r1)) { if (c().isIntegral(r2)) { // Cast int to int. result = result.constant(t2.getConstant().getValue()); } else { // Cast float to int: try to convert the original value // to a big integer value. try { result = result.constant(t2.getConstant().bigIntValue()); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { // Cast number to float: try to convert the original value // to a double value. try { result = result.constant(t2.getConstant().doubleValue()); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } } else { // Cast from a pointer. if (r1.isFloat()) { runtime.error("cannot convert from pointer type", n); result = ErrorT.TYPE; } else { result = t1; if (c().hasConstRef(t2)) { Reference ref = c().getConstRef(t2); Type pt2 = r2.toPointer().getType(); result = result.annotate(); if (ref.hasLocation()) { // Preserve the location. result = result.constant(ref.getLocation(c())); } else { // Soldier on with the reference as an arithmetic value // and hope that the program doesn't do any arithmetic // on the value beyond what is legal for pointers... result = result.constant(ref); } } } } } break; case ARRAY: { runtime.error("cast specifies array type", n); result = ErrorT.TYPE; } break; case FUNCTION: { runtime.error("cast specifies function type", n); result = ErrorT.TYPE; } break; case STRUCT: case UNION: { if (gcc) { // Handle the GCC exception. result = t1; break; } // Otherwise, fall through. } default: runtime.error("conversion to non-scalar type requested", n); result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified sizeof expression. */ public Type visitSizeofExpression(GNode n) { // C99 6.5.3.4 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); final Type r1 = t1.resolve(); Type result; if (r1.isError()) { // Nothing to see here. Move on. result = ErrorT.TYPE; } else if (c().isIncomplete(t1) && ! r1.isVoid()) { runtime.error("invalid application of 'sizeof' to incomplete type", n); result = ErrorT.TYPE; } else if (t1.hasVariable() && t1.toVariable().hasWidth()) { runtime.error("'sizeof' applied to a bit-field", n); result = ErrorT.TYPE; } else if (pedantic && r1.isFunction()) { runtime.error("'sizeof' applied to funcion", n); result = ErrorT.TYPE; } else { result = C.SIZEOF; if (! c().isVariablyModified(r1)) { result = result.annotate(). constant(BigInteger.valueOf(c().getSize(r1))); } } mark(n, result); return result; } /** Visit the specified alignof expression. */ public Type visitAlignofExpression(GNode n) { // No formal specification, so we treat it just like sizeof. // Experiments with GCC bear this out. final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); final Type r1 = t1.resolve(); final Type result; if (t1.hasError()) { // Nothing to see here. Move on. result = ErrorT.TYPE; } else if (c().isIncomplete(t1) && ! r1.isVoid() && ! r1.isArray()) { runtime.error("invalid application of '__alignof' to incomplete type", n); result = ErrorT.TYPE; } else if (t1.hasVariable() && t1.toVariable().hasWidth()) { runtime.error("'__alignof' applied to a bit-field", n); result = ErrorT.TYPE; } else if (pedantic && r1.isFunction()) { runtime.error("'__alignof' applied to function", n); result = ErrorT.TYPE; } else { result = C.SIZEOF.annotate(). constant(BigInteger.valueOf(c().getAlignment(t1))); } mark(n, result); return result; } /** Visit the specified offset of expression. */ public Type visitOffsetofExpression(GNode n) { final Type base = (Type)dispatch(n.getNode(0)); final Type member = processOffset(base, n.getGeneric(1)); final Type result; if (member.isError()) { result = ErrorT.TYPE; } else if (member.hasConstant()) { result = C.SIZEOF.annotate().constant(member.getConstant().getValue()); } else { result = C.SIZEOF; } mark(n, result); return result; } /** * Process the specified offset selection. * * @param base The base type. * @param selection The selection. * @return The selected type annotated with its offset or * <code>ErrorT.TYPE</code> on errors. */ protected Type processOffset(Type base, GNode selection) { if (selection.hasName("PrimaryIdentifier")) { final String name = selection.getString(0); final Type member = processSelection(selection, base, name, false); if (member.isError()) return ErrorT.TYPE; final long offset = c().getOffset(base.toStructOrUnion(), name); return member.annotate().constant(BigInteger.valueOf(offset)); } else if (selection.hasName("DirectComponentSelection")) { base = processOffset(base, selection.getGeneric(0)); if (base.isError()) return ErrorT.TYPE; final String name = selection.getString(1); final Type member = processSelection(selection, base, name, false); if (member.isError()) return ErrorT.TYPE; if (! base.hasConstant()) return member; final long offset = base.getConstant().longValue() + c().getOffset(base.toStructOrUnion(), name); return member.annotate().constant(BigInteger.valueOf(offset)); } else if (selection.hasName("SubscriptExpression")) { base = processOffset(base, selection.getGeneric(0)); if (base.isError()) return ErrorT.TYPE; final Type index = (Type)dispatch(selection.getNode(1)); if (index.isError()) return ErrorT.TYPE; final Type element = processSubscript(selection, base, index); if (element.isError()) return ErrorT.TYPE; if (! base.hasConstant() || ! index.hasConstant()) return element; final long offset = base.getConstant().longValue() + c().getSize(element) * index.getConstant().longValue(); return element.annotate().constant(BigInteger.valueOf(offset)); } else { runtime.error("second argument to 'offsetof' neither a " + "selection nor a subscript", selection); return ErrorT.TYPE; } } /** Visit the specified type compatibility expression. */ public Type visitTypeCompatibilityExpression(GNode n) { final Type t1 = (Type)dispatch(n.getNode(0)); final Type t2 = (Type)dispatch(n.getNode(1)); final Type result = NumberT.INT.annotate(). constant(c().compose(t1, t2, pedantic).isError() ? BigInteger.ZERO : BigInteger.ONE); mark(n, result); return result; } /** Visit the specified unary minus expression. */ public Type visitUnaryMinusExpression(GNode n) { // C99 6.5.3.3 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); Type result; if (ensureArithmetic(n1, t1)) { result = c().promote(t1); if (t1.hasConstant()) { result = result.annotate(); try { if (c().isIntegral(result)) { result = result. constant(c().mask(t1.getConstant().bigIntValue().negate(),result)); } else { result = result.constant(-t1.getConstant().doubleValue()); } } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified unary plus expression. */ public Type visitUnaryPlusExpression(GNode n) { // C99 6.5.3.3 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); Type result; if (ensureArithmetic(n1, t1)) { result = c().promote(t1); if (t1.hasConstant()) { result = result.annotate().constant(t1.getConstant().getValue()); } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified logical negation expression. */ public Type visitLogicalNegationExpression(GNode n) { // C99 6.5.3.3 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); final Type r1 = c().pointerize(t1); Type result; if (ensureScalar(n1, r1)) { result = NumberT.INT; if (t1.hasConstant()) { result = result.annotate().constant(! t1.getConstant().isTrue()); } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified bitwise negation expression. */ public Type visitBitwiseNegationExpression(GNode n) { // C99 6.5.3.3 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); Type result; if (ensureInteger(n1, t1)) { result = c().promote(t1); if (t1.hasConstant()) { try { result = result.annotate(). constant(c().mask(t1.getConstant().bigIntValue().not(), result)); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified address expression. */ public Type visitAddressExpression(GNode n) { // C99 6.5.3.2 final GNode n1 = n.getGeneric(0); // &(*...) and &(x[y]) expressions get special treatment. if (n1.hasName("IndirectionExpression")) { // Determine the base type. final Type base = (Type)dispatch(n1.getGeneric(0)); // Process the indirection and address to ensure that the types // are valid. Type type = processIndirection(n1, base, false); type = processAddress(n, type); // Return the base, but not as an lvalue. Type result; if (type.isError()) { result = ErrorT.TYPE; } else { // Ensure the result is a pointer and an rvalue. result = c().toRValue(c().pointerize(base)); // Track any constant value. final Type resolved = base.resolve(); if (resolved.isArray() || resolved.isFunction()) { // Account for pointer decay. if (base.hasShape() && base.getShape().isConstant()) { result = result.annotate().constant(base.getShape()); } } else if (base.hasConstant()) { result = result.annotate().constant(base.getConstant().getValue()); } } mark(n, result); return result; } else if (n1.hasName("SubscriptExpression")) { // Determine the base and index types. final Type base = (Type)dispatch(n1.getGeneric(0)); final Type index = (Type)dispatch(n1.getGeneric(1)); // Process the subscript to ensure that the types are valid. final Type type = processSubscript(n1, base, index); // Return the type as if performing a pointer, integer addition. Type result; if (type.isError()) { result = ErrorT.TYPE; } else { // Ensure the result is a pointer. result = c().pointerize(base); // Track any constant value. Reference ref = null; if (base.resolve().isArray() && base.hasShape() && base.getShape().isConstant()) { // Account for pointer decay. ref = base.getShape(); } else if (base.hasConstant()) { // A pointer's constant value must be a reference. assert base.getConstant().isReference(); ref = (Reference)base.getConstant().getValue(); } if (null != ref && index.hasConstant()) { result = result.annotate(); try { result = result.constant(ref.add(index.getConstant().bigIntValue())); } catch (IllegalStateException x) { result = result.constant(new StaticReference(result)); } } } mark(n, result); return result; } // Back to normal operation. final Type t1 = (Type)dispatch(n1); Type result = processAddress(n, t1); // Track compile-time constant pointers. if (t1.hasShape() && t1.getShape().isConstant()) { result = result.annotate().constant(t1.getShape()); } // Done. mark(n, result); return result; } /** * Process the specified address expression. This method assumes * that the specified node neither is an indirection nor a subscript * expression. * * @param n The node. * @param type The type. * @return The resulting type. */ protected Type processAddress(GNode n, Type type) { final Type resolved = type.resolve(); if (resolved.isError()) { return ErrorT.TYPE; } else if (type.hasShape()) { if (type.hasVariable() && type.toVariable().hasWidth()) { runtime.error("cannot take address of bit-field '" + type.toVariable().getName() + "'", n); return ErrorT.TYPE; } else if (type.hasAttribute(Constants.ATT_STORAGE_REGISTER)) { runtime.error("address of register " + toDescription(n.getNode(0)) + " requested", n); return ErrorT.TYPE; } else { Type result = new PointerT(c().qualify(resolved, type)); if (type.getShape().isConstant()) { result = result.annotate().constant(type.getShape()); } return result; } } else { runtime.error("invalid lvalue in unary '&'", n.getNode(0)); return ErrorT.TYPE; } } /** Visit the specified label address expression. */ public Type visitLabelAddressExpression(GNode n) { // The label's name is checked in a second phase after all a // function's labels have been added to the symbol table. Here, // we only construt the appropriate type (void *). final Type result = PointerT.TO_VOID.annotate(). constant(new StaticReference(n.getString(0), VoidT.TYPE)); mark(n, result); return result; } /** Visit the specified indirection expression. */ public Type visitIndirectionExpression(GNode n) { // C99 6.5.3.2 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); final Type result = processIndirection(n, t1, true); mark(n, result); return result; } /** * Process the specified indirection expression. * * @param n The node. * @param type The type. * @param warn The flag for whether to emit any warnings. * @return The resulting type. */ protected Type processIndirection(Node n, Type type, boolean warn) { final Type resolved = c().pointerize(type); if (resolved.isError()) { return ErrorT.TYPE; } else if (resolved.isPointer()) { final Type pt = resolved.toPointer().getType(); // PointedTo, PTResolved final Type ptr = pt.resolve(); Type result; if (ptr.isError()) { result = ErrorT.TYPE; } else if (c().isIncomplete(pt)) { runtime.error("dereferencing pointer to incomplete type", n); result = ErrorT.TYPE; } else { final Reference ref; if (type.hasShape()) { ref = type.getShape().indirect(type); } else if (type.hasConstant()) { // Note that arrays and functions never have a value. So, // just using the reference value is safe. ref = type.getConstant().refValue(); } else { ref = new DynamicReference(ptr); } result = c().qualify(ptr, pt).annotate().shape(ref); } if (ptr.isVoid() && warn) { runtime.warning("dereferencing 'void *' pointer", n); } return result; } else { runtime.error("operand to 'unary *' not a pointer type", n); return ErrorT.TYPE; } } /** Visit the specified preincrement expression. */ public Type visitPreincrementExpression(GNode n) { // C99 6.5.3.1 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); Type result; if (ensureScalar(n1, t1) && ensurePointerArithmetic(n, t1) && ensureLValue(n1, t1)) { result = t1.resolve(); } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified predecrement expression. */ public Type visitPredecrementExpression(GNode n) { // C99 6.5.3.1 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); Type result; if (ensureScalar(n1, t1) && ensurePointerArithmetic(n, t1) && ensureLValue(n1, t1)) { result = t1.resolve(); } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified extension expression. */ public Type visitExtensionExpression(GNode n) { // Nothing to see here. Move on. return (Type)dispatch(n.getNode(0)); } /** Visit the specified subscript expression. */ public Type visitSubscriptExpression(GNode n) { // C99 6.5.2.1 final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); final Type t1 = (Type)dispatch(n1); final Type t2 = (Type)dispatch(n2); final Type result = processSubscript(n, t1, t2); mark(n, result); return result; } /** * Process the specified subscript expression. * * @param n The node. * @param base The base type. * @param index The index type. * @return The corresponding element type or an error type in case * of an error. */ protected Type processSubscript(Node n, Type base, Type index) { // C99 6.5.2.1 final Type resolved = c().pointerize(base); final boolean cond2; if (index.hasError()) { cond2 = false; } else if (c().isIntegral(index)) { cond2 = true; } else { runtime.error("array subscript is not an integer", n); cond2 = false; } if (base.hasError()) { return ErrorT.TYPE; } else if (resolved.isPointer()) { final Type pt1 = resolved.toPointer().getType(); // PointedTo, PTResolved final Type ptr1 = pt1.resolve(); if (ptr1.isError()) { return ErrorT.TYPE; } else if (ptr1.isFunction()) { runtime.error("subscripted value is pointer to function", n); return ErrorT.TYPE; } else if (c().isIncomplete(pt1)) { runtime.error("dereferencing pointer to incomplete type", n); return ErrorT.TYPE; } else if (cond2) { Reference ref; if (base.hasShape() && index.hasConstant()) { try { ref = base.getShape().indirect(base). add(index.getConstant().bigIntValue()); } catch (IllegalStateException x) { ref = new StaticReference(ptr1); } } else { ref = new DynamicReference(ptr1); } return c().qualify(ptr1, pt1).annotate().shape(ref); } else { return ErrorT.TYPE; } } else { runtime.error("subscripted value is neither array nor pointer", n); return ErrorT.TYPE; } } /** Visit the specified direct component selection. */ public Type visitDirectComponentSelection(GNode n) { // C99 6.5.2.3 final Node n1 = n.getNode(0); final String id = n.getString(1); final Type t1 = (Type)dispatch(n1); final Type result = processSelection(n1, t1, id, false); mark(n, result); return result; } /** Visit the specified indirect component selection. */ public Type visitIndirectComponentSelection(GNode n) { // C99 6.5.2.3 final Node n1 = n.getNode(0); final String id = n.getString(1); final Type t1 = (Type)dispatch(n1); final Type result = processSelection(n1, t1, id, true); mark(n, result); return result; } /** * Process a component selection. This method extracts the member * type for the member with the specified name from the specified * struct or union type. * * @param node The node. * @param type The type. * @param name The name of the member. * @param indirect The flag for whether the component selection is * indirect. * @return The corresponding member type or an error type in case of * a malformed type or field name. */ protected Type processSelection(Node node, Type type, String name, boolean indirect) { if (type.hasError()) return ErrorT.TYPE; // First, resolve any indirection. Type base; if (indirect) { final Type resolved = c().pointerize(type); if (resolved.isPointer()) { base = resolved.toPointer().getType(); if (c().isIncomplete(base)) { runtime.error("dereferencing pointer to incomplete type", node); return ErrorT.TYPE; } } else { runtime.error("invalid type argument of '->'", node); return ErrorT.TYPE; } } else { base = type; } // Second, extract the member type. if (base.hasError()) { return ErrorT.TYPE; } else if (base.hasStructOrUnion()) { final Tagged tag = base.toTagged(); Type result = tag.lookup(name); if (result.isError()) { runtime.error("'" + (tag.isStruct() ? "struct " : "union ") + tag.getName() + "' has no member named '" + name + "'", node); return ErrorT.TYPE; } else { result = c().qualify(result, base); if (indirect || type.hasShape()) { Reference ref; if (type.hasShape()) { ref = indirect ? type.getShape().indirect(type) : type.getShape(); } else if (type.hasConstant()) { // Note that arrays and functions never have a value. So, // just using the reference value is safe. ref = type.getConstant().refValue(); } else { ref = new DynamicReference(base); } result = result.annotate().shape(new FieldReference(ref, name)); } return result; } } else { runtime.error("request for member '" + name + "' in something not a struct or union", node); return ErrorT.TYPE; } } /** Visit the specified function call. */ public Type visitFunctionCall(GNode n) { final Node n1 = n.getNode(0); final Node n2 = n.getNode(1); Type t1; if (GNode.cast(n1).hasName("PrimaryIdentifier")) { final String name = GNode.cast(n1).getString(0); // Support __xtc_trace() diagnostic. if ("__xtc_trace".equals(name)) { @SuppressWarnings("unchecked") final List<Type> args = (List<Type>)dispatch(n2); boolean first = true; Type result = null; for (Type t : args) { if (first) { first = false; result = t; } runtime.console().loc(n).p(": "); t.trace(runtime); } if (null == result) result = VoidT.TYPE; mark(n, result); return result; } // Back to our regularly scheduled programming... t1 = (Type)table.lookup(name); if (null == t1) { if (pedantic) { runtime.error("'" + name + "' undeclared", n1); t1 = ErrorT.TYPE; } else { // Implicitly declare this identifier as a function. Since // the lookup failed, the identifier is not declared in the // current scope nor in the global scope. t1 = new FunctionT(NumberT.INT, new ArrayList<Type>(0), false). attribute(Constants.ATT_STYLE_OLD). attribute(Constants.ATT_IMPLICIT). annotate().attribute(Constants.ATT_STORAGE_EXTERN); // Always enter the implicit declaration in the local scope. table.current().define(name, t1.shape(true, name).locate(n)); // Compare to any previous extern declarations. final Type extern = lookupExtern(name); if (null == extern) { // runtime.warning("implicit declaration of function '"+name+"'", n); defineExtern(name, t1.shape(true, name).locate(n)); } else if (! extern.hasAttribute(Constants.ATT_IMPLICIT)) { // FIXME: add warning for first implicit declaration. // Compose the two types to detect any errors. compose(n, name, t1, extern, false); } } } } else { t1 = (Type)dispatch(n1); } final Type r1 = c().pointerize(t1); @SuppressWarnings("unchecked") final List<Type> args = (List<Type>)dispatch(n2); Type result; if (r1.isError()) { result = ErrorT.TYPE; } else if (r1.isPointer() && r1.toPointer().getType().hasTag(Tag.FUNCTION)) { final FunctionT function = r1.toPointer().getType().resolve().toFunction(); final List<Type> parameters = function.getParameters(); // If the function has a prototype, check the types. if (! function.hasAttribute(Constants.ATT_STYLE_OLD)) { final int size1 = parameters.size(); final int size2 = (null == args) ? 0 : args.size(); final int min = Math.min(size1, size2); final String name = toFunctionName(n1); for (int i=0; i<min; i++) { String desc = "passing argument " + (i+1); if (null != name) desc = desc + " to '" + name + "'"; processAssignment(false, desc, n, parameters.get(i), args.get(i)); } if (size1 > size2) { if (null == name) { runtime.error("too few arguments to function", n); } else { runtime.error("too few arguments to function '" + name + "'", n); } } else if ((! function.isVarArgs()) && (size1 < size2)) { if (null == name) { runtime.error("too many arguments to function", n); } else { runtime.error("too many arguments to function '" + name + "'", n); } } } result = function.getResult(); } else { runtime.error("called " + toDescription(n1) + " is not a function", n); result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified postincrement expression. */ public Type visitPostincrementExpression(GNode n) { // C99 6.5.2.4 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); Type result; if (ensureScalar(n1, t1) && ensurePointerArithmetic(n, t1) && ensureLValue(n1, t1)) { result = t1.resolve(); } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified postdecrement expression. */ public Type visitPostdecrementExpression(GNode n) { // C99 6.5.2.4 final Node n1 = n.getNode(0); final Type t1 = (Type)dispatch(n1); Type result; if (ensureScalar(n1, t1) && ensurePointerArithmetic(n, t1) && ensureLValue(n1, t1)) { result = t1.resolve(); } else { result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified compound literal. */ public Type visitCompoundLiteral(GNode n) { // C99 6.5.2.5 Type t1 = (Type)dispatch(n.getNode(0)); // Compound literals are compile-time constant lvalues. final Reference ref = new StaticReference("<literal>", t1.resolve()); t1 = t1.annotate().shape(ref).constant(ref); if (isTopLevel) { // Top-level compound literals have static storage. t1 = t1.attribute(Constants.ATT_STORAGE_STATIC); } else { // Block-scope compound literals have automatic storage. t1 = t1.attribute(Constants.ATT_STORAGE_AUTO); } // Process the initializer. final Type result; if (t1.hasStructOrUnion() && c().isIncomplete(t1)) { final String kind = t1.hasTag(Tag.STRUCT) ? "struct" : "union"; if (t1.toTagged().isUnnamed()) { runtime.error("unnamed " + kind + " has incomplete type", n); } else { runtime.error("'" + kind + " " + t1.toTagged().getName() + "' has incomplete type", n); } result = ErrorT.TYPE; } else { result = processInitializer(n, null, t1, n.getGeneric(1)); } mark(n, result); return result; } /** Visit the specified primary identifier. */ public Type visitPrimaryIdentifier(GNode n) { Type result = (Type)table.lookup(n.getString(0)); if (null == result) { runtime.error("'" + n.getString(0) + "' undeclared", n); result = ErrorT.TYPE; } mark(n, result); return result; } /** Visit the specified floating constant. */ public Type visitFloatingConstant(GNode n) { final Type result = c().typeFloat(n.getString(0)); mark(n, result); return result; } /** Visit the specified integer constant. */ public Type visitIntegerConstant(GNode n) { Type result = c().typeInteger(n.getString(0)); if (! c().fits(result.getConstant().bigIntValue(), result)) { runtime.warning("integer constant is too large for its type"); // Reconstruct value. result = result.resolve().annotate(). constant(c().mask(result.getConstant().bigIntValue(), result)); } mark(n, result); return result; } /** Visit the specified character constant. */ public Type visitCharacterConstant(GNode n) { Type result = c().typeCharacter(n.getString(0)); if (! c().fits(result.getConstant().bigIntValue(), result)) { runtime.warning("character constant too large for its type", n); // Reconstruct value. result = result.resolve().annotate(). constant(c().mask(result.getConstant().bigIntValue(), result)); } mark(n, result); return result; } /** Visit the specified string constant. */ public Type visitStringConstant(GNode n) { // C99 6.4.5 // Build up the actual string value. final StringBuilder buf = new StringBuilder(); boolean wide = false; for (Object o : n) { String s = Token.cast(o); if (s.startsWith("L")) { try { buf.append(Utilities.unescape(s.substring(2, s.length()-1))); } catch (IllegalArgumentException x) { runtime.error(x.getMessage(), n); } wide = true; } else { try { buf.append(Utilities.unescape(s.substring(1, s.length()-1))); } catch (IllegalArgumentException x) { runtime.error(x.getMessage(), n); } } } final String value = buf.toString(); // Construct the type. Note that we ignore the terminating null // character for the string constant's size because later // comparisons with fixed-size arrays do not consider it (C99 // 6.7.8). Type base = wide ? C.WCHAR : NumberT.CHAR; if (Limits.IS_STRING_CONST) { base = base.annotate().attribute(Constants.ATT_CONSTANT); } Type result = new ArrayT(base, value.length()); result = result.annotate().shape(new StringReference(value, result)); result = result.constant(result.getShape()); // Done. mark(n, result); return result; } /** Visit the specified statement as expression. */ public Type visitStatementAsExpression(GNode n) { isStmtAsExpr = true; final Type result = (Type)dispatch(n.getNode(0)); mark(n, result); return result; } /** Visit the specified variable argument access. */ public Type visitVariableArgumentAccess(GNode n) { final Type list = (Type)dispatch(n.getNode(0)); if (! c().equal(InternalT.VA_LIST, list.resolve())) { runtime.error("first argument to 'va_arg' not of type 'va_list'"); } final Type result = (Type)dispatch(n.getNode(1)); mark(n, result); return result; } /** Visit the specified type name. */ public Type visitTypeName(GNode n) { final Specifiers spec = newSpecifiers(n.getGeneric(0), false); Type result = getDeclaredType(spec.getBaseType(), n.getGeneric(1)); result = spec.annotateFull(result); mark(n, result); return result; } /** Visit the specified generic node. */ public void visit(Node n) { final boolean scope = hasScope; hasScope = true; for (Object o : n) { if (o instanceof Node) dispatch((Node)o); } hasScope = scope; } // ======================================================================== /** * Check that the specified type is well-formed. This method checks * that the specified type is not an array of functions, not a * function returning an array or a function, and not a pointer to * either of the first two types. * * @param node The declaring node. * @param name The optional name. * @param type The type. * @return <code>true</code> if the type is well-formed. */ public boolean checkType(GNode node, String name, Type type) { // Resolve the type. type = type.resolve(); // Strip any pointers. while (type.isPointer()) { type = type.toPointer().getType().resolve(); } // Check arrays. if (type.isArray()) { Type element = type; do { element = element.toArray().getType().resolve(); } while (element.isArray()); if (element.isFunction()) { if (null == name) { runtime.error("declaration of array of functions", node); } else { runtime.error("'" + name + "' declared as array of functions", node); } return false; } else { // Recursively check the element type. return checkType(node, name, element); } } // Check functions. if (type.isFunction()) { Type result = type.toFunction().getResult().resolve(); if (result.isArray()) { if (null == name) { runtime.error("declaration of function returning an array", node); } else { runtime.error("'" + name + "' declared as function returning " + "an array", node); } return false; } else if (result.isFunction()) { if (null == name) { runtime.error("declaration of function returning a function", node); } else { runtime.error("'" + name + "' declared as function returning " + "a function", node); } return false; } else { // Recursively check the result type. return checkType(node, name, result); } } return true; } /** * Compose the specified type with the type of the previous * declaration. This method composes compatible object and function * types with each other, while also reporting any errors. * * @param decl The current declaration's node. * @param name The identifier name. * @param type The current declaration's type. * @param previous The previous declaration's type. * @param isFuncDef The flag for whether the composition is for a * function definition. * @return The composite type or {@link ErrorT#TYPE} if the two * types are not compatible. */ public Type compose(GNode decl, String name, Type type, Type previous, boolean isFuncDef) { if (previous.isAlias() || type.resolve().isFunction() != previous.resolve().isFunction()) { runtime.error("'" + name + "' redeclared as different kind of symbol", decl); reportPrevious(name, previous); return ErrorT.TYPE; } // For an invocation of C.compose(t1, t2), t1's attributes // dominate. Therefore, we give precedence to types not having // the extern attribute. Type composite; if (previous.hasAttribute(Constants.ATT_STORAGE_EXTERN) || (! type.hasAttribute(Constants.ATT_STORAGE_EXTERN)) || isFuncDef) { composite = c().compose(type, previous, pedantic); } else { composite = c().compose(previous, type, pedantic); } if (composite.isError()) { if (previous.hasAttribute(Constants.ATT_BUILTIN) && previous.resolve().isFunction()) { runtime.error("conflicting types for built-in function '" + name + "'", decl); } else { runtime.error("conflicting types for '" + name + "'", decl); reportPrevious(name, previous); } return ErrorT.TYPE; } else if (! c().hasSameQualifiers(type, previous)) { runtime.error("conflicting type qualifiers for '" + name + "'", decl); reportPrevious(name, previous); return ErrorT.TYPE; } return composite; } /** * Get the first declaration specifier with the specified name. * * @param name The name. * @param specifiers The specifiers. * @return The corresponding declaration specifier or * <code>null</code> if no such specifier exists. */ public static GNode getSpecifier(String name, GNode specifiers) { for (Object o : specifiers) { final GNode specifier = GNode.cast(o); if (specifier.hasName(name)) return specifier; } return null; } /** * Create a new sequence of specifiers. This factory method should * be used instead of allocating an instance of {@link Specifiers} * directly, so that the processing of declaration specifiers can be * customized by subclasses. * * @param specifiers The node holding the declaration specifiers * (which may be <code>null</code>). * @param refIsDecl The flag for whether a struct/union reference * also is a struct/union declaration. * @return The sequence of specifiers. */ public Specifiers newSpecifiers(GNode specifiers, boolean refIsDecl) { return new Specifiers(specifiers, refIsDecl); } /** * Convert the specified GCC attribute specifier or GCC attribute * specifier list to an xtc attribute list. If the specified node * is <code>null</code>, this method returns an empty list. * Otherwise, it returns a newly allocated, unsealed list. * * @param spec The GCC attribute specifier (list). * @return The corresponding xtc attributes. */ public List<Attribute> toAttributeList(GNode spec) { if (null == spec) { return Collections.emptyList(); } else { return toAttributeList(spec, new ArrayList<Attribute>()); } } /** The actual implementation of {@link #toAttributeList(GNode)}. */ private List<Attribute> toAttributeList(GNode spec, List<Attribute> result) { if (spec.hasName("AttributeSpecifier")) { if (null != spec.get(0)) { for (Object o : spec.getGeneric(0)) { final GNode entry = GNode.cast(o); final String name = toAttributeName(entry.getString(0)); final Object value = toAttributeValue(entry.getGeneric(1)); // Check values of known attributes. boolean seenError = false; if ("packed".equals(name)) { // ------------------------------------- if (null != value) { runtime.error("wrong number of arguments specified for 'packed'" + " attribute", entry); seenError = true; } } else if ("aligned".equals(name)) { // ----------------------------- if (null == value) { // Ignore. } else if (value instanceof List) { runtime.error("wrong number of arguments specified for 'aligned'" + " attribute", entry); seenError = true; } else if (value instanceof Node) { runtime.error("requested alignment is not a constant", entry); seenError = true; } else if (! (value instanceof BigInteger)) { runtime.error("requested alignment is not an integer constant", entry); seenError = true; } else { BigInteger i = (BigInteger)value; if (1 != i.signum() || 1 != i.bitCount()) { runtime.error("requested alignment is not a power of 2", entry); seenError = true; } else if (Limits.INT_MAX.compareTo(i) < 0) { runtime.error("requested alignment is too large", entry); seenError = true; } } } // Create attribute. if (! seenError) { result. add(new Attribute(Constants.NAME_GCC, new Attribute(name, value))); } } } return result; } else if (spec.hasName("AttributeSpecifierList")) { for (Object o : spec) toAttributeList(GNode.cast(o), result); return result; } else { throw new AssertionError("Not an attribute specifier (list): " + spec); } } /** * Convert the specified string to an attribute name. * * @param s The string. * @return The corresponding name. */ public String toAttributeName(String s) { if (s.startsWith("__")) s = s.substring(2); if (s.endsWith("__")) s = s.substring(0, s.length()-2); return s; } /** * Convert the specified node to an attribute value. This method * converts a node representing a GCC attribute's value to the * actual attribute's value:<ul> * * <li>If the node is <code>null</code> or an empty expression list, * this method returns <code>null</code>.</li> * * <li>If the node is an expression list with one element, it * returns the result of recursively processing that element.</li> * * <li>If the node is an expression list with more than one element, * it recursively processes each element, returning a new Java * collections framework list.</li> * * <li>If the node is a primary identifier, it returns the * corresponding symbol (without checking that the symbol is * defined).</li> * * <li>If the node is a constant expression, it returns the * corresponding value.</li> * * <li>Otherwise, it returns the AST node.</li> * * </ul> * * @param node The node. * @return The corresponding value. */ public Object toAttributeValue(GNode node) { if (null == node) return null; if (node.hasName("ExpressionList")) { if (0 == node.size()) { return null; } else if (1 == node.size()) { return toAttributeValue(node.getGeneric(0)); } else { final List<Object> l = new ArrayList<Object>(node.size()); for (Object o : node) l.add(toAttributeValue(GNode.cast(o))); return l; } } else if (node.hasName("PrimaryIdentifier")) { return node.getString(0); } else { final Type t = (Type)dispatch(node); return t.hasConstant() ? t.getConstant().getValue() : node; } } /** * Get the parameter types for the specified parameter declaration. * As a side effect, this method reports any errors in a parameter's * declaration specifiers, any void parameters (besides a single * void specifier), any multiple parameters in old-style * declarations, and any parameter redefinitions in new-style * declarations. * * @param parameters The parameters, which may be <code>null</code>. * @return The corresponding function type with the argument types * and variable flag filled in. */ public FunctionT getParameterTypes(GNode parameters) { if (null == parameters) { // No parameters. final FunctionT function = new FunctionT(null, new ArrayList<Type>(0), false); function.addAttribute(Constants.ATT_STYLE_OLD); return function; } else if (parameters.hasName("ParameterTypeList")) { // Enter a temporary scope so that variable length arrays can // reference previously declared parameters. table.enter(TMP_SCOPE); // A new-style parameter type list. final boolean variable; final List<Type> types; if (isVoidParameterTypeList(parameters)) { variable = false; types = new ArrayList<Type>(0); // Get the type. final GNode param = parameters.getGeneric(0).getGeneric(0); final Specifiers spec = newSpecifiers(param.getGeneric(0), false); final Type type = spec.annotateFull(spec.getBaseType()); // Check that the void specifier does not have a storage // class, qualifier, or function specifier. if (type.hasAttribute(Constants.NAME_STORAGE)) { runtime.error("'void' as only parameter may not have storage class", parameters); } if (c().hasQualifiers(type)) { runtime.error("'void' as only parameter may not be qualified", parameters); } if (type.hasAttribute(Constants.ATT_INLINE)) { runtime.error("'void' as only parameter may not be declared 'inline'", parameters); } } else { variable = (null != parameters.get(1)); parameters = parameters.getGeneric(0); types = new ArrayList<Type>(parameters.size()); for (Object o : parameters) { final GNode param = GNode.cast(o); final GNode decl = param.getGeneric(1); final GNode ident = getDeclaredId(decl); final String name = (null != ident) ? ident.getString(0) : null; final Specifiers spec = newSpecifiers(param.getGeneric(0), false); Type type = getDeclaredType(true, spec.getBaseType(), decl); // Check that the type is well-formed. checkType(param, name, type); // Pointerize array and function parameter types. switch (type.tag()) { case ARRAY: type = c(). qualify(new PointerT(type.resolve().toArray().getType()), type); break; case FUNCTION: type = c().qualify(new PointerT(type.resolve()), type); break; } // Annotate the type. if (null == name) { type = type.annotate().shape(false, "<param>"); } else { type = VariableT.newParam(type, name).shape(false, name); } type = spec.annotateFull(type). attribute(toAttributeList(param.getGeneric(2))); // Check that any storage class specifier is register. if ((type.hasAttribute(Constants.NAME_STORAGE) && (! type.hasAttribute(Constants.ATT_STORAGE_REGISTER))) || type.hasAttribute(Constants.ATT_THREAD_LOCAL)) { if (null == name) { runtime.error("storage class specified for parameter", param); } else { runtime.error("storage class specified for parameter '"+name+"'", param); } } // Check that there is no function specifier. if (spec.contains(Constants.ATT_INLINE)) { if (null == name) { runtime.error("parameter declared 'inline'", param); } else { runtime.error("parameter '"+name+"' declared 'inline'", param); } } if (null == name) { types.add(type); } else if (table.current().isDefinedLocally(name)) { runtime.error("redefinition of parameter '" + name + "'", param); types.add(VariableT.newParam(ErrorT.TYPE, name)); } else { table.current().define(name, type); types.add(type); } } } // Exit the temporary scope and delete it again. table.exit(); table.delete(TMP_SCOPE); // Done. final FunctionT function = new FunctionT(null, types, variable); function.addAttribute(Constants.ATT_STYLE_NEW); return function; } else if (parameters.hasName("IdentifierList")) { // An old-style identifier list. Set<String> names = new HashSet<String>(); List<Type> types = new ArrayList<Type>(parameters.size()); for (Object o : parameters) { final String name = Token.cast(o); if (names.contains(name)) { runtime.error("multiple parameters named '" + name + "'", parameters); types.add(VariableT.newParam(ErrorT.TYPE, name)); } else { names.add(name); types.add(VariableT.newParam(C.IMPLICIT, name).shape(false, name)); } } // Done. final FunctionT function = new FunctionT(null, types, false); function.addAttribute(Constants.ATT_STYLE_OLD); return function; } else { throw new AssertionError("Unrecognized parameter representation: " + parameters); } } /** * Get the declared type. The type is not a parameter type. * * @param base The base type. * @param declarator The declarator, which may be abstract or * <code>null</code>. * @return The declared type. */ public Type getDeclaredType(Type base, GNode declarator) { return getDeclaredType(false, base, declarator); } /** * Get the declared type. In addition to determining the declared * type, this method checks that any static storage class specifier * or type qualifiers only appear in an array declarator if the * declarator is part of a parameter declaration. * * @param isParam The flag for whether the type is a parameter type. * @param base The base type. * @param declarator The declarator, which may be abstract or * <code>null</code>. * @return The declared type. */ @SuppressWarnings("unused") public Type getDeclaredType(final boolean isParam, final Type base, final GNode declarator) { return (null == declarator)? base : (Type)new Visitor() { private Type result = base; private List<Attribute> list1 = null; private List<Attribute> list2 = null; private void annotate() { if ((null != list1) && (0 < list1.size())) { result = result.annotate().attribute(list1); list1 = null; } if ((null != list2) && (0 < list2.size())) { result = result.annotate().attribute(list2); list2 = null; } } private void processPointer(GNode pointer) { while (null != pointer) { final Specifiers spec = newSpecifiers(pointer.getGeneric(0), false); if (spec.hasBaseAttributes()) { result = spec.annotateBase(new PointerT(result).annotate()); } else { result = new PointerT(result); } pointer = pointer.getGeneric(1); } } private Type processArray(Type element, GNode expr, GNode decl) { // Process the size expression. if ((null != expr) && expr.hasName("VariableLength")) { // [*] denotes an incomplete variable length array. We // simply return an incomplete array type. if (! isParam) { runtime.error("'[*]' in non-parameter array declarator", decl); } return new ArrayT(element, true); } else if (null == expr) { // An incomplete array. return new ArrayT(element); } else { final Type size = processExpression(expr); if (size.hasError()) { return new ArrayT(element); } else if (! c().isIntegral(size)) { final GNode id = getDeclaredId(decl); if (null == id) { runtime.error("size of array has non-integer type", GNode.cast(expr)); } else { runtime.error("size of array '" + id.getString(0) + "' has non-integer type", GNode.cast(expr)); } return new ArrayT(element); } else if (size.hasConstant()) { BigInteger value; try { value = size.getConstant().bigIntValue(); } catch (IllegalStateException x) { final GNode id = getDeclaredId(decl); if (null == id) { runtime.warning("can't compute size of array", GNode.cast(expr)); } else { runtime.warning("can't compute size of array '" + id.getString(0) + "'", GNode.cast(expr)); } value = BigInteger.ONE; } // Test: value == 0 if (value.compareTo(BigInteger.ZERO) == 0) { if (pedantic) { final GNode id = getDeclaredId(decl); if (null == id) { runtime.error("ISO C forbids zero-size array", GNode.cast(expr)); } else { runtime.error("ISO C forbids zero-size array '" + id.getString(0) + "'", GNode.cast(expr)); } } return new ArrayT(element, 0); // Test: value < 0 } else if (value.compareTo(BigInteger.ZERO) < 0) { final GNode id = getDeclaredId(decl); if (null == id) { runtime.error("size of array is negative", GNode.cast(expr)); } else { runtime.error("size of array '" + id.getString(0) + "' is negative", GNode.cast(expr)); } return new ArrayT(element, 0); // Test: value > ARRAY_MAX } else if (value.compareTo(Limits.ARRAY_MAX) > 0) { final GNode id = getDeclaredId(decl); if (null == id) { runtime.error("size of array is too large", GNode.cast(expr)); } else { runtime.error("size of array '" + id.getString(0) + "' is too large", GNode.cast(expr)); } return new ArrayT(element, 0); } else { return new ArrayT(element, value.longValue()); } } else { return new ArrayT(element, true); } } } public Object visitAttributedDeclarator(GNode n) { if (null != n.get(0)) list1 = toAttributeList(n.getGeneric(0)); if (null != n.get(2)) list2 = toAttributeList(n.getGeneric(2)); return dispatch(n.getGeneric(1)); } public Object visitPointerDeclarator(GNode n) { processPointer(n.getGeneric(0)); annotate(); return dispatch(n.getGeneric(1)); } public Object visitArrayDeclarator(GNode n) { // Process the array size. result = processArray(result, n.getGeneric(2), n); // Process the array qualifier list. if (isParam) { if (n.getGeneric(0).hasName("SimpleDeclarator")) { final Specifiers spec = newSpecifiers(n.getGeneric(1), false); if (spec.hasBaseAttributes()) { result = spec.annotateBase(result.annotate()); } if (spec.hasInline() || (null != spec.getStorageClass())) { result = spec.annotateFull(result.annotate()); } } else if (0 < n.getGeneric(1).size()) { runtime.error("static or type qualifiers not in outermost " + "array type derivation", n); } } else if (0 < n.getGeneric(1).size()) { runtime.error("static or type qualifiers in non-parameter " + "array declarator", n); } // Process any annotations. annotate(); // Done. return dispatch(n.getGeneric(0)); } public Object visitFunctionDeclarator(GNode n) { final FunctionT function = getParameterTypes(n.getGeneric(1)); function.setResult(result); result = function; annotate(); return dispatch(n.getGeneric(0)); } public Object visitSimpleDeclarator(GNode n) { annotate(); return result; } public Object visitAttributedAbstractDeclarator(GNode n) { if (null != n.get(0)) list1 = toAttributeList(n.getGeneric(0)); return dispatch(n.getGeneric(1)); } public Object visitAbstractDeclarator(GNode n) { processPointer(n.getGeneric(0)); annotate(); return (null == n.get(1))? result : dispatch(n.getGeneric(1)); } public Object visitDirectAbstractDeclarator(GNode n) { if (3 == n.size()) { if ("[".equals(n.getString(1))) { result = processArray(result, n.getGeneric(2), n); } else { final FunctionT function = getParameterTypes(n.getGeneric(2)); function.setResult(result); result = function; } if (null == n.get(0)) { annotate(); return result; } else { annotate(); return dispatch(n.getGeneric(0)); } } else if (null == n.get(0)) { annotate(); return result; } else { annotate(); return dispatch(n.getGeneric(0)); } } }.dispatch(declarator); } /** * Get the declared identifier from the specified declarator. * * @param declarator The declarator, which may be abstract or * <code>null</code>. * @return The simple declarator node representing the declared * identifier or <code>null</code> if the specified declarator is * abstract or <code>null</code>. */ public static GNode getDeclaredId(GNode declarator) { return GNode.cast(getDeclaredIdVisitor.dispatch(declarator)); } /** The actual implementation of {@link #getDeclaredId(GNode)}. */ @SuppressWarnings("unused") private static final Visitor getDeclaredIdVisitor = new Visitor() { public Object visitBitField(GNode n) { return dispatch(n.getGeneric(1)); } public Object visitAttributedDeclarator(GNode n) { return dispatch(n.getGeneric(1)); } public Object visitPointerDeclarator(GNode n) { return dispatch(n.getGeneric(1)); } public Object visitFunctionDeclarator(GNode n) { return dispatch(n.getGeneric(0)); } public Object visitArrayDeclarator(GNode n) { return dispatch(n.getGeneric(0)); } public Object visitSimpleDeclarator(GNode n) { return n; } public Object visitAttributedAbstractDeclarator(GNode n) { return null; } public Object visitAbstractDeclarator(GNode n) { return null; } public Object visitDirectAbstractDeclarator(GNode n) { return null; } }; /** * Get the innermost function declarator from the specified * declarator. The innermost function declarator's parameters are * the parameters for a function declaration or definition. * * @param declarator The declarator. * @return The innermost function declarator or <code>null</code> if * the declarator does not contains a function declarator. */ public static GNode getFunctionDeclarator(GNode declarator) { return GNode.cast(getFunctionDeclaratorVisitor.dispatch(declarator)); } /** The actual implementation of {@link #getFunctionDeclarator}. */ @SuppressWarnings("unused") private static final Visitor getFunctionDeclaratorVisitor = new Visitor() { public Object visitAttributedDeclarator(GNode n) { return dispatch(n.getGeneric(1)); } public Object visitPointerDeclarator(GNode n) { return dispatch(n.getGeneric(1)); } public Object visitFunctionDeclarator(GNode n) { Object result = dispatch(n.getGeneric(0)); return (null == result)? n : result; } public Object visitArrayDeclarator(GNode n) { return dispatch(n.getGeneric(0)); } public Object visitSimpleDeclarator(GNode n) { return null; } }; /** * Determine whether the specified parameter type list represents a * function taking no arguments. This method checks whether the * parameter type list contains a single parameter without a * declarator, whose specifiers either contains a void type * specifier or a typedef name aliasing void. Though the parameter * type list may still be malformed, e.g., contain a qualifier, * storage class, or another type specifier. * * @param parameters The parameter type list. * @return <code>true</code> if the corresponding function takes no * arguments. */ public boolean isVoidParameterTypeList(GNode parameters) { assert parameters.hasName("ParameterTypeList"); if (null != parameters.get(1)) return false; parameters = parameters.getGeneric(0); if (1 != parameters.size()) return false; final GNode parameter = parameters.getGeneric(0); if (null != parameter.get(1)) return false; final GNode specifiers = parameter.getGeneric(0); for (Object o : specifiers) { final GNode spec = GNode.cast(o); if (spec.hasName("VoidTypeSpecifier")) { return true; } else if (spec.hasName("TypedefName")) { final Type type = (Type)table.current().lookup(spec.getString(0)); if ((null != type) && type.isAlias() && type.resolve().isVoid()) { return true; } } } return false; } /** * Determine whether the specified left-hand type can be initialized * from the specified right-hand type. * * @param t1 The left-hand type. * @param t2 The right-hand type. * @return <code>true</code> if the left-hand type can be * initialized by the right-hand type. */ protected boolean isInitializable(Type t1, Type t2) { if (t1.hasError() || t2.hasError()) return true; final Type r1 = t1.resolve(); final Type r2 = c().pointerize(t2); switch (r1.tag()) { case BOOLEAN: case INTEGER: case FLOAT: { if (r1.isBoolean()) { // Booleans can be assigned from scalar operands. return c().isScalar(r2); } else { // All other arithmetic types can only be assigned from // arithmetic types. GCC also allows assignments from // pointers. return c().isArithmetic(r2) || (r2.isPointer() && ! pedantic); } } case STRUCT: case UNION: { // A struct or union can only be assigned from another struct or // union of compatible type. return c().equal(r1, r2); } case ARRAY: { // An array can only be assigned in an initializer and only if // the left-hand type is a (wide) C string and the right-hand // type is a matching C string constant. return (t2.hasConstant() && ((c().isString(r1) && c().isString(t2)) || (c().isWideString(r1) && c().isWideString(t2)))); } case POINTER: { if (r2.isPointer()) { final Type pt1 = r1.toPointer().getType(); // PointedTo, PTResolved final Type pt2 = r2.toPointer().getType(); final Type ptr1 = pt1.resolve(); final Type ptr2 = pt2.resolve(); if (c().hasQualifiers(pt1, pt2) && (c().equal(ptr1, ptr2) || ptr1.isVoid() || ptr2.isVoid())) { return true; } else { // GCC extension. return ! pedantic; } } else if (t2.hasConstant() && t2.getConstant().isNull()) { return true; } else if (c().isIntegral(t2)) { // GCC extension. return ! pedantic; } else { return false; } } default: return (r1.isInternal() && r2.isInternal() && r1.toInternal().getName().equals(r2.toInternal().getName())); } } /** * Process the assignment. This method determines the resulting * type when assigning a value of the specified right-hand type to * an object with the specified left-hand type. It does not check * that the left-hand side represents a modifiable lvalue. * * @param init The flag for whether the assignment is in an * initializer. * @param op The name of the operation. * @param n The node. * @param t1 The left-hand type. * @param t2 The right-hand type. * @return The resulting type. */ protected Type processAssignment(boolean init, String op, Node n, Type t1, Type t2) { if (t1.hasError() || t2.hasError()) return ErrorT.TYPE; final Type r1 = t1.resolve(); final Type r2 = c().pointerize(t2); Type result = null; if (r2.isVoid()) { runtime.error("void value not ignored as it ought to be", n); return ErrorT.TYPE; } switch (r1.tag()) { case BOOLEAN: { // Booleans can be assigned from scalar operands. if (c().isScalar(r2)) { result = r1; } } break; case INTEGER: { // Integers can be assigned from scalar operands, but call for a // warning then the operand is a pointer. if (c().isArithmetic(r2)) { result = r1; } else if (r2.isPointer()) { if (pedantic) { runtime.error(op + " makes integer from pointer without a cast", n); result = ErrorT.TYPE; } else { // GCC extension. runtime.warning(op+" makes integer from pointer without a cast", n); result = r1; } } } break; case FLOAT: { // Floats can be assigned from other arithmetic types. if (c().isArithmetic(r2)) { result = r1; } } break; case STRUCT: case UNION: { // A struct or union can only be assigned from another struct or // union of compatible type. if (c().equal(r1, r2)) { result = r1; } } break; case ARRAY: { // An array can only be assigned in an initializer and only if // the left-hand type is a (wide) C string and the right-hand // type is a matching C string constant. if (init) { if (c().isString(r1) && t2.hasConstant()) { if (c().isString(t2)) { result = r1; } else if (c().isWideString(t2)) { runtime.error("char-array initialized from wide string", n); result = ErrorT.TYPE; } } else if (c().isWideString(r1) && t2.hasConstant()) { if (c().isString(t2)) { runtime.error("wchar_t-array initialized from non-wide string", n); result = ErrorT.TYPE; } else if (c().isWideString(t2)) { result = r1; } } } } break; case POINTER: { if (r2.isPointer()) { final Type pt1 = r1.toPointer().getType(); // PointedTo, PTResolved final Type pt2 = r2.toPointer().getType(); final Type ptr1 = pt1.resolve(); final Type ptr2 = pt2.resolve(); if (c().equal(ptr1, ptr2) || ptr1.isVoid() || ptr2.isVoid()) { if (c().hasQualifiers(pt1, pt2) || c().isStringLiteral(t2)) { result = r1; } else if (pedantic) { runtime.error(op + " discards qualifiers from pointer target " + "type", n); result = ErrorT.TYPE; } else { runtime.warning(op + " discards qualifiers from pointer target " + "type", n); result = r1; } } else if (ptr1.isNumber() && ptr2.isNumber() && NumberT.equalIgnoringSign(ptr1.toNumber().getKind(), ptr2.toNumber().getKind())) { // Note: We don't need to consider booleans here because all // booleans are unsigned. if (pedantic) { runtime.error("pointer targets in "+op+" differ in signedness",n); result = ErrorT.TYPE; } else { // GCC extension. runtime.warning("pointer targets in "+op+" differ in signedness",n); result = r1; } } else if (pedantic) { runtime.error("incompatible pointer types in " + op, n); result = ErrorT.TYPE; } else { // GCC extension. runtime.warning("incompatible pointer types in " + op, n); result = r1; } } else if (t2.hasConstant() && t2.getConstant().isNull()) { result = r1; } else if (c().isIntegral(t2)) { if (pedantic) { runtime.error(op + " makes pointer from integer without a cast", n); result = ErrorT.TYPE; } else { // GCC extension. runtime.warning(op + " makes pointer from integer without a cast", n); result = r1; } } } break; default: if (r1.isInternal() && r2.isInternal() && r1.toInternal().getName().equals(r2.toInternal().getName())) { result = r1; } } // Patch in generic error type and message. if (null == result) { runtime.error("incompatible types in " + op, n); result = ErrorT.TYPE; } // Done. return result; } /** * Mark the specified node with the specified type. * * @param node The node. * @param type The type. */ public void mark(Node node, Type type) { if (runtime.test("optionMarkAST")) { type.mark(node); } } /** * Ensure that the specified node with the specified type represents * a modifiable lvalue. * * @param n The node. * @param t The type. * @return <code>true</code> if the specified node represents a * modifiable lvalue. */ public boolean ensureLValue(Node n, Type t) { if (t.hasError()) { return false; } else if (! t.hasShape()) { runtime.error("invalid operand where lvalue required", n); return false; } else if (c().isIncomplete(t)) { runtime.error("assignment of incomplete " + toDescription(n), n); return false; } else if (! c().isModifiable(t)) { runtime.error("assignment of read-only " + toDescription(n), n); return false; } else { return true; } } /** * Ensure that the specified node with the specified type represents * a scalar. * * @param n The node. * @param t the type. * @return <code>true</code> if the specified node represents a * scalar. */ public boolean ensureScalar(Node n, Type t) { if (t.hasError()) { return false; } else if (! c().isScalar(t)) { runtime.error("invalid " + toDescription(n) + " where scalar required", n); return false; } else { return true; } } /** * Ensure that the specified node represents valid pointer * arithmetic for the specified type. If the specified type is a * pointer, this method ensures that the pointer points to a * complete type. For all other types besides the error type, it * returns <code>true</code>. * * @param n The node. * @param t The type. * @return <code>true</code> if the specified node represents valid * pointer arithmetic. */ public boolean ensurePointerArithmetic(Node n, Type t) { if (t.hasError()) { return false; } else if (t.resolve().isPointer()) { final Type pt = t.resolve().toPointer().getType(); if (! pt.resolve().isVoid() && c().isIncomplete(pt)) { runtime.error("arithmetic on pointer to an incomplete type", n); return false; } else { return true; } } else { return true; } } /** * Ensure that the specified node with the specified type represents * an arithmetic type. * * @param n The node. * @param t The type. * @return <code>true</code> if the specified node represents an * arithmetic type. */ public boolean ensureArithmetic(Node n, Type t) { if (t.hasError()) { return false; } else if (! c().isArithmetic(t)) { runtime.error("invalid " + toDescription(n) + " where arithmetic value required", n); return false; } else { return true; } } /** * Ensure that the specified note with the specified type represents * an integer. * * @param n The node. * @param t The type. * @return <code>true</code> if the specified node represents an * integer. */ public boolean ensureInteger(Node n, Type t) { if (t.hasError()) { return false; } else if (! c().isIntegral(t)) { runtime.error("invalid " + toDescription(n) + " where integer required", n); return false; } else { return true; } } /** * Convert the specified node representing an operand to its * description. * * @param n The node. * @return The corresponding description. */ public static String toDescription(Node n) { final GNode node = GNode.cast(n); if (node.hasName("PrimaryIdentifier")) { return "variable '" + node.getString(0) + "'"; } else if (node.hasName("DirectComponentSelection") || node.hasName("IndirectComponentSelection")) { return "field '" + node.getString(1) + "'"; } else if (node.hasName("IndirectionExpression")) { final GNode child = node.getGeneric(0); if (child.hasName("PrimaryIdentifier")) { return "object '*" + child.getString(0) + "'"; } else { return "location"; } } else if (node.hasName("TypeName")) { return "type name"; } else { return "operand"; } } /** * Convert the specified node representing a function to its * name. * * @param n The node. * @return The corresponding name or <code>null</code> if the name * cannot be determined. */ public static String toFunctionName(Node n) { final GNode node = GNode.cast(n); if (node.hasName("PrimaryIdentifier")) { return node.getString(0); } else if (node.hasName("DirectComponentSelection") || node.hasName("IndirectComponentSelection")) { return node.getString(1); } else if (node.hasName("indirectionExpressiion")) { final GNode child = node.getGeneric(0); if (child.hasName("PrimaryIdentifier")) { return child.getString(0); } else { return null; } } else { return null; } } /** * Determine whether the specified scope name represents a function * or macro scope. * * @param name The name. * @return <code>true</code> if the specified name represents a * function or macro scope. */ public static boolean isFunctionScope(String name) { return (SymbolTable.isFunctionScopeName(name) || SymbolTable.isMacroScopeName(name)); } /** * Look up the specified name in the scope for extern declarations. * * @param name The name. * @return The corresponding type or <code>null</code> if no such * binding exists. */ public Type lookupExtern(String name) { final Scope scope = table.getScope(EXTERN_PATH); return null == scope ? null : (Type)scope.lookupLocally(name); } /** * Define the specified name in the scope for extern declarations. * * @param name The name. * @param type The type. */ public void defineExtern(String name, Type type) { Scope scope = table.getScope(EXTERN_PATH); // If the extern scope does not exist, create it. if (null == scope) { final Scope current = table.current(); table.setScope(table.root()); table.enter(EXTERN_SCOPE); scope = table.current(); table.setScope(current); } scope.define(name, type); } /** * Report a previous declaration or definition. If the specified * type has a location, this method prints that location as a * previous declaration or definition. * * @param name The name. * @param type The type. */ public void reportPrevious(String name, Type type) { // If the type has a location, print the error message. if (type.hasLocation()) { runtime.errConsole().loc(type). p(": error: previous "); if (type.hasAttribute(Constants.ATT_MACRO) || type.hasAttribute(Constants.ATT_DEFINED)) { runtime.errConsole().p("definition"); } else { runtime.errConsole().p("declaration"); } runtime.errConsole().p(" of '").p(name).pln("' was here").flush(); } } /** * Report a previous declaration or definition of the specified * tagged type. If the specified type has a location, this method * prints that location as a previous declaration or definition. * * @param type The type. */ public void reportPreviousTag(Type type) { final Tagged tag = type.toTagged(); if (type.hasLocation()) { runtime.errConsole().loc(type). p(": error: previous "); if (null != tag.getMembers()) { runtime.errConsole().p("definition"); } else { runtime.errConsole().p("declaration"); } runtime.errConsole().p(" of '").p(tag.getName()).p("' was here").flush(); } } }