| /******************************************************************************* |
| * Copyright (c) 2000, 2017 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 |
| * Technical University Berlin - extended API and implementation |
| * Patrick Wienands <pwienands@abit.de> - Contribution for bug 393749 |
| * Stephan Herrmann - Contribution for |
| * bug 331649 - [compiler][null] consider null annotations for fields |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.compiler.ast; |
| |
| import org.eclipse.jdt.internal.compiler.ASTVisitor; |
| import org.eclipse.jdt.internal.compiler.ClassFile; |
| import org.eclipse.jdt.internal.compiler.CompilationResult; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; |
| import org.eclipse.jdt.internal.compiler.codegen.CodeStream; |
| import org.eclipse.jdt.internal.compiler.codegen.ConstantPool; |
| import org.eclipse.jdt.internal.compiler.codegen.Opcodes; |
| import org.eclipse.jdt.internal.compiler.flow.ExceptionHandlingFlowContext; |
| import org.eclipse.jdt.internal.compiler.flow.FlowInfo; |
| import org.eclipse.jdt.internal.compiler.flow.InitializationFlowContext; |
| import org.eclipse.jdt.internal.compiler.impl.Constant; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.ClassScope; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodScope; |
| import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; |
| import org.eclipse.jdt.internal.compiler.parser.Parser; |
| import org.eclipse.jdt.internal.compiler.problem.AbortMethod; |
| |
| /** |
| * OTDT changes: |
| * What: No assignment analysis for copied fields (initialized in copied code). |
| * |
| * What: detect and report roleCantInitializeStaticField(). |
| */ |
| public class Clinit extends AbstractMethodDeclaration { |
| private static int ENUM_CONSTANTS_THRESHOLD = 2000; |
| |
| private FieldBinding assertionSyntheticFieldBinding = null; |
| private FieldBinding classLiteralSyntheticField = null; |
| |
| public Clinit(CompilationResult compilationResult) { |
| super(compilationResult); |
| this.modifiers = 0; |
| this.selector = TypeConstants.CLINIT; |
| } |
| |
| public void analyseCode( |
| ClassScope classScope, |
| InitializationFlowContext staticInitializerFlowContext, |
| FlowInfo flowInfo) { |
| |
| if (this.ignoreFurtherInvestigation) |
| return; |
| try { |
| ExceptionHandlingFlowContext clinitContext = |
| new ExceptionHandlingFlowContext( |
| staticInitializerFlowContext.parent, |
| this, |
| Binding.NO_EXCEPTIONS, |
| staticInitializerFlowContext, |
| this.scope, |
| FlowInfo.DEAD_END); |
| |
| // check for missing returning path |
| if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) == 0) { |
| this.bits |= ASTNode.NeedFreeReturn; |
| } |
| |
| // check missing blank final field initializations |
| flowInfo = flowInfo.mergedWith(staticInitializerFlowContext.initsOnReturn); |
| FieldBinding[] fields = this.scope.enclosingSourceType().fields(); |
| for (int i = 0, count = fields.length; i < count; i++) { |
| FieldBinding field = fields[i]; |
| //{ObjectTeams: don't check copied fields: |
| if (field.copyInheritanceSrc != null) continue; |
| // SH} |
| if (field.isStatic()) { |
| if (!flowInfo.isDefinitelyAssigned(field)) { |
| if (field.isFinal()) { |
| this.scope.problemReporter().uninitializedBlankFinalField( |
| field, |
| this.scope.referenceType().declarationOf(field.original())); |
| // can complain against the field decl, since only one <clinit> |
| } else if (field.isNonNull()) { |
| this.scope.problemReporter().uninitializedNonNullField( |
| field, |
| this.scope.referenceType().declarationOf(field.original())); |
| } |
| } |
| } |
| } |
| // check static initializers thrown exceptions |
| staticInitializerFlowContext.checkInitializerExceptions( |
| this.scope, |
| clinitContext, |
| flowInfo); |
| } catch (AbortMethod e) { |
| this.ignoreFurtherInvestigation = true; |
| } |
| } |
| |
| /** |
| * Bytecode generation for a <clinit> method |
| * |
| * @param classScope org.eclipse.jdt.internal.compiler.lookup.ClassScope |
| * @param classFile org.eclipse.jdt.internal.compiler.codegen.ClassFile |
| */ |
| @Override |
| public void generateCode(ClassScope classScope, ClassFile classFile) { |
| |
| int clinitOffset = 0; |
| if (this.ignoreFurtherInvestigation) { |
| // should never have to add any <clinit> problem method |
| return; |
| } |
| CompilationResult unitResult = null; |
| int problemCount = 0; |
| if (classScope != null) { |
| TypeDeclaration referenceContext = classScope.referenceContext; |
| if (referenceContext != null) { |
| unitResult = referenceContext.compilationResult(); |
| problemCount = unitResult.problemCount; |
| } |
| } |
| boolean restart = false; |
| do { |
| try { |
| clinitOffset = classFile.contentsOffset; |
| this.generateCode(classScope, classFile, clinitOffset); |
| restart = false; |
| } catch (AbortMethod e) { |
| // should never occur |
| // the clinit referenceContext is the type declaration |
| // All clinit problems will be reported against the type: AbortType instead of AbortMethod |
| // reset the contentsOffset to the value before generating the clinit code |
| // decrement the number of method info as well. |
| // This is done in the addProblemMethod and addProblemConstructor for other |
| // cases. |
| if (e.compilationResult == CodeStream.RESTART_IN_WIDE_MODE) { |
| // a branch target required a goto_w, restart code gen in wide mode. |
| classFile.contentsOffset = clinitOffset; |
| classFile.methodCount--; |
| classFile.codeStream.resetInWideMode(); // request wide mode |
| // reset the problem count to prevent reporting the same warning twice |
| if (unitResult != null) { |
| unitResult.problemCount = problemCount; |
| } |
| // restart method generation |
| restart = true; |
| } else if (e.compilationResult == CodeStream.RESTART_CODE_GEN_FOR_UNUSED_LOCALS_MODE) { |
| classFile.contentsOffset = clinitOffset; |
| classFile.methodCount--; |
| classFile.codeStream.resetForCodeGenUnusedLocals(); |
| // reset the problem count to prevent reporting the same warning twice |
| if (unitResult != null) { |
| unitResult.problemCount = problemCount; |
| } |
| // restart method generation |
| restart = true; |
| } else { |
| // produce a problem method accounting for this fatal error |
| classFile.contentsOffset = clinitOffset; |
| classFile.methodCount--; |
| restart = false; |
| } |
| } |
| } while (restart); |
| } |
| |
| /** |
| * Bytecode generation for a <clinit> method |
| * |
| * @param classScope org.eclipse.jdt.internal.compiler.lookup.ClassScope |
| * @param classFile org.eclipse.jdt.internal.compiler.codegen.ClassFile |
| */ |
| private void generateCode( |
| ClassScope classScope, |
| ClassFile classFile, |
| int clinitOffset) { |
| |
| ConstantPool constantPool = classFile.constantPool; |
| int constantPoolOffset = constantPool.currentOffset; |
| int constantPoolIndex = constantPool.currentIndex; |
| classFile.generateMethodInfoHeaderForClinit(); |
| int codeAttributeOffset = classFile.contentsOffset; |
| classFile.generateCodeAttributeHeader(); |
| CodeStream codeStream = classFile.codeStream; |
| resolve(classScope); |
| |
| codeStream.reset(this, classFile); |
| TypeDeclaration declaringType = classScope.referenceContext; |
| |
| // initialize local positions - including initializer scope. |
| MethodScope staticInitializerScope = declaringType.staticInitializerScope; |
| staticInitializerScope.computeLocalVariablePositions(0, codeStream); |
| |
| // 1.4 feature |
| // This has to be done before any other initialization |
| if (this.assertionSyntheticFieldBinding != null) { |
| // generate code related to the activation of assertion for this class |
| codeStream.generateClassLiteralAccessForType( |
| classScope.outerMostClassScope().enclosingSourceType(), |
| this.classLiteralSyntheticField); |
| codeStream.invokeJavaLangClassDesiredAssertionStatus(); |
| BranchLabel falseLabel = new BranchLabel(codeStream); |
| codeStream.ifne(falseLabel); |
| codeStream.iconst_1(); |
| BranchLabel jumpLabel = new BranchLabel(codeStream); |
| codeStream.decrStackSize(1); |
| codeStream.goto_(jumpLabel); |
| falseLabel.place(); |
| codeStream.iconst_0(); |
| jumpLabel.place(); |
| codeStream.fieldAccess(Opcodes.OPC_putstatic, this.assertionSyntheticFieldBinding, null /* default declaringClass */); |
| } |
| boolean isJava9 = classScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK9; |
| // generate static fields/initializers/enum constants |
| final FieldDeclaration[] fieldDeclarations = declaringType.fields; |
| int sourcePosition = -1; |
| int remainingFieldCount = 0; |
| if (TypeDeclaration.kind(declaringType.modifiers) == TypeDeclaration.ENUM_DECL) { |
| int enumCount = declaringType.enumConstantsCounter; |
| if (!isJava9 && enumCount > ENUM_CONSTANTS_THRESHOLD) { |
| // generate synthetic methods to initialize all the enum constants |
| int begin = -1; |
| int count = 0; |
| if (fieldDeclarations != null) { |
| int max = fieldDeclarations.length; |
| for (int i = 0; i < max; i++) { |
| FieldDeclaration fieldDecl = fieldDeclarations[i]; |
| if (fieldDecl.isStatic()) { |
| if (fieldDecl.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT) { |
| if (begin == -1) { |
| begin = i; |
| } |
| count++; |
| if (count > ENUM_CONSTANTS_THRESHOLD) { |
| SyntheticMethodBinding syntheticMethod = declaringType.binding.addSyntheticMethodForEnumInitialization(begin, i); |
| codeStream.invoke(Opcodes.OPC_invokestatic, syntheticMethod, null /* default declaringClass */); |
| begin = i; |
| count = 1; |
| } |
| } else { |
| remainingFieldCount++; |
| } |
| } |
| } |
| if (count != 0) { |
| // add last synthetic method |
| SyntheticMethodBinding syntheticMethod = declaringType.binding.addSyntheticMethodForEnumInitialization(begin, max); |
| codeStream.invoke(Opcodes.OPC_invokestatic, syntheticMethod, null /* default declaringClass */); |
| } |
| } |
| } else if (fieldDeclarations != null) { |
| for (int i = 0, max = fieldDeclarations.length; i < max; i++) { |
| FieldDeclaration fieldDecl = fieldDeclarations[i]; |
| if (fieldDecl.isStatic()) { |
| if (fieldDecl.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT) { |
| fieldDecl.generateCode(staticInitializerScope, codeStream); |
| } else { |
| remainingFieldCount++; |
| } |
| } |
| } |
| } |
| // enum need to initialize $VALUES synthetic cache of enum constants |
| // $VALUES := new <EnumType>[<enumCount>] |
| codeStream.generateInlinedValue(enumCount); |
| codeStream.anewarray(declaringType.binding); |
| if (enumCount > 0) { |
| if (fieldDeclarations != null) { |
| for (int i = 0, max = fieldDeclarations.length; i < max; i++) { |
| FieldDeclaration fieldDecl = fieldDeclarations[i]; |
| // $VALUES[i] = <enum-constant-i> |
| if (fieldDecl.getKind() == AbstractVariableDeclaration.ENUM_CONSTANT) { |
| codeStream.dup(); |
| codeStream.generateInlinedValue(fieldDecl.binding.id); |
| codeStream.fieldAccess(Opcodes.OPC_getstatic, fieldDecl.binding, null /* default declaringClass */); |
| codeStream.aastore(); |
| } |
| } |
| } |
| } |
| codeStream.fieldAccess(Opcodes.OPC_putstatic, declaringType.enumValuesSyntheticfield, null /* default declaringClass */); |
| if (remainingFieldCount != 0) { |
| // if fields that are not enum constants need to be generated (static initializer/static field) |
| for (int i = 0, max = fieldDeclarations.length; i < max && remainingFieldCount >= 0; i++) { |
| FieldDeclaration fieldDecl = fieldDeclarations[i]; |
| switch (fieldDecl.getKind()) { |
| case AbstractVariableDeclaration.ENUM_CONSTANT : |
| break; |
| case AbstractVariableDeclaration.INITIALIZER : |
| if (!fieldDecl.isStatic()) { |
| break; |
| } |
| remainingFieldCount--; |
| sourcePosition = ((Initializer) fieldDecl).block.sourceEnd; |
| fieldDecl.generateCode(staticInitializerScope, codeStream); |
| break; |
| case AbstractVariableDeclaration.FIELD : |
| if (!fieldDecl.binding.isStatic()) { |
| break; |
| } |
| remainingFieldCount--; |
| sourcePosition = fieldDecl.declarationEnd; |
| fieldDecl.generateCode(staticInitializerScope, codeStream); |
| break; |
| } |
| } |
| } |
| } else { |
| if (fieldDeclarations != null) { |
| for (int i = 0, max = fieldDeclarations.length; i < max; i++) { |
| FieldDeclaration fieldDecl = fieldDeclarations[i]; |
| switch (fieldDecl.getKind()) { |
| case AbstractVariableDeclaration.INITIALIZER : |
| if (!fieldDecl.isStatic()) |
| break; |
| //{ObjectTeams: note: static initializer in inner type is already prohibited in Java. SH}// |
| sourcePosition = ((Initializer) fieldDecl).block.sourceEnd; |
| fieldDecl.generateCode(staticInitializerScope, codeStream); |
| break; |
| case AbstractVariableDeclaration.FIELD : |
| if (!fieldDecl.binding.isStatic()) |
| break; |
| //{ObjectTeams: roles cannot initialize static fields with non-constant values: |
| int previousPosition = codeStream.position; |
| // orig: |
| sourcePosition = fieldDecl.declarationEnd; |
| fieldDecl.generateCode(staticInitializerScope, codeStream); |
| // :giro |
| if ( previousPosition != codeStream.position // field has indeed generated code |
| && classScope.referenceContext.isRole()) |
| { |
| classScope.problemReporter().roleCantInitializeStaticField(fieldDecl); |
| } |
| // SH} |
| break; |
| } |
| } |
| } |
| //{ObjectTeams: also analyze fields in the interface part: |
| if (declaringType.isRole() && declaringType.getRoleModel() != null) { |
| TypeDeclaration ifcPart = declaringType.getRoleModel().getInterfaceAst(); |
| if (ifcPart != null && ifcPart.fields != null) |
| for (FieldDeclaration ifcField : ifcPart.fields) |
| if (ifcField.binding.constant() == Constant.NotAConstant) |
| classScope.problemReporter().roleCantInitializeStaticField(ifcField); |
| } |
| // SH} |
| if (isJava9) { |
| declaringType.binding.generateSyntheticFinalFieldInitialization(codeStream); |
| } |
| } |
| |
| if (codeStream.position == 0) { |
| // do not need to output a Clinit if no bytecodes |
| // so we reset the offset inside the byte array contents. |
| classFile.contentsOffset = clinitOffset; |
| // like we don't addd a method we need to undo the increment on the method count |
| classFile.methodCount--; |
| // reset the constant pool to its state before the clinit |
| constantPool.resetForClinit(constantPoolIndex, constantPoolOffset); |
| } else { |
| if ((this.bits & ASTNode.NeedFreeReturn) != 0) { |
| int before = codeStream.position; |
| codeStream.return_(); |
| if (sourcePosition != -1) { |
| // expand the last initializer variables to include the trailing return |
| codeStream.recordPositionsFrom(before, sourcePosition); |
| } |
| } |
| // Record the end of the clinit: point to the declaration of the class |
| codeStream.recordPositionsFrom(0, declaringType.sourceStart); |
| classFile.completeCodeAttributeForClinit(codeAttributeOffset); |
| } |
| } |
| |
| @Override |
| public boolean isClinit() { |
| |
| return true; |
| } |
| |
| @Override |
| public boolean isInitializationMethod() { |
| |
| return true; |
| } |
| |
| @Override |
| public boolean isStatic() { |
| |
| return true; |
| } |
| |
| @Override |
| public void parseStatements(Parser parser, CompilationUnitDeclaration unit) { |
| //the clinit is filled by hand .... |
| } |
| |
| @Override |
| public StringBuffer print(int tab, StringBuffer output) { |
| |
| printIndent(tab, output).append("<clinit>()"); //$NON-NLS-1$ |
| printBody(tab + 1, output); |
| return output; |
| } |
| |
| @Override |
| public void resolve(ClassScope classScope) { |
| |
| this.scope = new MethodScope(classScope, classScope.referenceContext, true); |
| } |
| |
| @Override |
| public void traverse( |
| ASTVisitor visitor, |
| ClassScope classScope) { |
| |
| visitor.visit(this, classScope); |
| visitor.endVisit(this, classScope); |
| } |
| |
| public void setAssertionSupport(FieldBinding assertionSyntheticFieldBinding, boolean needClassLiteralField) { |
| |
| this.assertionSyntheticFieldBinding = assertionSyntheticFieldBinding; |
| |
| // we need to add the field right now, because the field infos are generated before the methods |
| if (needClassLiteralField) { |
| SourceTypeBinding sourceType = |
| this.scope.outerMostClassScope().enclosingSourceType(); |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=22334 |
| if (!sourceType.isInterface() && !sourceType.isBaseType()) { |
| this.classLiteralSyntheticField = sourceType.addSyntheticFieldForClassLiteral(sourceType, this.scope); |
| } |
| } |
| } |
| |
| } |