org.eclipse.jdt.internal.compiler.lookup.BlockScope Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ecj Show documentation
Show all versions of ecj Show documentation
This is Eclipse JDT Core Batch Compiler used by Scout SDK
The newest version!
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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 - 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 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;
}
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++]);
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++]);
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
&& currentMethodScope.isConstructorCall
&& !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.
*/
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
int startIndex() {
return this.startIndex;
}
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)
((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 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 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