| /******************************************************************************* |
| * Copyright (c) 2000, 2004 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 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.ast; |
| |
| import java.util.ArrayList; |
| |
| import org.eclipse.jdt.core.compiler.*; |
| import org.eclipse.jdt.internal.compiler.*; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.codegen.*; |
| import org.eclipse.jdt.internal.compiler.flow.*; |
| import org.eclipse.jdt.internal.compiler.lookup.*; |
| import org.eclipse.jdt.internal.compiler.parser.*; |
| import org.eclipse.jdt.internal.compiler.problem.*; |
| |
| public class ConstructorDeclaration extends AbstractMethodDeclaration { |
| |
| public ExplicitConstructorCall constructorCall; |
| |
| public boolean isDefaultConstructor = false; |
| public TypeParameter[] typeParameters; |
| |
| public ConstructorDeclaration(CompilationResult compilationResult){ |
| super(compilationResult); |
| } |
| |
| public void analyseCode( |
| ClassScope classScope, |
| InitializationFlowContext initializerFlowContext, |
| FlowInfo flowInfo) { |
| |
| if (ignoreFurtherInvestigation) |
| return; |
| |
| if (this.binding != null && !this.binding.isUsed() && (this.binding.isPrivate() || (this.binding.declaringClass.tagBits & (TagBits.IsAnonymousType|TagBits.IsLocalType)) == TagBits.IsLocalType)) { |
| if (!classScope.referenceCompilationUnit().compilationResult.hasSyntaxError) { |
| scope.problemReporter().unusedPrivateConstructor(this); |
| } |
| } |
| |
| // check constructor recursion, once all constructor got resolved |
| if (isRecursive(null /*lazy initialized visited list*/)) { |
| this.scope.problemReporter().recursiveConstructorInvocation(this.constructorCall); |
| } |
| |
| try { |
| ExceptionHandlingFlowContext constructorContext = |
| new ExceptionHandlingFlowContext( |
| initializerFlowContext.parent, |
| this, |
| binding.thrownExceptions, |
| scope, |
| FlowInfo.DEAD_END); |
| initializerFlowContext.checkInitializerExceptions( |
| scope, |
| constructorContext, |
| flowInfo); |
| |
| // anonymous constructor can gain extra thrown exceptions from unhandled ones |
| if (binding.declaringClass.isAnonymousType()) { |
| ArrayList computedExceptions = constructorContext.extendedExceptions; |
| if (computedExceptions != null){ |
| int size; |
| if ((size = computedExceptions.size()) > 0){ |
| ReferenceBinding[] actuallyThrownExceptions; |
| computedExceptions.toArray(actuallyThrownExceptions = new ReferenceBinding[size]); |
| binding.thrownExceptions = actuallyThrownExceptions; |
| } |
| } |
| } |
| |
| // tag parameters as being set |
| if (this.arguments != null) { |
| for (int i = 0, count = this.arguments.length; i < count; i++) { |
| flowInfo.markAsDefinitelyAssigned(this.arguments[i].binding); |
| } |
| } |
| |
| // propagate to constructor call |
| if (constructorCall != null) { |
| // if calling 'this(...)', then flag all non-static fields as definitely |
| // set since they are supposed to be set inside other local constructor |
| if (constructorCall.accessMode == ExplicitConstructorCall.This) { |
| FieldBinding[] fields = binding.declaringClass.fields(); |
| for (int i = 0, count = fields.length; i < count; i++) { |
| FieldBinding field; |
| if (!(field = fields[i]).isStatic()) { |
| flowInfo.markAsDefinitelyAssigned(field); |
| } |
| } |
| } |
| flowInfo = constructorCall.analyseCode(scope, constructorContext, flowInfo); |
| } |
| // propagate to statements |
| if (statements != null) { |
| boolean didAlreadyComplain = false; |
| for (int i = 0, count = statements.length; i < count; i++) { |
| Statement stat = statements[i]; |
| if (!stat.complainIfUnreachable(flowInfo, scope, didAlreadyComplain)) { |
| flowInfo = stat.analyseCode(scope, constructorContext, flowInfo); |
| } else { |
| didAlreadyComplain = true; |
| } |
| } |
| } |
| // check for missing returning path |
| this.needFreeReturn = flowInfo.isReachable(); |
| |
| // check missing blank final field initializations |
| if ((constructorCall != null) |
| && (constructorCall.accessMode != ExplicitConstructorCall.This)) { |
| flowInfo = flowInfo.mergedWith(constructorContext.initsOnReturn); |
| FieldBinding[] fields = binding.declaringClass.fields(); |
| for (int i = 0, count = fields.length; i < count; i++) { |
| FieldBinding field; |
| if ((!(field = fields[i]).isStatic()) |
| && field.isFinal() |
| && (!flowInfo.isDefinitelyAssigned(fields[i]))) { |
| scope.problemReporter().uninitializedBlankFinalField( |
| field, |
| isDefaultConstructor ? (ASTNode) scope.referenceType() : this); |
| } |
| } |
| } |
| // check unreachable catch blocks |
| constructorContext.complainIfUnusedExceptionHandlers(this); |
| } catch (AbortMethod e) { |
| this.ignoreFurtherInvestigation = true; |
| } |
| } |
| |
| /** |
| * Bytecode generation for a constructor |
| * |
| * @param classScope org.eclipse.jdt.internal.compiler.lookup.ClassScope |
| * @param classFile org.eclipse.jdt.internal.compiler.codegen.ClassFile |
| */ |
| public void generateCode(ClassScope classScope, ClassFile classFile) { |
| |
| int problemResetPC = 0; |
| if (ignoreFurtherInvestigation) { |
| if (this.binding == null) |
| return; // Handle methods with invalid signature or duplicates |
| int problemsLength; |
| IProblem[] problems = |
| scope.referenceCompilationUnit().compilationResult.getProblems(); |
| IProblem[] problemsCopy = new IProblem[problemsLength = problems.length]; |
| System.arraycopy(problems, 0, problemsCopy, 0, problemsLength); |
| classFile.addProblemConstructor(this, binding, problemsCopy); |
| return; |
| } |
| try { |
| problemResetPC = classFile.contentsOffset; |
| this.internalGenerateCode(classScope, classFile); |
| } catch (AbortMethod e) { |
| if (e.compilationResult == CodeStream.RESTART_IN_WIDE_MODE) { |
| // a branch target required a goto_w, restart code gen in wide mode. |
| try { |
| classFile.contentsOffset = problemResetPC; |
| classFile.methodCount--; |
| classFile.codeStream.wideMode = true; // request wide mode |
| this.internalGenerateCode(classScope, classFile); // restart method generation |
| } catch (AbortMethod e2) { |
| int problemsLength; |
| IProblem[] problems = |
| scope.referenceCompilationUnit().compilationResult.getAllProblems(); |
| IProblem[] problemsCopy = new IProblem[problemsLength = problems.length]; |
| System.arraycopy(problems, 0, problemsCopy, 0, problemsLength); |
| classFile.addProblemConstructor(this, binding, problemsCopy, problemResetPC); |
| } |
| } else { |
| int problemsLength; |
| IProblem[] problems = |
| scope.referenceCompilationUnit().compilationResult.getAllProblems(); |
| IProblem[] problemsCopy = new IProblem[problemsLength = problems.length]; |
| System.arraycopy(problems, 0, problemsCopy, 0, problemsLength); |
| classFile.addProblemConstructor(this, binding, problemsCopy, problemResetPC); |
| } |
| } |
| } |
| |
| public void generateSyntheticFieldInitializationsIfNecessary( |
| MethodScope methodScope, |
| CodeStream codeStream, |
| ReferenceBinding declaringClass) { |
| |
| if (!declaringClass.isNestedType()) return; |
| |
| NestedTypeBinding nestedType = (NestedTypeBinding) declaringClass; |
| |
| SyntheticArgumentBinding[] syntheticArgs = nestedType.syntheticEnclosingInstances(); |
| for (int i = 0, max = syntheticArgs == null ? 0 : syntheticArgs.length; i < max; i++) { |
| SyntheticArgumentBinding syntheticArg; |
| if ((syntheticArg = syntheticArgs[i]).matchingField != null) { |
| codeStream.aload_0(); |
| codeStream.load(syntheticArg); |
| codeStream.putfield(syntheticArg.matchingField); |
| } |
| } |
| syntheticArgs = nestedType.syntheticOuterLocalVariables(); |
| for (int i = 0, max = syntheticArgs == null ? 0 : syntheticArgs.length; i < max; i++) { |
| SyntheticArgumentBinding syntheticArg; |
| if ((syntheticArg = syntheticArgs[i]).matchingField != null) { |
| codeStream.aload_0(); |
| codeStream.load(syntheticArg); |
| codeStream.putfield(syntheticArg.matchingField); |
| } |
| } |
| } |
| |
| private void internalGenerateCode(ClassScope classScope, ClassFile classFile) { |
| |
| classFile.generateMethodInfoHeader(binding); |
| int methodAttributeOffset = classFile.contentsOffset; |
| int attributeNumber = classFile.generateMethodInfoAttribute(this.binding); |
| if ((!binding.isNative()) && (!binding.isAbstract())) { |
| |
| TypeDeclaration declaringType = classScope.referenceContext; |
| int codeAttributeOffset = classFile.contentsOffset; |
| classFile.generateCodeAttributeHeader(); |
| CodeStream codeStream = classFile.codeStream; |
| codeStream.reset(this, classFile); |
| |
| // initialize local positions - including initializer scope. |
| ReferenceBinding declaringClass = binding.declaringClass; |
| |
| int enumOffset = declaringClass.isEnum() ? 2 : 0; // String name, int ordinal |
| int argSlotSize = 1 + enumOffset; // this==aload0 |
| |
| if (declaringClass.isNestedType()){ |
| NestedTypeBinding nestedType = (NestedTypeBinding) declaringClass; |
| this.scope.extraSyntheticArguments = nestedType.syntheticOuterLocalVariables(); |
| scope.computeLocalVariablePositions(// consider synthetic arguments if any |
| nestedType.enclosingInstancesSlotSize + 1 + enumOffset, |
| codeStream); |
| argSlotSize += nestedType.enclosingInstancesSlotSize; |
| argSlotSize += nestedType.outerLocalVariablesSlotSize; |
| } else { |
| scope.computeLocalVariablePositions(1 + enumOffset, codeStream); |
| } |
| |
| if (arguments != null) { |
| for (int i = 0, max = arguments.length; i < max; i++) { |
| // arguments initialization for local variable debug attributes |
| LocalVariableBinding argBinding; |
| codeStream.addVisibleLocalVariable(argBinding = arguments[i].binding); |
| argBinding.recordInitializationStartPC(0); |
| TypeBinding argType; |
| if ((argType = argBinding.type) == LongBinding || (argType == DoubleBinding)) { |
| argSlotSize += 2; |
| } else { |
| argSlotSize++; |
| } |
| } |
| } |
| |
| MethodScope initializerScope = declaringType.initializerScope; |
| initializerScope.computeLocalVariablePositions(argSlotSize, codeStream); // offset by the argument size (since not linked to method scope) |
| |
| boolean needFieldInitializations = constructorCall == null || constructorCall.accessMode != ExplicitConstructorCall.This; |
| |
| // post 1.4 source level, synthetic initializations occur prior to explicit constructor call |
| boolean preInitSyntheticFields = scope.compilerOptions().targetJDK >= ClassFileConstants.JDK1_4; |
| |
| if (needFieldInitializations && preInitSyntheticFields){ |
| generateSyntheticFieldInitializationsIfNecessary(scope, codeStream, declaringClass); |
| } |
| // generate constructor call |
| if (constructorCall != null) { |
| constructorCall.generateCode(scope, codeStream); |
| } |
| // generate field initialization - only if not invoking another constructor call of the same class |
| if (needFieldInitializations) { |
| if (!preInitSyntheticFields){ |
| generateSyntheticFieldInitializationsIfNecessary(scope, codeStream, declaringClass); |
| } |
| // generate user field initialization |
| if (declaringType.fields != null) { |
| for (int i = 0, max = declaringType.fields.length; i < max; i++) { |
| FieldDeclaration fieldDecl; |
| if (!(fieldDecl = declaringType.fields[i]).isStatic()) { |
| fieldDecl.generateCode(initializerScope, codeStream); |
| } |
| } |
| } |
| } |
| // generate statements |
| if (statements != null) { |
| for (int i = 0, max = statements.length; i < max; i++) { |
| statements[i].generateCode(scope, codeStream); |
| } |
| } |
| if (this.needFreeReturn) { |
| codeStream.return_(); |
| } |
| // local variable attributes |
| codeStream.exitUserScope(scope); |
| codeStream.recordPositionsFrom(0, this.bodyEnd); |
| classFile.completeCodeAttribute(codeAttributeOffset); |
| attributeNumber++; |
| } |
| classFile.completeMethodInfo(methodAttributeOffset, attributeNumber); |
| |
| // if a problem got reported during code gen, then trigger problem method creation |
| if (ignoreFurtherInvestigation) { |
| throw new AbortMethod(scope.referenceCompilationUnit().compilationResult, null); |
| } |
| } |
| |
| public boolean isConstructor() { |
| |
| return true; |
| } |
| |
| public boolean isDefaultConstructor() { |
| |
| return this.isDefaultConstructor; |
| } |
| |
| public boolean isInitializationMethod() { |
| |
| return true; |
| } |
| |
| /* |
| * Returns true if the constructor is directly involved in a cycle. |
| * Given most constructors aren't, we only allocate the visited list |
| * lazily. |
| */ |
| public boolean isRecursive(ArrayList visited) { |
| |
| if (this.binding == null |
| || this.constructorCall == null |
| || this.constructorCall.binding == null |
| || this.constructorCall.isSuperAccess() |
| || !this.constructorCall.binding.isValidBinding()) { |
| return false; |
| } |
| |
| ConstructorDeclaration targetConstructor = |
| ((ConstructorDeclaration)this.scope.referenceType().declarationOf(constructorCall.binding.original())); |
| if (this == targetConstructor) return true; // direct case |
| |
| if (visited == null) { // lazy allocation |
| visited = new ArrayList(1); |
| } else { |
| int index = visited.indexOf(this); |
| if (index >= 0) return index == 0; // only blame if directly part of the cycle |
| } |
| visited.add(this); |
| |
| return targetConstructor.isRecursive(visited); |
| } |
| |
| public void parseStatements(Parser parser, CompilationUnitDeclaration unit) { |
| |
| //fill up the constructor body with its statements |
| if (ignoreFurtherInvestigation) |
| return; |
| if (isDefaultConstructor && this.constructorCall == null){ |
| this.constructorCall = SuperReference.implicitSuperConstructorCall(); |
| this.constructorCall.sourceStart = this.sourceStart; |
| this.constructorCall.sourceEnd = this.sourceEnd; |
| return; |
| } |
| parser.parse(this, unit); |
| |
| } |
| |
| public StringBuffer printBody(int indent, StringBuffer output) { |
| |
| output.append(" {"); //$NON-NLS-1$ |
| if (constructorCall != null) { |
| output.append('\n'); |
| constructorCall.printStatement(indent, output); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| if (statements != null) { |
| for (int i = 0; i < statements.length; i++) { |
| output.append('\n'); |
| statements[i].printStatement(indent, output); //$NON-NLS-1$ |
| } |
| } |
| output.append('\n'); |
| printIndent(indent == 0 ? 0 : indent - 1, output).append('}'); |
| return output; |
| } |
| |
| public void resolveJavadoc() { |
| |
| if (this.binding == null || this.javadoc != null) { |
| super.resolveJavadoc(); |
| } else if (!isDefaultConstructor) { |
| this.scope.problemReporter().javadocMissing(this.sourceStart, this.sourceEnd, this.binding.modifiers); |
| } |
| } |
| |
| /* |
| * Type checking for constructor, just another method, except for special check |
| * for recursive constructor invocations. |
| */ |
| public void resolveStatements() { |
| |
| if (!CharOperation.equals(this.scope.enclosingSourceType().sourceName, selector)){ |
| this.scope.problemReporter().missingReturnType(this); |
| } |
| |
| if (this.typeParameters != null) { |
| for (int i = 0, length = this.typeParameters.length; i < length; i++) { |
| this.typeParameters[i].resolve(this.scope); |
| } |
| } |
| |
| if (this.binding != null && this.binding.declaringClass.isAnnotationType()) { |
| this.scope.problemReporter().annotationTypeDeclarationCannotHaveConstructor(this); |
| } |
| // if null ==> an error has occurs at parsing time .... |
| if (this.constructorCall != null) { |
| // e.g. using super() in java.lang.Object |
| if (this.binding != null |
| && this.binding.declaringClass.id == T_JavaLangObject |
| && this.constructorCall.accessMode != ExplicitConstructorCall.This) { |
| if (this.constructorCall.accessMode == ExplicitConstructorCall.Super) { |
| this.scope.problemReporter().cannotUseSuperInJavaLangObject(this.constructorCall); |
| } |
| this.constructorCall = null; |
| } else { |
| this.constructorCall.resolve(this.scope); |
| } |
| } |
| if ((modifiers & AccSemicolonBody) != 0) { |
| this.scope.problemReporter().methodNeedBody(this); |
| } |
| super.resolveStatements(); |
| } |
| |
| public void traverse( |
| ASTVisitor visitor, |
| ClassScope classScope) { |
| |
| |
| if (visitor.visit(this, classScope)) { |
| if (this.annotations != null) { |
| int annotationsLength = this.annotations.length; |
| for (int i = 0; i < annotationsLength; i++) |
| this.annotations[i].traverse(visitor, scope); |
| } |
| if (this.typeParameters != null) { |
| int typeParametersLength = this.typeParameters.length; |
| for (int i = 0; i < typeParametersLength; i++) { |
| this.typeParameters[i].traverse(visitor, scope); |
| } |
| } |
| if (arguments != null) { |
| int argumentLength = arguments.length; |
| for (int i = 0; i < argumentLength; i++) |
| arguments[i].traverse(visitor, scope); |
| } |
| if (thrownExceptions != null) { |
| int thrownExceptionsLength = thrownExceptions.length; |
| for (int i = 0; i < thrownExceptionsLength; i++) |
| thrownExceptions[i].traverse(visitor, scope); |
| } |
| if (constructorCall != null) |
| constructorCall.traverse(visitor, scope); |
| if (statements != null) { |
| int statementsLength = statements.length; |
| for (int i = 0; i < statementsLength; i++) |
| statements[i].traverse(visitor, scope); |
| } |
| } |
| visitor.endVisit(this, classScope); |
| } |
| public TypeParameter[] typeParameters() { |
| return this.typeParameters; |
| } |
| } |