org.eclipse.jdt.internal.compiler.lookup.BlockScope.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.compiler.lookup.BlockScope.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann - Contributions for
 *                          bug 349326 - [1.7] new warning for missing try-with-resources
 *                        bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points
 *                        bug 358903 - Filter practically unimportant resource leak warnings
 *                        bug 368546 - [compiler][resource] Avoid remaining false positives found when compiling the Eclipse SDK
 *                        bug 370639 - [compiler][resource] restore the default for resource leak warnings
 *                        bug 388996 - [compiler][resource] Incorrect 'potential resource leak'
 *                        bug 379784 - [compiler] "Method can be static" is not getting reported
 *                        bug 394768 - [compiler][resource] Incorrect resource leak warning when creating stream in conditional
 *                        bug 404649 - [1.8][compiler] detect illegal reference to indirect or redundant super
 *                        Bug 429958 - [1.8][null] evaluate new DefaultLocation attribute of @NonNullByDefault
 *                        Bug 371614 - [compiler][resource] Wrong "resource leak" problem on return/throw inside while loop
 *                        Bug 421035 - [resource] False alarm of resource leak warning when casting a closeable in its assignment
 *                        Bug 444964 - [1.7+][resource] False resource leak warning (try-with-resources for ByteArrayOutputStream - return inside for loop)
 *                        Bug 396575 - [compiler][resources] Incorrect Errors/Warnings check for potential resource leak when surrounding with try-catch
 *     Jesper S Moller <jesper@selskabet.org> - Contributions for
 *                        bug 378674 - "The method can be declared as static" is wrong
 *     Keigo Imai - Contribution for  bug 388903 - Cannot extend inner class as an anonymous class when it extends the outer class
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.lookup;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class BlockScope extends Scope {

    // Local variable management
    public LocalVariableBinding[] locals;
    public int localIndex; // position for next variable
    public int startIndex; // start position in this scope - for ordering scopes vs. variables
    public int offset; // for variable allocation throughout scopes
    public int maxOffset; // for variable allocation throughout scopes

    // finally scopes must be shifted behind respective try&catch scope(s) so as to avoid
    // collisions of secret variables (return address, save value).
    public BlockScope[] shiftScopes;

    public Scope[] subscopes = new Scope[1]; // need access from code assist
    public int subscopeCount = 0; // need access from code assist
    // record the current case statement being processed (for entire switch case block).
    public CaseStatement enclosingCase; // from 1.4 on, local types should not be accessed across switch case blocks (52221)

    public final static VariableBinding[] EmulationPathToImplicitThis = {};
    public final static VariableBinding[] NoEnclosingInstanceInConstructorCall = {};

    public final static VariableBinding[] NoEnclosingInstanceInStaticContext = {};

    // annotation support
    public boolean insideTypeAnnotation = false;
    public Statement blockStatement;

    public BlockScope(BlockScope parent) {
        this(parent, true);
    }

    public BlockScope(BlockScope parent, boolean addToParentScope) {
        this(Scope.BLOCK_SCOPE, parent);
        this.locals = new LocalVariableBinding[5];
        if (addToParentScope)
            parent.addSubscope(this);
        this.startIndex = parent.localIndex;
    }

    public BlockScope(BlockScope parent, int variableCount) {
        this(Scope.BLOCK_SCOPE, parent);
        this.locals = new LocalVariableBinding[variableCount];
        parent.addSubscope(this);
        this.startIndex = parent.localIndex;
    }

    protected BlockScope(int kind, Scope parent) {
        super(kind, parent);
    }

    /* Create the class scope & binding for the anonymous type.
     */
    public final void addAnonymousType(TypeDeclaration anonymousType, ReferenceBinding superBinding) {
        ClassScope anonymousClassScope = new ClassScope(this, anonymousType);
        anonymousClassScope.buildAnonymousTypeBinding(enclosingSourceType(), superBinding);

        /* Tag any enclosing lambdas as instance capturing. Strictly speaking they need not be, unless the local/anonymous type references enclosing instance state.
           but the types themselves track enclosing types regardless of whether the state is accessed or not. This creates a mismatch in expectations in code generation
           time, if we choose to make the lambda method static. To keep things simple and avoid a messy rollback, we force the lambda to be an instance method under 
           this situation. However if per source, the lambda occurs in a static context, we would generate a static synthetic method.
        */
        MethodScope methodScope = methodScope();
        while (methodScope != null && methodScope.referenceContext instanceof LambdaExpression) {
            LambdaExpression lambda = (LambdaExpression) methodScope.referenceContext;
            if (!lambda.scope.isStatic && !lambda.scope.isConstructorCall) {
                lambda.shouldCaptureInstance = true;
            }
            methodScope = methodScope.enclosingMethodScope();
        }
    }

    /* Create the class scope & binding for the local type.
     */
    public final void addLocalType(TypeDeclaration localType) {
        ClassScope localTypeScope = new ClassScope(this, localType);
        addSubscope(localTypeScope);
        localTypeScope.buildLocalTypeBinding(enclosingSourceType());

        // See comment in addAnonymousType.
        MethodScope methodScope = methodScope();
        while (methodScope != null && methodScope.referenceContext instanceof LambdaExpression) {
            LambdaExpression lambda = (LambdaExpression) methodScope.referenceContext;
            if (!lambda.scope.isStatic && !lambda.scope.isConstructorCall) {
                lambda.shouldCaptureInstance = true;
            }
            methodScope = methodScope.enclosingMethodScope();
        }
    }

    /* Insert a local variable into a given scope, updating its position
     * and checking there are not too many locals or arguments allocated.
     */
    public final void addLocalVariable(LocalVariableBinding binding) {
        checkAndSetModifiersForVariable(binding);
        // insert local in scope
        if (this.localIndex == this.locals.length)
            System.arraycopy(this.locals, 0, (this.locals = new LocalVariableBinding[this.localIndex * 2]), 0,
                    this.localIndex);
        this.locals[this.localIndex++] = binding;

        // update local variable binding
        binding.declaringScope = this;
        binding.id = outerMostMethodScope().analysisIndex++;
        // share the outermost method scope analysisIndex
    }

    public void addSubscope(Scope childScope) {
        if (this.subscopeCount == this.subscopes.length)
            System.arraycopy(this.subscopes, 0, (this.subscopes = new Scope[this.subscopeCount * 2]), 0,
                    this.subscopeCount);
        this.subscopes[this.subscopeCount++] = childScope;
    }

    /**
     * Answer true if the receiver is suitable for assigning final blank fields.
     * in other words, it is inside an initializer, a constructor or a clinit
     */
    public final boolean allowBlankFinalFieldAssignment(FieldBinding binding) {
        if (TypeBinding.notEquals(enclosingReceiverType(), binding.declaringClass))
            return false;

        MethodScope methodScope = methodScope();
        if (methodScope.isStatic != binding.isStatic())
            return false;
        if (methodScope.isLambdaScope())
            return false;
        return methodScope.isInsideInitializer() // inside initializer
                || ((AbstractMethodDeclaration) methodScope.referenceContext).isInitializationMethod(); // inside constructor or clinit
    }

    String basicToString(int tab) {
        String newLine = "\n"; //$NON-NLS-1$
        for (int i = tab; --i >= 0;)
            newLine += "\t"; //$NON-NLS-1$

        String s = newLine + "--- Block Scope ---"; //$NON-NLS-1$
        newLine += "\t"; //$NON-NLS-1$
        s += newLine + "locals:"; //$NON-NLS-1$
        for (int i = 0; i < this.localIndex; i++)
            s += newLine + "\t" + this.locals[i].toString(); //$NON-NLS-1$
        s += newLine + "startIndex = " + this.startIndex; //$NON-NLS-1$
        return s;
    }

    private void checkAndSetModifiersForVariable(LocalVariableBinding varBinding) {
        int modifiers = varBinding.modifiers;
        if ((modifiers & ExtraCompilerModifiers.AccAlternateModifierProblem) != 0
                && varBinding.declaration != null) {
            problemReporter().duplicateModifierForVariable(varBinding.declaration, this instanceof MethodScope);
        }
        int realModifiers = modifiers & ExtraCompilerModifiers.AccJustFlag;

        int unexpectedModifiers = ~ClassFileConstants.AccFinal;
        if ((realModifiers & unexpectedModifiers) != 0 && varBinding.declaration != null) {
            problemReporter().illegalModifierForVariable(varBinding.declaration, this instanceof MethodScope);
        }
        varBinding.modifiers = modifiers;
    }

    /* Compute variable positions in scopes given an initial position offset
     * ignoring unused local variables.
     *
     * No argument is expected here (ilocal is the first non-argument local of the outermost scope)
     * Arguments are managed by the MethodScope method
     */
    void computeLocalVariablePositions(int ilocal, int initOffset, CodeStream codeStream) {
        this.offset = initOffset;
        this.maxOffset = initOffset;

        // local variable init
        int maxLocals = this.localIndex;
        boolean hasMoreVariables = ilocal < maxLocals;

        // scope init
        int iscope = 0, maxScopes = this.subscopeCount;
        boolean hasMoreScopes = maxScopes > 0;

        // iterate scopes and variables in parallel
        while (hasMoreVariables || hasMoreScopes) {
            if (hasMoreScopes && (!hasMoreVariables || (this.subscopes[iscope].startIndex() <= ilocal))) {
                // consider subscope first
                if (this.subscopes[iscope] instanceof BlockScope) {
                    BlockScope subscope = (BlockScope) this.subscopes[iscope];
                    int subOffset = subscope.shiftScopes == null ? this.offset : subscope.maxShiftedOffset();
                    subscope.computeLocalVariablePositions(0, subOffset, codeStream);
                    if (subscope.maxOffset > this.maxOffset)
                        this.maxOffset = subscope.maxOffset;
                }
                hasMoreScopes = ++iscope < maxScopes;
            } else {

                // consider variable first
                LocalVariableBinding local = this.locals[ilocal]; // if no local at all, will be locals[ilocal]==null

                // check if variable is actually used, and may force it to be preserved
                boolean generateCurrentLocalVar = (local.useFlag > LocalVariableBinding.UNUSED
                        && local.constant() == Constant.NotAConstant);

                // do not report fake used variable
                if (local.useFlag == LocalVariableBinding.UNUSED && (local.declaration != null) // unused (and non secret) local
                        && ((local.declaration.bits & ASTNode.IsLocalDeclarationReachable) != 0)) { // declaration is reachable

                    if (local.isCatchParameter()) {
                        problemReporter().unusedExceptionParameter(local.declaration); // report unused catch arguments
                    } else {
                        problemReporter().unusedLocalVariable(local.declaration);
                    }
                }

                // could be optimized out, but does need to preserve unread variables ?
                if (!generateCurrentLocalVar) {
                    if (local.declaration != null && compilerOptions().preserveAllLocalVariables) {
                        generateCurrentLocalVar = true; // force it to be preserved in the generated code
                        if (local.useFlag == LocalVariableBinding.UNUSED)
                            local.useFlag = LocalVariableBinding.USED;
                    }
                }

                // allocate variable
                if (generateCurrentLocalVar) {

                    if (local.declaration != null) {
                        codeStream.record(local); // record user-defined local variables for attribute generation
                    }
                    // assign variable position
                    local.resolvedPosition = this.offset;

                    if ((TypeBinding.equalsEquals(local.type, TypeBinding.LONG))
                            || (TypeBinding.equalsEquals(local.type, TypeBinding.DOUBLE))) {
                        this.offset += 2;
                    } else {
                        this.offset++;
                    }
                    if (this.offset > 0xFFFF) { // no more than 65535 words of locals
                        problemReporter().noMoreAvailableSpaceForLocal(local,
                                local.declaration == null ? (ASTNode) methodScope().referenceContext
                                        : local.declaration);
                    }
                } else {
                    local.resolvedPosition = -1; // not generated
                }
                hasMoreVariables = ++ilocal < maxLocals;
            }
        }
        if (this.offset > this.maxOffset)
            this.maxOffset = this.offset;
    }

    /*
     *   Record the suitable binding denoting a synthetic field or constructor argument,
     * mapping to the actual outer local variable in the scope context.
     * Note that this may not need any effect, in case the outer local variable does not
     * need to be emulated and can directly be used as is (using its back pointer to its
     * declaring scope).
     */
    public void emulateOuterAccess(LocalVariableBinding outerLocalVariable) {
        BlockScope outerVariableScope = outerLocalVariable.declaringScope;
        if (outerVariableScope == null)
            return; // no need to further emulate as already inserted (val$this$0)

        int depth = 0;
        Scope scope = this;
        while (outerVariableScope != scope) {
            switch (scope.kind) {
            case CLASS_SCOPE:
                depth++;
                break;
            case METHOD_SCOPE:
                if (scope.isLambdaScope()) {
                    LambdaExpression lambdaExpression = (LambdaExpression) scope.referenceContext();
                    lambdaExpression.addSyntheticArgument(outerLocalVariable);
                }
                break;
            }
            scope = scope.parent;
        }
        if (depth == 0)
            return;

        MethodScope currentMethodScope = methodScope();
        if (outerVariableScope.methodScope() != currentMethodScope) {
            NestedTypeBinding currentType = (NestedTypeBinding) enclosingSourceType();

            //do nothing for member types, pre emulation was performed already
            if (!currentType.isLocalType()) {
                return;
            }
            // must also add a synthetic field if we're not inside a constructor
            if (!currentMethodScope.isInsideInitializerOrConstructor()) {
                currentType.addSyntheticArgumentAndField(outerLocalVariable);
            } else {
                currentType.addSyntheticArgument(outerLocalVariable);
            }
        }
    }

    /* Note that it must never produce a direct access to the targetEnclosingType,
     * but instead a field sequence (this$2.this$1.this$0) so as to handle such a test case:
     *
     * class XX {
     *   void foo() {
     *      class A {
     *         class B {
     *            class C {
     *               boolean foo() {
     *                  return (Object) A.this == (Object) B.this;
     *               }
     *            }
     *         }
     *      }
     *      new A().new B().new C();
     *   }
     * }
     * where we only want to deal with ONE enclosing instance for C (could not figure out an A for C)
     */
    public final ReferenceBinding findLocalType(char[] name) {
        long compliance = compilerOptions().complianceLevel;
        for (int i = this.subscopeCount - 1; i >= 0; i--) {
            if (this.subscopes[i] instanceof ClassScope) {
                LocalTypeBinding sourceType = (LocalTypeBinding) ((ClassScope) this.subscopes[i]).referenceContext.binding;
                // from 1.4 on, local types should not be accessed across switch case blocks (52221)
                if (compliance >= ClassFileConstants.JDK1_4 && sourceType.enclosingCase != null) {
                    if (!isInsideCase(sourceType.enclosingCase)) {
                        continue;
                    }
                }
                if (CharOperation.equals(sourceType.sourceName(), name))
                    return sourceType;
            }
        }
        return null;
    }

    /**
     * Returns all declarations of most specific locals containing a given position in their source range.
     * This code does not recurse in nested types.
     * Returned array may have null values at trailing indexes.
     */
    public LocalDeclaration[] findLocalVariableDeclarations(int position) {
        // local variable init
        int ilocal = 0, maxLocals = this.localIndex;
        boolean hasMoreVariables = maxLocals > 0;
        LocalDeclaration[] localDeclarations = null;
        int declPtr = 0;

        // scope init
        int iscope = 0, maxScopes = this.subscopeCount;
        boolean hasMoreScopes = maxScopes > 0;

        // iterate scopes and variables in parallel
        while (hasMoreVariables || hasMoreScopes) {
            if (hasMoreScopes && (!hasMoreVariables || (this.subscopes[iscope].startIndex() <= ilocal))) {
                // consider subscope first
                Scope subscope = this.subscopes[iscope];
                if (subscope.kind == Scope.BLOCK_SCOPE) { // do not dive in nested types
                    localDeclarations = ((BlockScope) subscope).findLocalVariableDeclarations(position);
                    if (localDeclarations != null) {
                        return localDeclarations;
                    }
                }
                hasMoreScopes = ++iscope < maxScopes;
            } else {
                // consider variable first
                LocalVariableBinding local = this.locals[ilocal]; // if no local at all, will be locals[ilocal]==null
                if (local != null) {
                    LocalDeclaration localDecl = local.declaration;
                    if (localDecl != null) {
                        if (localDecl.declarationSourceStart <= position) {
                            if (position <= localDecl.declarationSourceEnd) {
                                if (localDeclarations == null) {
                                    localDeclarations = new LocalDeclaration[maxLocals];
                                }
                                localDeclarations[declPtr++] = localDecl;
                            }
                        } else {
                            return localDeclarations;
                        }
                    }
                }
                hasMoreVariables = ++ilocal < maxLocals;
                if (!hasMoreVariables && localDeclarations != null) {
                    return localDeclarations;
                }
            }
        }
        return null;
    }

    @Override
    public LocalVariableBinding findVariable(char[] variableName) {
        int varLength = variableName.length;
        for (int i = this.localIndex - 1; i >= 0; i--) { // lookup backward to reach latest additions first
            LocalVariableBinding local;
            char[] localName;
            if ((localName = (local = this.locals[i]).name).length == varLength
                    && CharOperation.equals(localName, variableName))
                return local;
        }
        return null;
    }

    /* API
     * flag is a mask of the following values VARIABLE (= FIELD or LOCAL), TYPE.
     * Only bindings corresponding to the mask will be answered.
     *
     *   if the VARIABLE mask is set then
     *      If the first name provided is a field (or local) then the field (or local) is answered
     *      Otherwise, package names and type names are consumed until a field is found.
     *      In this case, the field is answered.
     *
     *   if the TYPE mask is set,
     *      package names and type names are consumed until the end of the input.
     *      Only if all of the input is consumed is the type answered
     *
     *   All other conditions are errors, and a problem binding is returned.
     *
     *   NOTE: If a problem binding is returned, senders should extract the compound name
     *   from the binding & not assume the problem applies to the entire compoundName.
     *
     *   The VARIABLE mask has precedence over the TYPE mask.
     *
     *   InvocationSite implements
     *      isSuperAccess(); this is used to determine if the discovered field is visible.
     *      setFieldIndex(int); this is used to record the number of names that were consumed.
     *
     *   For example, getBinding({"foo","y","q", VARIABLE, site) will answer
     *   the binding for the field or local named "foo" (or an error binding if none exists).
     *   In addition, setFieldIndex(1) will be sent to the invocation site.
     *   If a type named "foo" exists, it will not be detected (and an error binding will be answered)
     *
     *   IMPORTANT NOTE: This method is written under the assumption that compoundName is longer than length 1.
     */
    public Binding getBinding(char[][] compoundName, int mask, InvocationSite invocationSite, boolean needResolve) {
        Binding binding = getBinding(compoundName[0], mask | Binding.TYPE | Binding.PACKAGE, invocationSite,
                needResolve);
        invocationSite.setFieldIndex(1);
        if (binding instanceof VariableBinding)
            return binding;
        CompilationUnitScope unitScope = compilationUnitScope();
        // in the problem case, we want to ensure we record the qualified dependency in case a type is added
        // and we do not know that its package was also added (can happen with CompilationParticipants)
        unitScope.recordQualifiedReference(compoundName);
        if (!binding.isValidBinding())
            return binding;

        int length = compoundName.length;
        int currentIndex = 1;
        foundType: if (binding instanceof PackageBinding) {
            PackageBinding packageBinding = (PackageBinding) binding;
            while (currentIndex < length) {
                unitScope.recordReference(packageBinding.compoundName, compoundName[currentIndex]);
                binding = packageBinding.getTypeOrPackage(compoundName[currentIndex++], module(),
                        currentIndex < length);
                invocationSite.setFieldIndex(currentIndex);
                if (binding == null) {
                    if (currentIndex == length) {
                        // must be a type if its the last name, otherwise we have no idea if its a package or type
                        return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                                null, ProblemReasons.NotFound);
                    }
                    return new ProblemBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                            ProblemReasons.NotFound);
                }
                if (binding instanceof ReferenceBinding) {
                    if (!binding.isValidBinding())
                        return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                                (ReferenceBinding) ((ReferenceBinding) binding).closestMatch(),
                                binding.problemId());
                    if (!((ReferenceBinding) binding).canBeSeenBy(this))
                        return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                                (ReferenceBinding) binding, ProblemReasons.NotVisible);
                    break foundType;
                }
                packageBinding = (PackageBinding) binding;
            }

            // It is illegal to request a PACKAGE from this method.
            return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex), null,
                    ProblemReasons.NotFound);
        }

        // know binding is now a ReferenceBinding
        ReferenceBinding referenceBinding = (ReferenceBinding) binding;
        binding = environment().convertToRawType(referenceBinding,
                false /*do not force conversion of enclosing types*/);
        if (invocationSite instanceof ASTNode) {
            ASTNode invocationNode = (ASTNode) invocationSite;
            if (invocationNode.isTypeUseDeprecated(referenceBinding, this)) {
                problemReporter().deprecatedType(referenceBinding, invocationNode);
            }
        }
        Binding problemFieldBinding = null;
        while (currentIndex < length) {
            referenceBinding = (ReferenceBinding) binding;
            char[] nextName = compoundName[currentIndex++];
            invocationSite.setFieldIndex(currentIndex);
            invocationSite.setActualReceiverType(referenceBinding);
            if ((mask & Binding.FIELD) != 0 && (binding = findField(referenceBinding, nextName, invocationSite,
                    true /*resolve*/)) != null) {
                if (binding.isValidBinding()) {
                    break; // binding is now a field
                }
                problemFieldBinding = new ProblemFieldBinding(((ProblemFieldBinding) binding).closestMatch,
                        ((ProblemFieldBinding) binding).declaringClass,
                        CharOperation.concatWith(CharOperation.subarray(compoundName, 0, currentIndex), '.'),
                        binding.problemId());
                // https://bugs.eclipse.org/bugs/show_bug.cgi?id=317858 : If field is inaccessible,
                // don't give up yet, continue to look for a visible member type 
                if (binding.problemId() != ProblemReasons.NotVisible) {
                    return problemFieldBinding;
                }
            }
            if ((binding = findMemberType(nextName, referenceBinding)) == null) {
                if (problemFieldBinding != null) {
                    return problemFieldBinding;
                }
                if ((mask & Binding.FIELD) != 0) {
                    return new ProblemFieldBinding(null, referenceBinding, nextName, ProblemReasons.NotFound);
                } else if ((mask & Binding.VARIABLE) != 0) {
                    return new ProblemBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                            referenceBinding, ProblemReasons.NotFound);
                }
                return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                        referenceBinding, ProblemReasons.NotFound);
            }
            // binding is a ReferenceBinding
            if (!binding.isValidBinding()) {
                if (problemFieldBinding != null) {
                    return problemFieldBinding;
                }
                return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                        (ReferenceBinding) ((ReferenceBinding) binding).closestMatch(), binding.problemId());
            }
            if (invocationSite instanceof ASTNode) {
                referenceBinding = (ReferenceBinding) binding;
                ASTNode invocationNode = (ASTNode) invocationSite;
                if (invocationNode.isTypeUseDeprecated(referenceBinding, this)) {
                    problemReporter().deprecatedType(referenceBinding, invocationNode);
                }
            }
        }
        if ((mask & Binding.FIELD) != 0 && (binding instanceof FieldBinding)) {
            // was looking for a field and found a field
            FieldBinding field = (FieldBinding) binding;
            if (!field.isStatic())
                return new ProblemFieldBinding(field, field.declaringClass,
                        CharOperation.concatWith(CharOperation.subarray(compoundName, 0, currentIndex), '.'),
                        ProblemReasons.NonStaticReferenceInStaticContext);
            // Since a qualified reference must be for a static member, it won't affect static-ness of the enclosing method, 
            // so we don't have to call resetEnclosingMethodStaticFlag() in this case
            return binding;
        }
        if ((mask & Binding.TYPE) != 0 && (binding instanceof ReferenceBinding)) {
            // was looking for a type and found a type
            return binding;
        }

        // handle the case when a field or type was asked for but we resolved the compoundName to a type or field
        return new ProblemBinding(CharOperation.subarray(compoundName, 0, currentIndex), ProblemReasons.NotFound);
    }

    // Added for code assist... NOT Public API
    public final Binding getBinding(char[][] compoundName, InvocationSite invocationSite) {
        int currentIndex = 0;
        int length = compoundName.length;
        Binding binding = getBinding(compoundName[currentIndex++],
                Binding.VARIABLE | Binding.TYPE | Binding.PACKAGE, invocationSite, true /*resolve*/);
        if (!binding.isValidBinding())
            return binding;

        foundType: if (binding instanceof PackageBinding) {
            while (currentIndex < length) {
                PackageBinding packageBinding = (PackageBinding) binding;
                binding = packageBinding.getTypeOrPackage(compoundName[currentIndex++], module(),
                        currentIndex < length);
                if (binding == null) {
                    if (currentIndex == length) {
                        // must be a type if its the last name, otherwise we have no idea if its a package or type
                        return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                                null, ProblemReasons.NotFound);
                    }
                    return new ProblemBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                            ProblemReasons.NotFound);
                }
                if (binding instanceof ReferenceBinding) {
                    if (!binding.isValidBinding())
                        return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                                (ReferenceBinding) ((ReferenceBinding) binding).closestMatch(),
                                binding.problemId());
                    if (!((ReferenceBinding) binding).canBeSeenBy(this))
                        return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                                (ReferenceBinding) binding, ProblemReasons.NotVisible);
                    break foundType;
                }
            }
            return binding;
        }

        foundField: if (binding instanceof ReferenceBinding) {
            while (currentIndex < length) {
                ReferenceBinding typeBinding = (ReferenceBinding) binding;
                char[] nextName = compoundName[currentIndex++];
                TypeBinding receiverType = typeBinding.capture(this, invocationSite.sourceStart(),
                        invocationSite.sourceEnd());
                if ((binding = findField(receiverType, nextName, invocationSite, true /*resolve*/)) != null) {
                    if (!binding.isValidBinding()) {
                        return new ProblemFieldBinding((FieldBinding) binding,
                                ((FieldBinding) binding).declaringClass, CharOperation
                                        .concatWith(CharOperation.subarray(compoundName, 0, currentIndex), '.'),
                                binding.problemId());
                    }
                    if (!((FieldBinding) binding).isStatic())
                        return new ProblemFieldBinding((FieldBinding) binding,
                                ((FieldBinding) binding).declaringClass, CharOperation
                                        .concatWith(CharOperation.subarray(compoundName, 0, currentIndex), '.'),
                                ProblemReasons.NonStaticReferenceInStaticContext);
                    break foundField; // binding is now a field
                }
                if ((binding = findMemberType(nextName, typeBinding)) == null) {
                    return new ProblemBinding(CharOperation.subarray(compoundName, 0, currentIndex), typeBinding,
                            ProblemReasons.NotFound);
                }
                if (!binding.isValidBinding()) {
                    return new ProblemReferenceBinding(CharOperation.subarray(compoundName, 0, currentIndex),
                            (ReferenceBinding) ((ReferenceBinding) binding).closestMatch(), binding.problemId());
                }
            }
            return binding;
        }

        VariableBinding variableBinding = (VariableBinding) binding;
        while (currentIndex < length) {
            TypeBinding typeBinding = variableBinding.type;
            if (typeBinding == null) {
                return new ProblemFieldBinding(null, null,
                        CharOperation.concatWith(CharOperation.subarray(compoundName, 0, currentIndex), '.'),
                        ProblemReasons.NotFound);
            }
            TypeBinding receiverType = typeBinding.capture(this, invocationSite.sourceStart(),
                    invocationSite.sourceEnd());
            variableBinding = findField(receiverType, compoundName[currentIndex++], invocationSite,
                    true /*resolve*/);
            if (variableBinding == null) {
                return new ProblemFieldBinding(null,
                        receiverType instanceof ReferenceBinding ? (ReferenceBinding) receiverType : null,
                        CharOperation.concatWith(CharOperation.subarray(compoundName, 0, currentIndex), '.'),
                        ProblemReasons.NotFound);
            }
            if (!variableBinding.isValidBinding())
                return variableBinding;
        }
        return variableBinding;
    }

    /*
     * This retrieves the argument that maps to an enclosing instance of the suitable type,
     *    if not found then answers nil -- do not create one
     *
     *      #implicitThis                     : the implicit this will be ok
     *      #((arg) this$n)                  : available as a constructor arg
     *       #((arg) this$n ... this$p)          : available as as a constructor arg + a sequence of fields
     *       #((fieldDescr) this$n ... this$p)    : available as a sequence of fields
     *       nil                                         : not found
     *
     *    Note that this algorithm should answer the shortest possible sequence when
     *       shortcuts are available:
     *             this$0 . this$0 . this$0
     *       instead of
     *             this$2 . this$1 . this$0 . this$1 . this$0
     *       thus the code generation will be more compact and runtime faster
     */
    public VariableBinding[] getEmulationPath(LocalVariableBinding outerLocalVariable) {
        MethodScope currentMethodScope = methodScope();
        SourceTypeBinding sourceType = currentMethodScope.enclosingSourceType();

        // identity check
        BlockScope variableScope = outerLocalVariable.declaringScope;
        if (variableScope == null /*val$this$0*/ || currentMethodScope == variableScope.methodScope()) {
            return new VariableBinding[] { outerLocalVariable };
            // implicit this is good enough
        }
        if (currentMethodScope.isLambdaScope()) {
            LambdaExpression lambda = (LambdaExpression) currentMethodScope.referenceContext;
            SyntheticArgumentBinding syntheticArgument;
            if ((syntheticArgument = lambda.getSyntheticArgument(outerLocalVariable)) != null) {
                return new VariableBinding[] { syntheticArgument };
            }
        }
        // use synthetic constructor arguments if possible
        if (currentMethodScope.isInsideInitializerOrConstructor() && (sourceType.isNestedType())) {
            SyntheticArgumentBinding syntheticArg;
            if ((syntheticArg = ((NestedTypeBinding) sourceType)
                    .getSyntheticArgument(outerLocalVariable)) != null) {
                return new VariableBinding[] { syntheticArg };
            }
        }
        // use a synthetic field then
        if (!currentMethodScope.isStatic) {
            FieldBinding syntheticField;
            if ((syntheticField = sourceType.getSyntheticField(outerLocalVariable)) != null) {
                return new VariableBinding[] { syntheticField };
            }
        }
        return null;
    }

    /*
     * This retrieves the argument that maps to an enclosing instance of the suitable type,
     *    if not found then answers nil -- do not create one
     *
     *      #implicitThis                                             :  the implicit this will be ok
     *      #((arg) this$n)                                       : available as a constructor arg
     *    #((arg) this$n access$m... access$p)       : available as as a constructor arg + a sequence of synthetic accessors to synthetic fields
     *    #((fieldDescr) this$n access#m... access$p)   : available as a first synthetic field + a sequence of synthetic accessors to synthetic fields
     *    null                                                     : not found
     *   jls 15.9.2 + http://www.ergnosis.com/java-spec-report/java-language/jls-8.8.5.1-d.html
     */
    public Object[] getEmulationPath(ReferenceBinding targetEnclosingType, boolean onlyExactMatch,
            boolean denyEnclosingArgInConstructorCall) {
        MethodScope currentMethodScope = methodScope();
        SourceTypeBinding sourceType = currentMethodScope.enclosingSourceType();

        // use 'this' if possible
        if (!currentMethodScope.isStatic && !currentMethodScope.isConstructorCall) {
            if (TypeBinding.equalsEquals(sourceType, targetEnclosingType)
                    || (!onlyExactMatch && sourceType.findSuperTypeOriginatingFrom(targetEnclosingType) != null)) {
                return BlockScope.EmulationPathToImplicitThis; // implicit this is good enough
            }
        }
        if (!sourceType.isNestedType() || sourceType.isStatic()) { // no emulation from within non-inner types
            if (currentMethodScope.isConstructorCall) {
                return BlockScope.NoEnclosingInstanceInConstructorCall;
            } else if (currentMethodScope.isStatic) {
                return BlockScope.NoEnclosingInstanceInStaticContext;
            }
            return null;
        }
        boolean insideConstructor = currentMethodScope.isInsideInitializerOrConstructor();
        // use synthetic constructor arguments if possible
        if (insideConstructor) {
            SyntheticArgumentBinding syntheticArg;
            if ((syntheticArg = ((NestedTypeBinding) sourceType).getSyntheticArgument(targetEnclosingType,
                    onlyExactMatch, currentMethodScope.isConstructorCall)) != null) {
                boolean isAnonymousAndHasEnclosing = sourceType.isAnonymousType()
                        && sourceType.scope.referenceContext.allocation.enclosingInstance != null;
                // reject allocation and super constructor call
                if (denyEnclosingArgInConstructorCall && !isAnonymousAndHasEnclosing
                        && (TypeBinding.equalsEquals(sourceType, targetEnclosingType) || (!onlyExactMatch
                                && sourceType.findSuperTypeOriginatingFrom(targetEnclosingType) != null))) {
                    return BlockScope.NoEnclosingInstanceInConstructorCall;
                }
                return new Object[] { syntheticArg };
            }
        }

        // use a direct synthetic field then
        if (currentMethodScope.isStatic) {
            return BlockScope.NoEnclosingInstanceInStaticContext;
        }
        if (sourceType.isAnonymousType()) {
            ReferenceBinding enclosingType = sourceType.enclosingType();
            if (enclosingType.isNestedType()) {
                NestedTypeBinding nestedEnclosingType = (NestedTypeBinding) enclosingType;
                SyntheticArgumentBinding enclosingArgument = nestedEnclosingType.getSyntheticArgument(
                        nestedEnclosingType.enclosingType(), onlyExactMatch, currentMethodScope.isConstructorCall);
                if (enclosingArgument != null) {
                    FieldBinding syntheticField = sourceType.getSyntheticField(enclosingArgument);
                    if (syntheticField != null) {
                        if (TypeBinding.equalsEquals(syntheticField.type, targetEnclosingType)
                                || (!onlyExactMatch && ((ReferenceBinding) syntheticField.type)
                                        .findSuperTypeOriginatingFrom(targetEnclosingType) != null))
                            return new Object[] { syntheticField };
                    }
                }
            }
        }
        FieldBinding syntheticField = sourceType.getSyntheticField(targetEnclosingType, onlyExactMatch);
        if (syntheticField != null) {
            if (currentMethodScope.isConstructorCall) {
                return BlockScope.NoEnclosingInstanceInConstructorCall;
            }
            return new Object[] { syntheticField };
        }

        // could be reached through a sequence of enclosing instance link (nested members)
        Object[] path = new Object[2]; // probably at least 2 of them
        ReferenceBinding currentType = sourceType.enclosingType();
        if (insideConstructor) {
            path[0] = ((NestedTypeBinding) sourceType).getSyntheticArgument(currentType, onlyExactMatch,
                    currentMethodScope.isConstructorCall);
        } else {
            if (currentMethodScope.isConstructorCall) {
                return BlockScope.NoEnclosingInstanceInConstructorCall;
            }
            path[0] = sourceType.getSyntheticField(currentType, onlyExactMatch);
        }
        if (path[0] != null) { // keep accumulating

            int count = 1;
            ReferenceBinding currentEnclosingType;
            while ((currentEnclosingType = currentType.enclosingType()) != null) {

                //done?
                if (TypeBinding.equalsEquals(currentType, targetEnclosingType) || (!onlyExactMatch
                        && currentType.findSuperTypeOriginatingFrom(targetEnclosingType) != null))
                    break;

                if (currentMethodScope != null) {
                    currentMethodScope = currentMethodScope.enclosingMethodScope();
                    if (currentMethodScope != null && currentMethodScope.isConstructorCall) {
                        return BlockScope.NoEnclosingInstanceInConstructorCall;
                    }
                    if (currentMethodScope != null && currentMethodScope.isStatic) {
                        return BlockScope.NoEnclosingInstanceInStaticContext;
                    }
                }

                syntheticField = ((NestedTypeBinding) currentType).getSyntheticField(currentEnclosingType,
                        onlyExactMatch);
                if (syntheticField == null)
                    break;

                // append inside the path
                if (count == path.length) {
                    System.arraycopy(path, 0, (path = new Object[count + 1]), 0, count);
                }
                // private access emulation is necessary since synthetic field is private
                path[count++] = ((SourceTypeBinding) syntheticField.declaringClass)
                        .addSyntheticMethod(syntheticField, true/*read*/, false /*not super access*/);
                currentType = currentEnclosingType;
            }
            if (TypeBinding.equalsEquals(currentType, targetEnclosingType)
                    || (!onlyExactMatch && currentType.findSuperTypeOriginatingFrom(targetEnclosingType) != null)) {
                return path;
            }
        }
        return null;
    }

    /* Answer true if the variable name already exists within the receiver's scope.
     */
    public final boolean isDuplicateLocalVariable(char[] name) {
        BlockScope current = this;
        while (true) {
            for (int i = 0; i < this.localIndex; i++) {
                if (CharOperation.equals(name, current.locals[i].name))
                    return true;
            }
            if (current.kind != Scope.BLOCK_SCOPE)
                return false;
            current = (BlockScope) current.parent;
        }
    }

    public int maxShiftedOffset() {
        int max = -1;
        if (this.shiftScopes != null) {
            for (int i = 0, length = this.shiftScopes.length; i < length; i++) {
                if (this.shiftScopes[i] != null) {
                    int subMaxOffset = this.shiftScopes[i].maxOffset;
                    if (subMaxOffset > max)
                        max = subMaxOffset;
                }
            }
        }
        return max;
    }

    /**
     * Returns true if the context requires to check initialization of final blank fields.
     * in other words, it is inside an initializer, a constructor or a clinit
     */
    public final boolean needBlankFinalFieldInitializationCheck(FieldBinding binding) {
        boolean isStatic = binding.isStatic();
        ReferenceBinding fieldDeclaringClass = binding.declaringClass;
        // loop in enclosing context, until reaching the field declaring context
        MethodScope methodScope = namedMethodScope();
        while (methodScope != null) {
            if (methodScope.isStatic != isStatic)
                return false;
            if (!methodScope.isInsideInitializer() // inside initializer
                    && !((AbstractMethodDeclaration) methodScope.referenceContext).isInitializationMethod()) { // inside constructor or clinit
                return false; // found some non-initializer context
            }
            ReferenceBinding enclosingType = methodScope.enclosingReceiverType();
            if (TypeBinding.equalsEquals(enclosingType, fieldDeclaringClass)) {
                return true; // found the field context, no need to check any further
            }
            if (!enclosingType.erasure().isAnonymousType()) {
                return false; // only check inside anonymous type
            }
            methodScope = methodScope.enclosingMethodScope().namedMethodScope();
        }
        return false;
    }

    /* Answer the problem reporter to use for raising new problems.
     *
     * Note that as a side-effect, this updates the current reference context
     * (unit, type or method) in case the problem handler decides it is necessary
     * to abort.
     */
    @Override
    public ProblemReporter problemReporter() {
        return methodScope().problemReporter();
    }

    /*
     * Code responsible to request some more emulation work inside the invocation type, so as to supply
     * correct synthetic arguments to any allocation of the target type.
     */
    public void propagateInnerEmulation(ReferenceBinding targetType, boolean isEnclosingInstanceSupplied) {
        // no need to propagate enclosing instances, they got eagerly allocated already.

        SyntheticArgumentBinding[] syntheticArguments;
        if ((syntheticArguments = targetType.syntheticOuterLocalVariables()) != null) {
            for (int i = 0, max = syntheticArguments.length; i < max; i++) {
                SyntheticArgumentBinding syntheticArg = syntheticArguments[i];
                // need to filter out the one that could match a supplied enclosing instance
                if (!(isEnclosingInstanceSupplied
                        && (TypeBinding.equalsEquals(syntheticArg.type, targetType.enclosingType())))) {
                    emulateOuterAccess(syntheticArg.actualOuterLocalVariable);
                }
            }
        }
    }

    /* Answer the reference type of this scope.
     *
     * It is the nearest enclosing type of this scope.
     */
    public TypeDeclaration referenceType() {
        return methodScope().referenceType();
    }

    /*
     * Answer the index of this scope relatively to its parent.
     * For method scope, answers -1 (not a classScope relative position)
     */
    public int scopeIndex() {
        if (this instanceof MethodScope)
            return -1;
        BlockScope parentScope = (BlockScope) this.parent;
        Scope[] parentSubscopes = parentScope.subscopes;
        for (int i = 0, max = parentScope.subscopeCount; i < max; i++) {
            if (parentSubscopes[i] == this)
                return i;
        }
        return -1;
    }

    // start position in this scope - for ordering scopes vs. variables
    @Override
    int startIndex() {
        return this.startIndex;
    }

    @Override
    public String toString() {
        return toString(0);
    }

    public String toString(int tab) {
        String s = basicToString(tab);
        for (int i = 0; i < this.subscopeCount; i++)
            if (this.subscopes[i] instanceof BlockScope)
                s += ((BlockScope) this.subscopes[i]).toString(tab + 1) + "\n"; //$NON-NLS-1$
        return s;
    }

    private List trackingVariables; // can be null if no resources are tracked
    /** Used only during analyseCode and only for checking if a resource was closed in a finallyBlock. */
    public FlowInfo finallyInfo;

    /**
     * Register a tracking variable and compute its id.
     */
    public int registerTrackingVariable(FakedTrackingVariable fakedTrackingVariable) {
        if (this.trackingVariables == null)
            this.trackingVariables = new ArrayList(3);
        this.trackingVariables.add(fakedTrackingVariable);
        MethodScope outerMethodScope = outerMostMethodScope();
        return outerMethodScope.analysisIndex++;
    }

    /** When are no longer interested in this tracking variable - remove it. */
    public void removeTrackingVar(FakedTrackingVariable trackingVariable) {
        if (trackingVariable.innerTracker != null) {
            trackingVariable.innerTracker.withdraw();
            trackingVariable.innerTracker = null;
        }
        if (this.trackingVariables != null)
            if (this.trackingVariables.remove(trackingVariable))
                return;
        if (this.parent instanceof BlockScope)
            ((BlockScope) this.parent).removeTrackingVar(trackingVariable);
    }

    /** Unregister a wrapper resource without affecting its inner. */
    public void pruneWrapperTrackingVar(FakedTrackingVariable trackingVariable) {
        this.trackingVariables.remove(trackingVariable);
    }

    /**
     * At the end of a block check the closing-status of all tracked closeables that are declared in this block.
     * Also invoked when entering unreachable code.
     */
    public void checkUnclosedCloseables(FlowInfo flowInfo, FlowContext flowContext, ASTNode location,
            BlockScope locationScope) {
        if (!compilerOptions().analyseResourceLeaks)
            return;
        if (this.trackingVariables == null) {
            // at a method return we also consider enclosing scopes
            if (location != null && this.parent instanceof BlockScope && !isLambdaScope())
                ((BlockScope) this.parent).checkUnclosedCloseables(flowInfo, flowContext, location, locationScope);
            return;
        }
        if (location != null && flowInfo.reachMode() != 0)
            return;

        FakedTrackingVariable returnVar = (location instanceof ReturnStatement)
                ? FakedTrackingVariable.getCloseTrackingVariable(((ReturnStatement) location).expression, flowInfo,
                        flowContext)
                : null;

        // iterate variables according to the priorities defined in FakedTrackingVariable.IteratorForReporting.Stage
        Iterator<FakedTrackingVariable> iterator = new FakedTrackingVariable.IteratorForReporting(
                this.trackingVariables, this, location != null);
        while (iterator.hasNext()) {
            FakedTrackingVariable trackingVar = iterator.next();

            if (returnVar != null && trackingVar.isResourceBeingReturned(returnVar)) {
                continue;
            }

            if (location != null && trackingVar.hasDefinitelyNoResource(flowInfo)) {
                continue; // reporting against a specific location, there is no resource at this flow, don't complain
            }

            if (location != null && flowContext != null
                    && flowContext.recordExitAgainstResource(this, flowInfo, trackingVar, location)) {
                continue; // handled by the flow context
            }

            // compute the most specific null status for this resource,
            int status = trackingVar.findMostSpecificStatus(flowInfo, this, locationScope);

            if (status == FlowInfo.NULL) {
                // definitely unclosed: highest priority
                reportResourceLeak(trackingVar, location, status);
                continue;
            }
            if (location == null) // at end of block and not definitely unclosed
            {
                // problems at specific locations: medium priority
                if (trackingVar.reportRecordedErrors(this, status, flowInfo.reachMode() != FlowInfo.REACHABLE)) // ... report previously recorded errors
                    continue;
            }
            if (status == FlowInfo.POTENTIALLY_NULL) {
                // potentially unclosed: lower priority
                reportResourceLeak(trackingVar, location, status);
            } else if (status == FlowInfo.NON_NULL) {
                // properly closed but not managed by t-w-r: lowest priority 
                if (environment().globalOptions.complianceLevel >= ClassFileConstants.JDK1_7)
                    trackingVar.reportExplicitClosing(problemReporter());
            }
        }
        if (location == null) {
            // when leaving this block dispose off all tracking variables:
            for (int i = 0; i < this.localIndex; i++)
                this.locals[i].closeTracker = null;
            this.trackingVariables = null;
        }
    }

    private void reportResourceLeak(FakedTrackingVariable trackingVar, ASTNode location, int nullStatus) {
        if (location != null)
            trackingVar.recordErrorLocation(location, nullStatus);
        else
            trackingVar.reportError(problemReporter(), null, nullStatus);
    }

    /** 
     * If one branch of an if-else closes any AutoCloseable resource, and if the same
     * resource is known to be null on the other branch mark it as closed, too,
     * so that merging both branches indicates that the resource is always closed.
     * Example:
     *   FileReader fr1 = null;
     *   try {\n" +
     *      fr1 = new FileReader(someFile);" + 
     *      fr1.read(buf);\n" + 
     *   } finally {\n" + 
     *      if (fr1 != null)\n" +
     *           try {\n" +
     *               fr1.close();\n" +
     *           } catch (IOException e) {
     *              // do nothing 
     *           }
     *      // after this if statement fr1 is definitely not leaked 
     *   }
     */
    public void correlateTrackingVarsIfElse(FlowInfo thenFlowInfo, FlowInfo elseFlowInfo) {
        if (this.trackingVariables != null) {
            int trackVarCount = this.trackingVariables.size();
            for (int i = 0; i < trackVarCount; i++) {
                FakedTrackingVariable trackingVar = (FakedTrackingVariable) this.trackingVariables.get(i);
                if (trackingVar.originalBinding == null)
                    continue;
                if (thenFlowInfo.isDefinitelyNonNull(trackingVar.binding) // closed in then branch
                        && elseFlowInfo.isDefinitelyNull(trackingVar.originalBinding)) // null in else branch
                {
                    elseFlowInfo.markAsDefinitelyNonNull(trackingVar.binding); // -> always closed
                } else if (elseFlowInfo.isDefinitelyNonNull(trackingVar.binding) // closed in else branch
                        && thenFlowInfo.isDefinitelyNull(trackingVar.originalBinding)) // null in then branch
                {
                    thenFlowInfo.markAsDefinitelyNonNull(trackingVar.binding); // -> always closed
                } else {
                    if (thenFlowInfo == FlowInfo.DEAD_END || elseFlowInfo == FlowInfo.DEAD_END)
                        continue; // short cut

                    for (int j = i + 1; j < trackVarCount; j++) {
                        FakedTrackingVariable var2 = ((FakedTrackingVariable) this.trackingVariables.get(j));
                        if (trackingVar.originalBinding == var2.originalBinding) {
                            // two tracking variables for the same original, merge info from both branches now:
                            boolean var1SeenInThen = thenFlowInfo.hasNullInfoFor(trackingVar.binding);
                            boolean var1SeenInElse = elseFlowInfo.hasNullInfoFor(trackingVar.binding);
                            boolean var2SeenInThen = thenFlowInfo.hasNullInfoFor(var2.binding);
                            boolean var2SeenInElse = elseFlowInfo.hasNullInfoFor(var2.binding);
                            int newStatus;
                            if (!var1SeenInThen && var1SeenInElse && var2SeenInThen && !var2SeenInElse) {
                                newStatus = FlowInfo.mergeNullStatus(thenFlowInfo.nullStatus(var2.binding),
                                        elseFlowInfo.nullStatus(trackingVar.binding));
                            } else if (var1SeenInThen && !var1SeenInElse && !var2SeenInThen && var2SeenInElse) {
                                newStatus = FlowInfo.mergeNullStatus(thenFlowInfo.nullStatus(trackingVar.binding),
                                        elseFlowInfo.nullStatus(var2.binding));
                            } else {
                                continue;
                            }
                            thenFlowInfo.markNullStatus(trackingVar.binding, newStatus);
                            elseFlowInfo.markNullStatus(trackingVar.binding, newStatus);
                            trackingVar.originalBinding.closeTracker = trackingVar; // avoid further use of var2
                            thenFlowInfo.markNullStatus(var2.binding, FlowInfo.NON_NULL);
                            elseFlowInfo.markNullStatus(var2.binding, FlowInfo.NON_NULL);
                        }
                    }
                }
            }
        }
        if (this.parent instanceof BlockScope)
            ((BlockScope) this.parent).correlateTrackingVarsIfElse(thenFlowInfo, elseFlowInfo);
    }

    /** 15.12.3 (Java 8) "Compile-Time Step 3: Is the Chosen Method Appropriate?" */
    public void checkAppropriateMethodAgainstSupers(char[] selector, MethodBinding compileTimeMethod,
            TypeBinding[] parameters, InvocationSite site) {
        ReferenceBinding enclosingType = enclosingReceiverType();
        MethodBinding otherMethod = getMethod(enclosingType.superclass(), selector, parameters, site);
        if (checkAppropriate(compileTimeMethod, otherMethod, site)) {
            ReferenceBinding[] superInterfaces = enclosingType.superInterfaces();
            if (superInterfaces != null) {
                for (int i = 0; i < superInterfaces.length; i++) {
                    otherMethod = getMethod(superInterfaces[i], selector, parameters, site);
                    if (!checkAppropriate(compileTimeMethod, otherMethod, site))
                        break;
                }
            }
        }
    }

    private boolean checkAppropriate(MethodBinding compileTimeDeclaration, MethodBinding otherMethod,
            InvocationSite location) {
        if (otherMethod == null || !otherMethod.isValidBinding()
                || otherMethod.original() == compileTimeDeclaration.original())
            return true;
        if (MethodVerifier.doesMethodOverride(otherMethod, compileTimeDeclaration, this.environment())) {
            problemReporter().illegalSuperCallBypassingOverride(location, compileTimeDeclaration,
                    otherMethod.declaringClass);
            return false;
        }
        return true;
    }

}