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 Patrick Wienands - Contribution for bug 393749 Stephan Herrmann - Contribution for bug 331649 - [compiler][null] consider null annotations for fields
/******************************************************************************* * 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 * 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.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; 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]; 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 method
Params:
  • classScope – org.eclipse.jdt.internal.compiler.lookup.ClassScope
  • classFile – org.eclipse.jdt.internal.compiler.codegen.ClassFile
/** * 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 method
Params:
  • classScope – org.eclipse.jdt.internal.compiler.lookup.ClassScope
  • classFile – org.eclipse.jdt.internal.compiler.codegen.ClassFile
/** * 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, 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; sourcePosition = ((Initializer) fieldDecl).block.sourceEnd; fieldDecl.generateCode(staticInitializerScope, codeStream); break; case AbstractVariableDeclaration.FIELD : if (!fieldDecl.binding.isStatic()) break; sourcePosition = fieldDecl.declarationEnd; fieldDecl.generateCode(staticInitializerScope, codeStream); break; } } } 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, classScope); } } @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); } } } }