/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.internal.as.codegen; import static org.apache.flex.abc.ABCConstants.OP_findproperty; import static org.apache.flex.abc.ABCConstants.OP_findpropstrict; import static org.apache.flex.abc.ABCConstants.OP_getlex; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.semantics.Instruction; import org.apache.flex.abc.semantics.InstructionFactory; import org.apache.flex.abc.semantics.Metadata; import org.apache.flex.abc.semantics.MethodBodyInfo; import org.apache.flex.abc.semantics.MethodInfo; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.Namespace; import org.apache.flex.abc.semantics.Nsset; import org.apache.flex.abc.semantics.PooledValue; import org.apache.flex.abc.semantics.Trait; import org.apache.flex.abc.visitors.IABCVisitor; import org.apache.flex.abc.visitors.IMetadataVisitor; import org.apache.flex.abc.visitors.IMethodBodyVisitor; import org.apache.flex.abc.visitors.IMethodVisitor; import org.apache.flex.abc.visitors.ITraitVisitor; import org.apache.flex.abc.visitors.ITraitsVisitor; import org.apache.flex.abc.visitors.IVisitor; import org.apache.flex.compiler.common.IMetaInfo; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IParameterDefinition; import org.apache.flex.compiler.definitions.metadata.IMetaTag; import org.apache.flex.compiler.definitions.metadata.IMetaTagAttribute; import org.apache.flex.compiler.internal.definitions.DefinitionBase; import org.apache.flex.compiler.internal.definitions.metadata.MetaTag; import org.apache.flex.compiler.internal.definitions.metadata.MetaTagAttribute; import org.apache.flex.compiler.internal.semantics.MethodBodySemanticChecker; import org.apache.flex.compiler.internal.semantics.SemanticUtils; import org.apache.flex.compiler.internal.tree.as.BaseVariableNode; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.tree.as.IdentifierNode; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.tree.as.ILanguageIdentifierNode; import org.apache.flex.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind; import com.google.common.collect.ImmutableList; public class LexicalScope { /** The "*" type. */ public static final Name anyType = null; /** Constant initializer not present. */ public static final Object noInitializer = null; /** * Manifest constant for "debug line number not known." */ public static final int DEBUG_LINE_UNKNOWN = -1; private static final Name NAME_Array = new Name(IASLanguageConstants.Array); private static final Name NAME_arguments = new Name(IASLanguageConstants.arguments); private static final IMetaInfo[] EMPTY_META_INFO = new IMetaInfo[0]; /** * Utility class to manage the allocation and merging of temps within * lexical scopes. */ protected static class TempManager { /** * Temporary registers allocated at this scope. */ private final ArrayList<Binding> allocatedTemps; /** * Allocated temporary registers that are free for re-use. */ private final ArrayList<Binding> freeTemps; /** * Index used to create unique names for temporary registers. * @warn does not correspond to the temp's register number. */ private int tempNum; /** * default constructor */ protected TempManager() { allocatedTemps = new ArrayList<Binding>(); freeTemps = new ArrayList<Binding>(); tempNum = 0; } /** * Construct a TempManager which can be merged back into the supplied * tempManager. This basically means that the temp numbering will never * overlap. * * @param tempManager the TempManager which this tempManager may be merged into */ protected TempManager(TempManager tempManager) { this(); tempNum = tempManager.tempNum; } protected Binding allocateTemp(boolean reuse_free) { Binding result; if ( this.freeTemps.isEmpty() || !reuse_free ) { result = new Binding(null, new Name("Temp #" + tempNum++), null); result.setIsLocal(true); addAllocatedTemp(result); } else { result = this.freeTemps.remove(0); } return result; } protected void addAllocatedTemp(Binding temp) { this.allocatedTemps.add(temp); } protected void addAllocatedTemps(ImmutableList<Binding> temps) { for (Binding temp : temps) this.allocatedTemps.add(temp); } protected ImmutableList<Binding> getAllocatedTemps() { return ImmutableList.copyOf(this.allocatedTemps); } protected void clearAllocatedTemps() { this.allocatedTemps.clear(); } protected void releaseTemp(Binding temp) { assert( !freeTemps.contains(temp) ): "Temp " + temp + " is already freed."; freeTemps.add(temp); } protected int initializeTempRegisters(int base) { for (Binding temp: allocatedTemps) { temp.setLocalRegister(base++); } return base; } protected void mergeTemps(TempManager tempManagerToMerge) { // first release all the temps in the TempManager being merged for (Binding temp : tempManagerToMerge.allocatedTemps) { if (!tempManagerToMerge.freeTemps.contains(temp)) tempManagerToMerge.releaseTemp(temp); } tempNum += tempManagerToMerge.tempNum; freeTemps.addAll(tempManagerToMerge.freeTemps); allocatedTemps.addAll(tempManagerToMerge.allocatedTemps); } } /** * Back reference to the global lexical scope */ private final GlobalLexicalScope globalLexicalScope; /** * Local variables. */ private final Map <String,Binding>localBindings = new HashMap<String,Binding>(); /** * Inlined functions local variables. */ private List<Binding> inlinedBindings = null; /** * LexicalScope (if present) that lexically encloses this one. */ private final LexicalScope enclosingFrame; /** * The MethodInfo of this scope's anonymous function, * or null if this is not an anonymous function scope. */ private MethodInfo methodInfo = null; /** * Set if the function needs "this" on the scope stack. */ private boolean needsThis = true; /** * Parameter types of this scope's explicit parameters, * not including any "..." parameter. */ private Vector<Name> paramTypes = null; /** * Set if the function has a "..." parameter. */ private boolean hasRestParam = false; /** * All implicit or explicit parameters, including a "..." parameter, * defined by this scope's method on activation. */ private final ArrayList<Name> allParams = new ArrayList<Name>(); /** * This scope's ControlFlowContextManager. * The LexicalScope manages the ControlFlowContextManager * because the CFCM depends on the LexicalScope to manage * its temporaries. */ private ControlFlowContextManager flowMgr = null; /** * The Binding that represents the register that the activation object is stored in. * This is used to restore the activation object when necessary - at the beginning * of a catch block for example. The value is null if the activation object is not * stored in a local. */ private Binding activationStorage = null; /** * Hasnext2Wapper objects (hasnext2 mangement objects) * in use by this scope's code. These are kept here so * that the hasnext2 instruction can be informed of its * allocated temps at function wrapup time. */ private final ArrayList<Hasnext2Wrapper> hasnexts = new ArrayList<Hasnext2Wrapper>(); /** * The syntax tree node for which this lexical scope was created, which is * also the node that scopes the visibility of labels for goto statements. * This can be null. */ private IASNode initialControlFlowRegionNode; /** * Next slot id to allocate for traits. Start at 1, as * 0 denotes VM allocated id. */ private int slotId = 1; /** * Initialization instructions for hoisted definitions * (such as functions) that appear in the body of this * scope but should occur at the routine prologue. * Contrast initInsns, which is set only on scopes * that collect results from many routines or code * snippets (e.g., class scopes, the global scope). */ private InstructionList hoistedInitInstructions = null; /** * Local scope of the function (if any) under compilation. */ private IASScope localScope = null; /** * Names explicitly declared at this scope. */ private final Set<Name>declaredVariables = new HashSet<Name>(); /** * Debug info: current file name. */ private String currentDebugFile = null; /** * Debug info: current line number. */ private int currentDebugLine = -1; /** * The nesting state of a function. */ public enum NestingState { NotNested, Nested }; /** Nesting state of this LexicalScope. */ private NestingState nestingState = NestingState.NotNested; /** * {@link List} of {@link IVisitor}'s whose * {@link IVisitor#visitEnd()} method calls have been deferred * until we are known to be on the main code generation thread. */ private final List<IVisitor>deferredVisitEnds = new LinkedList<IVisitor>(); /** Enum to keep track of what references/decls of 'arguments' we have seen so far. */ private enum ArgumentsState { INITIAL, // seen neither a ref or a decl REFERENCED, // seen a ref, but not decl DECLARED, // seen a decl } /** * The current state of 'arguments' referenced and declared */ private ArgumentsState argumentsState = ArgumentsState.INITIAL; /** * Variables with visibility to nested scopes * are defined here. */ ITraitsVisitor traitsVisitor = null; /** * If this LexicalScope is the outer scope of a method * compilation, this is its method body visitor. * @see #getMethodBodyVisitor() */ IMethodBodyVisitor methodBodyVisitor = null; /** * If this LexicalScope is the outer scope of a method * compilation, this is its method visitor. */ IMethodVisitor methodVisitor = null; /** * The semantic checker for this compilation. */ protected MethodBodySemanticChecker methodBodySemanticChecker = null; /** * The temporary register manager for this lexical scope */ private final TempManager tempManager; /** * constructor which should only ever be called * by {@link GlobalLexicalScope} */ protected LexicalScope() { assert (this instanceof GlobalLexicalScope) : "LexicalScope() should only ever be called by GlobalLexicalScope()"; this.globalLexicalScope = (GlobalLexicalScope)this; // GlobalLexicalScope should never have an enclosingFrame this.enclosingFrame = null; this.tempManager = new TempManager(); } /** * constructor which is called whenever pushing a new * lexical scope * * @param enclosingFrame the parent frame */ protected LexicalScope(LexicalScope enclosingFrame) { this(enclosingFrame, false); } /** * constructor which is called whenever pushing a new * lexical scope, and the temps are potentially going * to be shared with enclosing scope. * * @param enclosingFrame the parent frame */ protected LexicalScope(LexicalScope enclosingFrame, boolean mergableTempManager) { assert (!(this instanceof GlobalLexicalScope)) : "LexicalScope(LexicalScope) should never be called by GlobalLexicalScope()"; this.enclosingFrame = enclosingFrame; this.globalLexicalScope = enclosingFrame.globalLexicalScope; this.nestingState = enclosingFrame.nestingState; if (mergableTempManager) this.tempManager = new TempManager(enclosingFrame.tempManager); else this.tempManager = new TempManager(); } /* * ** Variable Management -- Creation ** */ /** * Create a variable with potential visibility * to nested scopes. * * @param var The variable binding */ public void makeVariable(Binding var) { makeVariable(var, anyType, EMPTY_META_INFO); } /** * Create a variable with potential visibility * to nested scopes. * * @param var The variable binding * @param var_type The Name of the type of the variable * @param meta_tags The metadata on the variable */ public void makeVariable(Binding var, Name var_type, IMetaInfo[] meta_tags) { makeVariable(var, var_type, meta_tags, noInitializer); } /** * Create a variable with potential visibility * to nested scopes. * * @param var The variable binding * @param var_type The Name of the type of the variable * @param meta_tags The metadata on the variable * @param initializer An initializer. null if no initializer. only consts can have an initializer */ public void makeVariable(Binding var, Name var_type, IMetaInfo[] meta_tags, Object initializer) { makeVariable(var, var_type, meta_tags, initializer, VariableMutability.Default); } /** * Mutability settings for a variable declaration. */ public enum VariableMutability { Default, Variable, Constant }; /** * Create a variable with potential visibility * to nested scopes. * * @param var The variable binding * @param var_type The Name of the type of the variable * @param meta_tags The metadata on the variable * @param initializer An initializer. null if no initializer. only consts can have an initializer * @param mutability - the caller's desired mutability. */ public void makeVariable(Binding var, Name var_type, IMetaInfo[] meta_tags, Object initializer, VariableMutability mutability) { assert (initializer == null || SemanticUtils.isConstDefinition(var.getDefinition())) : "only consts can have an initializer"; Name var_name = var.getName(); if ( var_name == null || this.declaredVariables.contains(var_name) ) { return; } declareVariableName(var_name); IDefinition var_def = var.getDefinition(); if ( this.localsInRegisters() ) { // The Binding will already be in localBindings if it was referenced // before it was set; if not, add it. if ( !this.localBindings.containsKey(var_name.getBaseName()) ) { if ( var.getNode() instanceof BaseVariableNode ) { IExpressionNode name_node = ((BaseVariableNode)var.getNode()).getNameExpressionNode(); Binding var_binding = getBinding(name_node, var_name, var_def); this.localBindings.put(var_name.getBaseName(), var_binding); var_binding.setIsLocal(true); } else { // Error case, synthesize a Binding. Binding error_binding = new Binding(var.getNode(), new Name("error in binding"), null); this.localBindings.put(var_name.getBaseName(), error_binding); } } } else { int trait_kind; if ( SemanticUtils.isConstDefinition(var_def) || mutability == VariableMutability.Constant ) trait_kind = ABCConstants.TRAIT_Const; else trait_kind = ABCConstants.TRAIT_Var; assert(this.traitsVisitor != null): "No traits"; ITraitVisitor tv = this.traitsVisitor.visitSlotTrait( trait_kind, ensureQName(var_name), getSlotId(var), var_type, initializer ); tv.visitStart(); processMetadata(tv, meta_tags); this.deferredVisitEnds.add(tv); } } private int getSlotId(Binding binding) { // only need to allocate slot ids when there's an activation // in the room, as we need to be able to reference the id when codegening setslot // and debug ops. otherwise just let the VM handle the allocation. if (!needsActivation()) return ITraitsVisitor.RUNTIME_SLOT; binding.setSlotId(slotId++); return binding.getSlotId(); } /** * Helper method to determine if a variable binding has already been referenced. When called before * makeVariable, it can be used to determine if the var was referenced before it was initialized, which means * it will need hoisted init instructions to make sure it has the right initial value. * @param var_name The name for the variable * * @return true if the binding for var_name is already in the localBindings table */ public boolean needsHoistedInitInsns (Name var_name, boolean has_initializer) { if ( this.localsInRegisters() ) { // If it's a re-decl of a parameter, then don't add hoisted init instructions if( allParams.contains(var_name) ) return false; // If there is no explicit initializer, then we need to init the local // could do better here - if it's never ref'ed before being assigned to // might be able to omit the initializer too if( !has_initializer ) return true; // The Binding will already be in localBindings if it was referenced // before it was set Binding b = this.localBindings.get(var_name.getBaseName()); return b != null ; } return false; } /** * Add a variable name to the set of known names. * @param n - the Name to add. */ public void declareVariableName(Name n) { checkForArgumentsDecl(n); this.declaredVariables.add(n); } /** * Create a bindable property. This will create a getter/setter that will do * the bindable magic, and a backing property that will store the actual * value. * <p> * This method must only be called on the thread that started code * generation of the syntax tree that contains the bindable variable. This * constraint is met today, because we do not generate code for classes in * parallel and bindable variables are always be class members. * * @param metaTags The IMetaTagsNode representing the metadata nodes for * this definition. If it is null, this method will ask the varDef for the * metadata - some callers of this method will not have an MetaTagsNode in * the AST, and want the metadata from the definition */ public void makeBindableVariable(Binding var, Name var_type, IMetaInfo[] metaTags) { Name var_name = var.getName(); if ( var_name == null || this.declaredVariables.contains(var_name) ) { return; } declareVariableName(var_name); Name backingPropertyName = BindableHelper.getBackingPropertyName(var_name); assert(this.traitsVisitor != null): "No traits"; this.traitsVisitor.visitSlotTrait( ABCConstants.TRAIT_Var, ensureQName(backingPropertyName), getSlotId(var), var_type, noInitializer ); IDefinition bindableVarDef = var.getDefinition(); generateBindableGetter(bindableVarDef, var_name, backingPropertyName, var_type, metaTags); generateBindableSetter(bindableVarDef, var_name, backingPropertyName, var_type, metaTags); } public void generateBindableGetter(IDefinition bindableVarDef, Name var_name, Name backingPropertyName, Name var_type, IMetaInfo[] metaTags) { ITraitVisitor getterTv = BindableHelper.generateBindableGetter(this, var_name, backingPropertyName, var_type); IMetaTag gotoDefinitionMetaTag = MetaTag.createGotoDefinitionHelp(bindableVarDef, bindableVarDef.getContainingFilePath(), Integer.toString(bindableVarDef.getNameStart()), false); metaTags = MetaTag.addMetaTag(metaTags, gotoDefinitionMetaTag); // If we have an IMetaTagsNode use that, otherwise get the metadata from the definition processMetadata(getterTv, metaTags); if (bindableVarDef.isOverride()) getterTv.visitAttribute(Trait.TRAIT_OVERRIDE, Boolean.TRUE); // We don't codegen classes in parallel right now, // so we know that we are on the main code generation thread // because bindable variables are always members of a class. // Since we know are on the main code generation thread we can immediately // call visitEnd here and the vistEnd calls in generateBindableSetter // are ok too. getterTv.visitEnd(); } public void generateBindableSetter(IDefinition bindableVarDef, Name var_name, Name backingPropertyName, Name var_type, IMetaInfo[] metaTags) { ITraitVisitor setterTv = BindableHelper.generateBindableSetter(this, var_name, backingPropertyName, var_type, bindableVarDef); IMetaTag gotoDefinitionMetaTag = MetaTag.createGotoDefinitionHelp(bindableVarDef, bindableVarDef.getContainingFilePath(), Integer.toString(bindableVarDef.getNameStart()), false); metaTags = MetaTag.addMetaTag(metaTags, gotoDefinitionMetaTag); processMetadata(setterTv, metaTags); if (bindableVarDef.isOverride()) setterTv.visitAttribute(Trait.TRAIT_OVERRIDE, Boolean.TRUE); setterTv.visitEnd(); } /** * Workaround namerezo's habit of returning a multiname * for a local definition if the definition was referenced * before it was declared. * @param n - the name per name resolution. * @return the input name if it was a QName, or a QName * with the same base name and a more or less appropriate * qualifier if it was not a QName. */ private Name ensureQName(Name n) { Name result; if ( n != null ) { if ( n.getKind() == ABCConstants.CONSTANT_Qname ) { result = n; } else { result = new Name(ABCConstants.CONSTANT_Qname, new Nsset(new Namespace(ABCConstants.CONSTANT_PackageNs)), n.getBaseName()); } } else { result = null; } return result; } /** * Declare and define a namespace. * @param ns_binding - the namespace declaration's binding. * @param ns_init - the namespace definition's initial AET Namespace. May not be null. */ public void makeNamespace(Binding ns_binding, Namespace ns_init) { Name ns_name = ns_binding.getName(); // TODO: Semantic analysis will issue a diagnostic. // Repair locally by ignoring the second declaration. if ( this.declaredVariables.contains(ns_name)) return; else declareVariableName(ns_name); assert ( ns_init != null ); assert(this.traitsVisitor != null): "No traits"; if ( this.localsInRegisters() ) { this.localBindings.put(ns_name.getBaseName(), ns_binding); ns_binding.setIsLocal(true); } else { this.traitsVisitor.visitSlotTrait(ABCConstants.TRAIT_Const, ns_name, getSlotId(ns_binding), anyType, ns_init); } } /* * ** Variable Management -- Local Variables ** */ /** * Declare an explicit parameter. * @param def - the parameter's definition. * @param param_type - the name of the parameter's type. */ public void makeParameter(IDefinition def, Name param_type) { Name param_name = ((DefinitionBase)def).getMName(getProject()); if (((IParameterDefinition)def).isRest()) hasRestParam = true; else getParamTypes().add(param_type); allParams.add(param_name); Binding var = getBinding(def); makeVariable(var, param_type, EMPTY_META_INFO); } /** * Declare an implicit parameter. * The only implicit parameter in AS3 is <code>arguments</code>. * @param param_name - the parameter's name. * @param param_type - the name of the parameter's type. */ private void makeImplicitParameter(Name param_name, Name param_type) { allParams.add(param_name); // Add this parameter's name to the set of // locally defined variables. Binding var = getBinding(null, param_name, null); makeVariable(var, param_type, EMPTY_META_INFO); } /** * Add a default value to the currently compiling method. * @see #makeParameter which is called first. * @note Why is this not folded into makeParameter()? Because * null is a valid initial value. */ public void addDefaultValue(PooledValue value) { if( value != null ) this.methodInfo.addDefaultValue(value); } /** * @return true if "this" needed. */ public boolean needsThis() { return this.needsThis; } /** * @return the types of this scope's function parameters. */ public Vector<Name> getParamTypes() { if ( this.paramTypes == null) this.paramTypes = new Vector<Name>(); return this.paramTypes; } /** * @return this scope's MethodInfo, or null if none present. */ public MethodInfo getMethodInfo() { return this.methodInfo; } /** * sets this scope's MethodInfo, */ public void setMethodInfo(MethodInfo methodInfo) { assert (this.methodInfo == null) : "trying to set an already set methodInfo"; this.methodInfo = methodInfo; // Setting a MethodInfo means we're generating a method body, // so we need a fresh MethodBodySemanticChecker. this.methodBodySemanticChecker = new MethodBodySemanticChecker(this); } /** * Reset this scope's MethodInfo to null; called from * a ClassDirectiveProcessor's static initialization * error recovery logic. */ public void resetMethodInfo() { this.methodInfo = null; this.methodBodySemanticChecker = null; } /** * Allocate a temporary register. The temp is * assumed to hold the "*" type. * @return the temp register's Binding. * A temp's Binding is used to hold * the get/set/kill, etc., instructions * that relate to it. */ public Binding allocateTemp() { return allocateTemp(true); } /** * Allocate a temporary register. * @param reuse_free - re-use a free temp if set. * @return the temp's local number. */ public Binding allocateTemp(boolean reuse_free) { return tempManager.allocateTemp(reuse_free); } /** * Release a temporary register. * @param temp - the temp's Binding. * @see #allocateTemp() */ public void releaseTemp(Binding temp) { tempManager.releaseTemp(temp); } /** * Merge the temps from the specified lexical scope into this lexical scope. * * @param scopeToMerge the scope containing the temps to merge */ protected void mergeTemps(LexicalScope scopeToMerge) { tempManager.mergeTemps(scopeToMerge.tempManager); } /** * Add a collection of bindings to the collection of inlined bindings * in this lexical scope. * * @param bindings the bindings to add */ protected void addInlinedBindings(Collection<Binding> bindings) { if (inlinedBindings == null) inlinedBindings = new ArrayList<Binding>(); inlinedBindings.addAll(bindings); } /** * Add a collection of hasnext wrappers to the collection of * hasnexts in this lexical scope. * * @param nexts the hasnexts to add */ protected void addHasNexts(Collection<Hasnext2Wrapper> nexts) { hasnexts.addAll(nexts); } /** * @return collection of Hasnext2Wrappers */ protected Collection<Hasnext2Wrapper> getHasNexts() { return hasnexts; } /** * @return All the local bindings in the lexical scope. */ protected Collection<Binding> getLocalBindings() { return localBindings.values(); } /** * Allocate a hasnext2 management object. * @return the new hasnext2 management object. */ Hasnext2Wrapper hasnext2() { Hasnext2Wrapper hasnext = new Hasnext2Wrapper(); this.hasnexts.add(hasnext); hasnext.stem_temp = allocateTemp(false); hasnext.index_temp = allocateTemp(false); return hasnext; } /** * Hasnext2Wapper is a struct-like class * that holds the temps and instruction * that comprise a hasnext2 operation. */ class Hasnext2Wrapper { Binding stem_temp, index_temp; Instruction instruction = InstructionFactory.getHasnext2Instruction(); /** * Release the temps from a hasnext2. */ public void release() { releaseTemp(index_temp); releaseTemp(stem_temp); } } /** * @return the LexicalScope's corresponding ASScope. */ public IASScope getLocalASScope() { return this.localScope; } /** * Set this LexicalScope's corresponding ASScope. */ public void setLocalASScope(IASScope scope) { assert this.localScope == null || this.localScope == scope : "Local scope already set."; this.localScope = scope; } /** * @return true if the candidate definition is local to this LexicalScope's corresponding ASScope. * @param candidate - the IDefinition under examination. */ public boolean isLocalDefinition(IDefinition candidate) { return candidate != null && candidate.getContainingScope() != null && candidate.getContainingScope() == this.localScope; } /* * ** Name Management ** */ /** * Resolve a name, and return a Binding for it. * The Binding will include information such as * the multiname (or qname), and local register info * @param id The IdentifierNode you need a Binding for * @return A Binding */ public Binding resolveName(IdentifierNode id) { ICompilerProject project = getProject(); if(id instanceof ILanguageIdentifierNode) { ILanguageIdentifierNode lang_id = (ILanguageIdentifierNode)id; LanguageIdentifierKind kind = lang_id.getKind(); if ( kind == LanguageIdentifierKind.THIS ) { return getThisBinding(id); } else if ( kind == LanguageIdentifierKind.ANY_TYPE ) { return new Binding(id, id.getMName(project), id.resolve(project)); } else if ( kind == LanguageIdentifierKind.VOID ) { return new Binding(id, id.getMName(project), id.resolve(project)); } else { assert false: "Unhandled LanguageIdentifierKind " + kind; } } IDefinition def = id.resolve(project); Name name; if ( id.getName().length() == 0 ) name = new Name(getGlobalScope().getSyntheticName("anonymous")); else name = id.getMName(project); if( SemanticUtils.isRefToClassBeingInited(id, def) && !insideInlineFunction()) { // Return a slightly modified binding // this binding will have the name and node that created the binding, // but its local will be set to 0, so that getlocal0 will be used instead of a name lookup // this is because we are in code that may be run as part of the class cinit method, and the // class slot won't have been initialized yet, so a named lookup will fail. // class C // { // static var a = C; //C must be codegen'ed as getlocal0 instead of findprop, getprop // } Binding b = getBinding(id, name, def); b.setIsLocal(true); b.setLocalRegister(0); return b; } else if ( name != null ) { return getBinding(id, name, def); } else { // Error case, return trivial name return new Binding(id, new Name(id.getName()), def); } } /** * Get or create the Binding for an IDefinition. * @param def - the IDefinition. * @return said Binding. */ public Binding getBinding(IDefinition def) { return getBinding(null, getNameFromDefinition(def), def); } /** * Get or create the Binding for a Name and its associated IDefinition. * @param name - the name under consideration. * @param def - the name's IDefinition. * @return said Binding. */ public Binding getBinding(IASNode node, Name name, IDefinition def) { if (name != null && isLocalDefinition(def) && this.localBindings.containsKey(name.getBaseName()) ) { Binding b = localBindings.get(name.getBaseName()); // return a new Binding to make sure it has the right Node. return new Binding(node, b); } else { Binding result = new Binding(node, name, def); if ( isLocalDefinition(def) ) { this.localBindings.put(name.getBaseName(), result); if ( this.localsInRegisters() ) result.setIsLocal(true); } checkForArgumentsReference(result); return result; } } /** * Determine if this method needs to set the NEEDS_Arguments flag. It only needs * to if arguments is referenced somewhere in the method, and arguments is not declared in the method. * @return */ private boolean needsImplicitArguments () { // We only need the arguments flag if arguments was referenced, but never declared. return argumentsState == ArgumentsState.REFERENCED; } /** * Check if the node and name passed in are declaration of a property named 'arguments'. * Necessary so we can generate NEEDS_Arguments flag on the MethodInfo if needed. * @param name The Name of the reference */ private void checkForArgumentsDecl (Name name) { if( name != null && IASLanguageConstants.arguments.equals(name.getBaseName()) ) { switch(argumentsState) { case INITIAL: case REFERENCED: argumentsState = ArgumentsState.DECLARED; break; case DECLARED: break; } } } /** * Check if the binding is a potential reference to the arguments object * Necessary so we can generate NEEDS_Arguments flag on the MethodInfo if needed. * @param b The node that produced the reference */ private void checkForArgumentsReference (Binding b) { if( SemanticUtils.isArgumentsReference(b) ) { switch(argumentsState) { case INITIAL: argumentsState = ArgumentsState.REFERENCED; break; case REFERENCED: case DECLARED: break; } } } /** * Get the local binding for the given name, if one exists * @param name the name of the local * @return the Binding for the local property, or null if there isn't one. */ Binding getLocalBinding(Name name) { if ( name != null ) return localBindings.get(name.getBaseName()); else return null; } /** * Extract a definition's name. */ private Name getNameFromDefinition(IDefinition idef) { DefinitionBase def = (DefinitionBase)idef; return def.getMName(getProject()); } /** * Fetch the Binding that represents "this." * @return said Binding. */ protected Binding getThisBinding(IdentifierNode id) { Binding thisBinding = new Binding(id, null, null); thisBinding.setIsLocal(true); thisBinding.setLocalRegister(0); return thisBinding; } /** * Get the property for the specified binding. Use this method rather * than using findprop directly, because inlining needs to do some magic * * @param binding Binding of the property to find * @param useStrict Use OP_findpropstrict * @return InstructionList */ public InstructionList findProperty(Binding binding, boolean useStrict) { return findProperty(binding.getName(), binding.getDefinition(), useStrict); } /** * Get the property for the specified name. Use this method rather * than using findprop directly, because inlining needs to do some magic * * @param name Name of the property to find * @param def Definition of the property to find * @param useStrict Use OP_findpropstrict * @return InstructionList */ public InstructionList findProperty(Name name, IDefinition def, boolean useStrict) { InstructionList result = new InstructionList(1); if (useStrict) result.addInstruction(OP_findpropstrict, name); else result.addInstruction(OP_findproperty, name); return result; } /** * Get the property value for the specified binding. Use this method rather * than using getlex directly, because inlining needs to do some magic * * @param binding Binding of the property to find * @return InstructionList */ public InstructionList getPropertyValue(Binding binding) { return getPropertyValue(binding.getName(), binding.getDefinition()); } /** * Get the property value for the specified name. Use this method rather * than using getlex directly, because inlining needs to do some magic * * @param name Name of the property to find * @param def Definition of the property to find * @return InstructionList */ public InstructionList getPropertyValue(Name name, IDefinition def) { InstructionList result = new InstructionList(1); result.addInstruction(OP_getlex, name); return result; } /** * @return this scope's enclosing scope. */ public LexicalScope getEnclosingFrame() { return enclosingFrame; } /** * Finalize a method's declarations. * @param has_body - true if the method * has a non-empty body. * @return an InstructionList containing * the method's prologue instructions. */ InstructionList finishMethodDeclaration(final boolean hasBody, String source_path) { InstructionList result = new InstructionList(); // TODO: The scope stack can be optimized out if // no instruction (e.g., findprop) requires one. if ( this.needsThis ) // && this.needsScopeStack() ) { result.addInstruction(ABCConstants.OP_getlocal0); result.addInstruction(ABCConstants.OP_pushscope); } // Add a debugfile to start the method. if( source_path != null ) { String encoded_filename = getGlobalScope().getEncodedDebugFile(source_path); result.addInstruction(ABCConstants.OP_debugfile, encoded_filename); } // TODO: Set this more intelligently; // it's only necessary if arguments is read but not written. if (needsImplicitArguments()) { this.setNeedsArguments(); } if ( this.needsArguments() ) { makeImplicitParameter(NAME_arguments, NAME_Array); } if ( !isGlobalScope() ) { // temp_register_count is the total number of temp registers // which need to be initialized. int temp_register_count = 0; if ( this.needsActivation() ) { // Create a new activation object, store it in a local (so it can be restored // if needed later, like in a catch block), and push the activation object // onto the scope stack. result.addInstruction(ABCConstants.OP_newactivation); if( this.activationStorage != null ) { result.addInstruction(ABCConstants.OP_dup); result.addInstruction(this.activationStorage.setlocal()); } result.addInstruction(ABCConstants.OP_pushscope); // TODO: More fine-grained determination // of which parameters need to live in the activation record. for ( int param_num = 0; param_num < this.allParams.size(); param_num++ ) { Name param_name = this.allParams.get(param_num); result.addInstruction(ABCConstants.OP_findpropstrict, param_name); // Parameter numbering in the AVM is 1-based // since local 0 holds "this." result.addInstruction(ABCConstants.OP_getlocal, param_num + 1); result.addInstruction(ABCConstants.OP_setproperty, param_name); } temp_register_count = this.allParams.size() + 1; } else { // Find the bindings for the parameters // and give them their corresponding local number. // There is a complication, however. The same parameter name may occur more than // Once in the parameter list. In this case, ECMA says the last one wins. // So in the loop below will go from last parameter to first, and skip any // that we have already seen Set<String> uniqueParamNames = new HashSet<String>(); for (int param_num = this.allParams.size()-1; param_num >= 0; param_num--) { Name param_name = this.allParams.get(param_num); String param_base_name = param_name.getBaseName(); Binding param_binding = this.localBindings.get(param_base_name); if (param_binding!=null && !uniqueParamNames.contains(param_base_name)) { param_binding.setLocalRegister(param_num+1); uniqueParamNames.add(param_base_name); } } // Now traverse the entire set of local bindings and // give local numbers to those that weren't parameters. int local_num = this.allParams.size() + 1; for ( Binding local_binding : this.localBindings.values() ) { if ( ! local_binding.localNumberIsSet() ) { local_binding.setLocalRegister(local_num++); } } temp_register_count = local_num; } // Normally nothing should be called after initializeTempRegisters(), as bad things will happen // if another register is added. The addDebugNamesToDefinitions() is a special case, as it needs // to have all registers set. this.initializeTempRegisters(temp_register_count); } // Need to call this after initializeTempRegisters to ensure that the registers are set in all cases. addDebugNamesToDefinitions(result); if ( this.hasHoistedInitInstructions() ) result.addAll(this.getHoistedInitInstructions()); return result; } /** * Finish a class static initializer method. * @param cinit_insns - initialization instructions. * Classes may have ad-hoc static initialization code, * so the content of this list is arbitrary. */ public void finishClassStaticInitializer(InstructionList cinit_insns) { this.initializeTempRegisters(1); addDebugNamesToDefinitions(cinit_insns); } private void addDebugNamesToDefinitions(InstructionList result) { if (needsActivation()) { for (int param_num = 0; param_num < allParams.size(); param_num++) { // create debug op codes for the params. if we don't do this, builder won't // filter out the param names, and we get will show the formals as _argN and // then again in the activation record. addDebugNameToDefinition(allParams.get(param_num).getBaseName(), param_num, result); } // add the debug info for an activation record. One debug op tells the VM to add the data for whole // activation record. Need to set the name of the debug field to the funcName$0, as builder keys of // this when displaying the variables. if (activationStorage != null) addDebugNameToDefinition(methodInfo.getMethodName() + "$0", activationStorage.getLocalRegister() - 1, result); } else { for (Binding local_binding : localBindings.values()) { if (SemanticUtils.isConstDefinition(local_binding.getDefinition())) continue; // Need to -1 the index, as the local register starts at 1 to handle the this // but that's not included in the bindings list int index = local_binding.getLocalRegister() - 1; addDebugNameToDefinition(local_binding.getName().getBaseName(), index, result); } } } private void addDebugNameToDefinition(String name, int index, InstructionList result) { Object[] args = new Object[] { ABCConstants.DI_LOCAL, name, index }; result.addInstruction(ABCConstants.OP_debug, args); } /** * Give all temporaries a register number. */ public void initializeTempRegisters(int base) { base = tempManager.initializeTempRegisters(base); if (inlinedBindings != null) { for (Binding inlined_binding : inlinedBindings) { if (!inlined_binding.localNumberIsSet()) inlined_binding.setLocalRegister(base++); } } // Now that all temps have been assigned // to registers, give the hasnext2 insns // their proper operands. for ( Hasnext2Wrapper hasnext: this.hasnexts ) { hasnext.instruction.setTempRegisters( new Object[] { hasnext.stem_temp.getLocalRegister(), hasnext.index_temp.getLocalRegister() } ); } } /* * ** Scope Management ** */ /** * Push a new lexical scope. * @return the new scope. */ public LexicalScope pushFrame() { return new LexicalScope(this); } /** * Push a new lexical scope for a function being inlined. * @return the new scope. */ public InlineFunctionLexicalScope pushInlineFunctionFrame(IASScope containingScope, boolean storeClassBinding, FunctionNode functionNode) { return new InlineFunctionLexicalScope(this, containingScope, storeClassBinding, functionNode); } /** * Test whether we are currently inside the scope of a function being inlined. * @return true if currently inside an inline function. */ public boolean insideInlineFunction() { return false; } /** * Pop the current lexical scope. * @return the enclosing lexical scope. * @post this LexicalScope still remembers its enclosing LexicalScope. */ public LexicalScope popFrame() { assert(!isGlobalScope()): "popping global scope"; LexicalScope result = this.enclosingFrame; result.deferredVisitEnds.addAll(deferredVisitEnds); return result; } /** * @return true if this scope is the root of a scope chain. */ public boolean isGlobalScope() { return false; } /** * @return the root of this scope chain. */ public final GlobalLexicalScope getGlobalScope() { return globalLexicalScope; } /** * @return true if this scope's method * needs an activation record. */ public boolean needsActivation() { boolean result = this.methodInfo != null && (this.methodInfo.getFlags() & ABCConstants.NEED_ACTIVATION) != 0; return result; } /** * Turn on thie scope's method's NEED_ACTIVATION flag; * this will cause the AVM to allocate an activation * object when the method is called. */ public void setNeedsActivation() { if ( this.methodInfo != null ) this.methodInfo.setFlags(this.methodInfo.getFlags() | ABCConstants.NEED_ACTIVATION); } /** * @return true if this scope stores its locals in registers. */ private boolean localsInRegisters() { return !needsActivation() && localScope != null; } /** * @return true if this scope's method * needs an "arguments" array. */ public boolean needsArguments() { return this.methodInfo != null && (this.methodInfo.getFlags() & ABCConstants.NEED_ARGUMENTS) != 0; } /** * Turn on this scope's method's NEED_ARGUMENTS flag; * this will cause the AVM to allocate an implicit * arguments parameter when the method is called. */ public void setNeedsArguments() { if ( this.methodInfo != null ) this.methodInfo.setFlags(this.methodInfo.getFlags() | ABCConstants.NEED_ARGUMENTS); } /** * Turn on this scope's NEED_REST flag. */ public void setNeedsRest() { assert this.methodInfo != null : "methodInfo should not be null here"; this.methodInfo.setFlags(this.methodInfo.getFlags() | ABCConstants.NEED_REST); } /** * Turn on this scope's SETS_DXNS flag. */ public void setSetsDxns() { if ( this.methodInfo != null ) { this.methodInfo.setFlags(this.methodInfo.getFlags() | ABCConstants.SETS_DXNS); } } /* * ** Control flow ** */ /** * Create a ControlFlowContextManager on demand. * @return the ControlFlowContextManager associated * with this scope. */ public ControlFlowContextManager getFlowManager() { if ( null == flowMgr ) flowMgr = new ControlFlowContextManager(this); return flowMgr; } /** * Fetch the Binding that refers to a local reserved for * this scope's function's activation record. * @return said Binding. Created if not already present. */ Binding getActivationStorage() { assert this.needsActivation(): "no activation storage present"; if ( this.activationStorage == null ) { this.activationStorage = new Binding(null, new Name("activation"),null); this.activationStorage.setIsLocal(true); this.tempManager.addAllocatedTemp(this.activationStorage); } return this.activationStorage; } /** * @return true if any hoisted init instructions * have been generated by the body of the routine. */ boolean hasHoistedInitInstructions() { return this.hoistedInitInstructions != null; } /** * Fetch the list used to hoist initialization * instructions to the top of the routine. * @return the hoisted init list; created on demand. * @see #hasHoistedInitInstructions() which checks * for hoisted instructions without creating them. */ InstructionList getHoistedInitInstructions() { if ( this.hoistedInitInstructions == null ) this.hoistedInitInstructions = new InstructionList(); return this.hoistedInitInstructions; } /** * @return the active IMethodBodyVisitor. */ IMethodBodyVisitor getMethodBodyVisitor() { IMethodBodyVisitor result = this.methodBodyVisitor; if ( result == null && this.enclosingFrame != null ) { result = this.enclosingFrame.getMethodBodyVisitor(); } assert result != null; return result; } /** * Set up this scope's data structures * that support anonymous functions. */ void declareAnonymousFunction() { declareNestedFunction(); setFunctionName(getGlobalScope().getSyntheticName("anonymous")); } /** * Set up this scope's data structures * that support named function closures. */ void declareFunctionObject(final String name) { assert name != null: "Name must be specified."; declareNestedFunction(); setFunctionName(name); } void declareNestedFunction() { setMethodInfo(new MethodInfo()); this.methodVisitor = getEmitter().visitMethod(this.methodInfo); this.methodVisitor.visit(); MethodBodyInfo mbi = new MethodBodyInfo(); mbi.setMethodInfo(this.methodInfo); this.methodBodyVisitor = this.methodVisitor.visitBody(mbi); this.methodBodyVisitor.visit(); this.traitsVisitor = this.methodBodyVisitor.visitTraits(); // nested functions don't push "this" onto // the scope stack. this.needsThis = false; // Note that this function is nested. this.nestingState = NestingState.Nested; } void setFunctionName(String func_name) { assert(this.methodInfo != null && this.methodInfo.getMethodName() == null); this.methodInfo.setMethodName(func_name); } /** * Add the method body to a nested function, * and finish generating it in other respects. */ void generateNestedFunction(InstructionList body) { assert this.methodInfo != null && this.methodInfo.getMethodName() != null: String.format( "this.methodInfo %s, this.methodInfo.getMethodName %s", this.methodInfo, this.methodInfo != null? this.methodInfo.getMethodName(): "n/a" ) ; // Finish generating the method: // Set its parameter types this.methodInfo.setParamTypes(getParamTypes()); // Set its NEED_REST flag if there is a ... parameter. if (this.hasRestParam) setNeedsRest(); // Set its NEED_ARGUMENTS flag if the method uses // the special 'arguments' implicit parameter. if ( !this.declaredVariables.contains(NAME_arguments) && this.localBindings.containsKey(NAME_arguments.getBaseName())) { setNeedsArguments(); } // Set its body's instructions. if ( body != null ) this.methodBodyVisitor.visitInstructionList(body); finishMethod(); } /** * Arrange for the {@link IVisitor#visitEnd()} methods of * any visitors related to this lexical scope to be called. */ private void finishMethod() { this.deferredVisitEnds.add(this.traitsVisitor); this.deferredVisitEnds.add(this.methodBodyVisitor); this.deferredVisitEnds.add(this.methodVisitor); } /** * Metadata management - works on IMetaInfo, * which is a common interface of IMetaTagNode and IMetaTag. */ public void processMetadata(ITraitVisitor tv, IMetaInfo[] meta_infos) { if (meta_infos == null) return; if (meta_infos.length == 0) return; IMetadataVisitor mv = tv.visitMetadata(meta_infos.length); processMetadata(mv, meta_infos); } private static void processMetadata(IMetadataVisitor mv, IMetaInfo[] meta_infos) { for ( IMetaInfo meta_info: meta_infos ) { String name = meta_info.getTagName(); IMetaTagAttribute[] attrs = meta_info.getAllAttributes(); if (name.equals(BindableHelper.BINDABLE) && attrs.length == 0) { attrs = new MetaTagAttribute[1]; attrs[0] = new MetaTagAttribute("event", BindableHelper.PROPERTY_CHANGE); } String[] keys = new String[attrs.length]; String[] values = new String[attrs.length]; for ( int i = 0; i < attrs.length; i++ ) { keys[i] = attrs[i].getKey(); values[i] = attrs[i].getValue(); } Metadata metadata = new Metadata(name, keys, values); mv.visit(metadata); } } /* * ************************* * ** Debug Information ** * ************************* */ /** * @return true if the new file name is valid and does * not match the existing file name, in which case * the caller should emit a debugfile instruction and * reset the file name. */ public boolean emitFile(String candidate) { return candidate != null && (!getGlobalScope().getEncodedDebugFile(candidate).equals(currentDebugFile) ); } /** * @return true if the current file name is valid, and the new * line number is valid and does not equal the existing line number, * in which case the caller should emit a debugline instruction and * reset the line number. */ public boolean emitLine(final int candidate) { return candidate > 0 && currentDebugFile != null && candidate != currentDebugLine; } /** * Set the debug info file name. * @param file_name - the name to set. May be null. */ public void setDebugFile(String file_name) { this.currentDebugFile = getGlobalScope().getEncodedDebugFile(file_name); setDebugLine(DEBUG_LINE_UNKNOWN); } /** * @return the current debug info file name. May be null */ public String getDebugFile() { return currentDebugFile; } /** * Set the debug info line number. * @param line_num - the line number to set. */ public void setDebugLine(final int line_num) { this.currentDebugLine = line_num; } /** * Reset the debug info to initial values. */ public void resetDebugInfo() { setDebugFile(null); setDebugLine(DEBUG_LINE_UNKNOWN); } public NestingState getNestingState() { return this.nestingState; } /* * ************************* * ** Driver Interfaces ** * ************************* */ /** * Sets the syntax tree node which establishes the initial scope for labels * visible to goto statements. The passed in node should usually be the syntax * tree node which established this lexical scope. * <p> * This method should only be called once on each instance of this class. * * @param node the syntax tree node which establishes the initial scope for labels * visible to goto statements. */ void setInitialControlFlowRegionNode(IASNode node) { assert this.initialControlFlowRegionNode == null : "The syntax tree node should only be set once."; assert node != null : "The syntax tree node should never be set to null."; this.initialControlFlowRegionNode = node; } /** * Gets the syntax tree node which establishes the initial scope for labels * visible to goto statements. * * @return the syntax tree node which establishes the initial scope for labels * visible to goto statements. */ IASNode getInitialControlFlowRegionNode() { return this.initialControlFlowRegionNode; } /** * Add all the {@link IVisitor}s whose {@link IVisitor#visitEnd()} method calls * have been deferred to the specified {@link List} of {@link IVisitor}s. * @param visitEnds List of {@link IVisitor}s to add to. */ void addVisitEndsToList(List<IVisitor> visitEnds) { visitEnds.addAll(this.deferredVisitEnds); } /** * Make any deferred {@link IVisitor#visitEnd()} calls. This method must only * be called from the thread that started code generation of the syntax tree * that contains this lexical scope. */ void callVisitEnds() { for (IVisitor v : this.deferredVisitEnds) v.visitEnd(); } /** * Transfer information about local initializers from the * scope in which they were declared (the class or instance scope) * to this scope (a constructor). * @pre the declaring scope must be the immediately enclosing scope. */ public void transferInitializerData() { this.hasnexts.addAll(this.enclosingFrame.hasnexts); this.enclosingFrame.hasnexts.clear(); this.tempManager.addAllocatedTemps(this.enclosingFrame.tempManager.getAllocatedTemps()); this.enclosingFrame.tempManager.clearAllocatedTemps(); } /* * *********************************************** * ** Methods to delegate to the global scope ** * *********************************************** */ /** * @return this scope's compiler project. */ public ICompilerProject getProject() { return getGlobalScope().getProject(); } /** * @return the code generator that this lexical scope is using. */ public ICodeGenerator getGenerator() { return getGlobalScope().getGenerator(); } /** * @return the IABCVisitor backing this code generation phase. */ IABCVisitor getEmitter() { return getGlobalScope().getEmitter(); } /** * @return true if this scope is for an invisible compilation unit. * In this case, {@link IDefinition}'s for package/file classes, * interfaces, function's, var's, const's, and namespaces need to be * normalized before comparing them by identity in some semantic checks ( * namely the type conversion checks ). * Also, in this case, we must do additional checking to determine * whether an import is valid. */ public boolean getInInvisibleCompilationUnit() { return getGlobalScope().getInInvisibleCompilationUnit(); } /** * @return any initial instructions for this code generation phase. */ public InstructionList getInitInstructions() { return getGlobalScope().getInitInstructions(); } /** * @return the MethodBodySemanticChecker covering this code generation phase. */ public MethodBodySemanticChecker getMethodBodySemanticChecker() { if ( this.methodBodySemanticChecker != null ) { return this.methodBodySemanticChecker; } else if ( this.enclosingFrame != null ) { return this.enclosingFrame.getMethodBodySemanticChecker(); } else { assert false: "No MethodBodySemanticChecker found."; return null; } } /** * @return the problems covering this code generation phase. */ public Collection<ICompilerProblem> getProblems() { return getGlobalScope().getProblems(); } /** * Add a problem to this code generation phase. * * @param problem The {@link ICompilerProblem} to add. */ public void addProblem(ICompilerProblem problem) { getProblems().add(problem); } /** * Add a collection of problems to this code generation phase. * * @param problems The collection of {@link ICompilerProblem} objects to add. */ public void addProblems(Collection<ICompilerProblem> problems) { getProblems().addAll(problems); } }