/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.parser.statements; import gw.internal.gosu.parser.DynamicFunctionSymbol; import gw.internal.gosu.parser.GosuClassParseInfo; import gw.internal.gosu.parser.IGosuClassInternal; import gw.internal.gosu.parser.IGosuProgramInternal; import gw.internal.gosu.parser.Symbol; import gw.internal.gosu.parser.ThisConstructorFunctionSymbol; import gw.lang.parser.IExpression; import gw.lang.parser.IInitializerSymbol; import gw.lang.parser.IParseTree; import gw.lang.parser.IParsedElement; import gw.lang.parser.IStatement; import gw.lang.parser.ISymbol; import gw.lang.parser.Keyword; import gw.lang.parser.StandardSymbolTable; import gw.lang.parser.exceptions.ParseException; import gw.lang.parser.expressions.IBlockExpression; import gw.lang.parser.expressions.IIdentifierExpression; import gw.lang.parser.expressions.IMethodCallExpression; import gw.lang.parser.expressions.IVarStatement; import gw.lang.parser.resources.Res; import gw.lang.parser.resources.ResourceKey; import gw.lang.parser.statements.IAssignmentStatement; import gw.lang.parser.statements.IBreakStatement; import gw.lang.parser.statements.ICaseClause; import gw.lang.parser.statements.ICatchClause; import gw.lang.parser.statements.IClassStatement; import gw.lang.parser.statements.IContinueStatement; import gw.lang.parser.statements.IDoWhileStatement; import gw.lang.parser.statements.IFunctionStatement; import gw.lang.parser.statements.IHideFieldNoOpStatement; import gw.lang.parser.statements.IIfStatement; import gw.lang.parser.statements.ILoopStatement; import gw.lang.parser.statements.IMemberAssignmentStatement; import gw.lang.parser.statements.IMethodCallStatement; import gw.lang.parser.statements.IStatementList; import gw.lang.parser.statements.ISwitchStatement; import gw.lang.parser.statements.ITerminalStatement; import gw.lang.parser.statements.ITryCatchFinallyStatement; import gw.lang.parser.statements.IUsingStatement; import gw.lang.reflect.IEnumType; import gw.lang.reflect.IType; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.IGosuProgram; import java.util.ArrayList; import java.util.List; /** */ public class VarInitializationVerifier { public enum AssignedState { Unassigned, Partially, Fully } private boolean _bFinalOnly; private VarInitializationVerifier( boolean bFinalOnly ) { _bFinalOnly = bFinalOnly; } private boolean isFinalOnly() { return _bFinalOnly; } /** * Verifies initialization of final fields in the provided class and, recursively, all nested inner classes: * <ol> * <li>Verifies that a final field is fully initialized either in the declaration or in the constructor[s]</li> * <li>Verifies that an assignment to a final field is mutually exclusive wrt other assignments to the field</li> * <li>Verifies that a reference to a final field is in a position in the source where the final field is fully initialized</li> * </ol> */ public static void verifyFinalFields( IGosuClass gsClass ) { new VarInitializationVerifier( true ).verifyFields( (IGosuClassInternal)gsClass ); } /** * Verifies initialization of local vars in the provided class and, recursively, all nested inner classes: * <ol> * <li>Verifies that a final local var is fully initialized either in the declaration or in the scope of the var</li> * <li>Verifies that an assignment to a final local var is mutually exclusive wrt other assignments to the var</li> * <li>Verifies that a reference to any local var, not just finals, is in a position in the source where the var is fully initialized</li> * </ol> */ public static void verifyLocalVars( IGosuClass gsClass, boolean bFinalOnly ) { new VarInitializationVerifier( bFinalOnly ).verifyLocals( (IGosuClassInternal)gsClass); } private void verifyLocals( IGosuClassInternal gsClass ) { GosuClassParseInfo parseInfo = gsClass.getParseInfo(); for( DynamicFunctionSymbol dfs : parseInfo.getConstructorFunctions().values() ) { IStatement stmt = (IStatement)dfs.getValueDirectly(); verifyLocalsRecursively( stmt ); } for( DynamicFunctionSymbol dfs : parseInfo.getMemberFunctions().values() ) { IStatement stmt = (IStatement)dfs.getValueDirectly(); verifyLocalsRecursively( stmt ); } for( DynamicFunctionSymbol dfs : parseInfo.getStaticFunctions() ) { IStatement stmt = (IStatement)dfs.getValueDirectly(); verifyLocalsRecursively( stmt ); } } private void verifyLocalsRecursively( IParsedElement pe ) { if( pe == null || pe instanceof IBlockExpression || pe instanceof IClassStatement ) { return; } if( pe instanceof IVarStatement && (!isFinalOnly() || ((IVarStatement)pe).isFinal()) ) { verifyLocalVar( (IVarStatement)pe ); } IParseTree location = pe.getLocation(); if( location != null && location.getChildren() != null ) { for( IParseTree child: location.getChildren() ) { verifyLocalsRecursively( child.getParsedElement() ); } } } private void verifyLocalVar( IVarStatement varStmt ) { boolean bFinal = varStmt.isFinal(); boolean bAssigned = varStmt.getAsExpression() != null || (varStmt.getSymbol() instanceof Symbol && ((Symbol)varStmt.getSymbol()).isImplicitlyInitialized()); IStatement enclosingStatement = findEnclosingStatement( varStmt.getParent() ); List<IStatement> trailingStmts = findTrailingStmts( enclosingStatement, varStmt ); ArrayList<AssignmentOrReference> assignments = new ArrayList<AssignmentOrReference>(); AssignedState state = getAssignedStateForStatements( varStmt.getSymbol(), assignments, trailingStmts.toArray( new IStatement[trailingStmts.size()] ), AssignedState.Unassigned ); verifyVar( varStmt, false, bFinal, bAssigned, assignments ); if( bFinal && !bAssigned && state != AssignedState.Fully ) { ParseException parseException = new ParseException( varStmt.getLineNum(), 1, varStmt.getLocation().getColumn(), varStmt.getLocation().getOffset(), varStmt.getLocation().getExtent(), new StandardSymbolTable(), Res.MSG_VAR_MIGHT_NOT_HAVE_BEEN_INIT, varStmt.getSymbol().getName() ); varStmt.addParseException( parseException ); } } private List<IStatement> findTrailingStmts( IStatement enclosingStatement, IVarStatement finalVar ) { IParseTree location = enclosingStatement.getLocation(); List<IStatement> trailingStmts = new ArrayList<IStatement>(); if( location != null ) { boolean bFound = false; for( IParseTree pt: location.getChildren() ) { IParsedElement child = pt.getParsedElement(); if( child instanceof IStatement ) { if( !bFound ) { if( child == finalVar ) { bFound = true; } } else { trailingStmts.add( (IStatement)child ); } } } } return trailingStmts; } private IStatement findEnclosingStatement( IParsedElement pe ) { if( pe instanceof IStatement ) { return (IStatement)pe; } return findEnclosingStatement( pe.getParent() ); } private void verifyFields( IGosuClassInternal gsClass ) { // Instance fields for( VarStatement varStmt: gsClass.getParseInfo().getMemberFields().values() ) { if( varStmt.isFinal() ) { verifyInstanceField( gsClass, varStmt ); } } // Static fields for( VarStatement varStmt: gsClass.getParseInfo().getStaticFields().values() ) { if( varStmt.isFinal() ) { verifyStaticField( gsClass, varStmt ); } } } private void verifyInstanceField( IGosuClassInternal gsClass, VarStatement varStmt ) { boolean bAssigned = varStmt.getAsExpression() != null; AssignedState overall = null; if( gsClass instanceof IGosuProgramInternal ) { for( DynamicFunctionSymbol dfs : gsClass.getParseInfo().getMemberFunctions().values() ) { if( dfs.getDisplayName().equals( "evaluate" ) ) { overall = verifyInstanceFieldInConstructor( varStmt, bAssigned, overall, dfs ); } } } else { for( DynamicFunctionSymbol dfs: gsClass.getParseInfo().getConstructorFunctions().values() ) { overall = verifyInstanceFieldInConstructor( varStmt, bAssigned, overall, dfs ); } verifyInstanceFieldInOtherInstanceFields( gsClass, varStmt ); } if( !bAssigned && overall != AssignedState.Fully ) { ParseException parseException = new ParseException( varStmt.getLineNum(), 1, varStmt.getLocation().getColumn(), varStmt.getLocation().getOffset(), varStmt.getLocation().getExtent(), new StandardSymbolTable(), Res.MSG_VAR_MIGHT_NOT_HAVE_BEEN_INIT, varStmt.getSymbol().getName() ); varStmt.addParseException( parseException ); } } private void verifyInstanceFieldInOtherInstanceFields( IGosuClassInternal gsClass, VarStatement varStmt ) { if( gsClass instanceof IGosuProgram ) { return; } ArrayList<AssignmentOrReference> assignments = new ArrayList<AssignmentOrReference>(); AssignedState state = AssignedState.Unassigned; for( IVarStatement vs: gsClass.getParseInfo().getMemberFields().values() ) { if( vs == varStmt ) { if( varStmt.getAsExpression() != null ) { assignments.add( new AssignmentOrReference( vs, assignments, state ) ); state = AssignedState.Fully; } } else { IExpression expr = vs.getAsExpression(); if( expr != null ) { getAssignedState( varStmt.getSymbol(), expr, assignments, state ); } } } markErrorsOnBadAssignmentsAndReferences( varStmt, true, true, assignments ); } private AssignedState verifyInstanceFieldInConstructor( VarStatement varStmt, boolean bAssigned, AssignedState overall, DynamicFunctionSymbol dfs ) { IStatement stmt = (IStatement)dfs.getValueDirectly(); ArrayList<AssignmentOrReference> assignments = new ArrayList<AssignmentOrReference>(); AssignedState initState = getAssignedState( varStmt.getSymbol(), dfs.getInitializer(), assignments, AssignedState.Unassigned ); AssignedState state = getAssignedState( varStmt.getSymbol(), stmt, assignments, initState ); state = state.ordinal() > initState.ordinal() ? state : initState; overall = overall == null ? state : overall.ordinal() > state.ordinal() ? state : overall; verifyVar( varStmt, true, true, bAssigned, assignments ); return overall; } private void verifyVar( IVarStatement varStmt, boolean bField, boolean bFinal, boolean bAssigned, ArrayList<AssignmentOrReference> assignments ) { if( bAssigned ) { if( bFinal ) { markErrorsOnAssignmentsToFinal( varStmt, assignments ); } } else { markErrorsOnBadAssignmentsAndReferences( varStmt, bField, bFinal, assignments ); } } private void markErrorsOnAssignmentsToFinal( IVarStatement varStmt, ArrayList<AssignmentOrReference> assignments ) { for( AssignmentOrReference assignment : assignments ) { if( assignment.getStmt() instanceof IStatement ) { IStatement s = (IStatement)assignment.getStmt(); ParseException parseException = new ParseException( s.getLineNum(), 1, s.getLocation().getColumn(), s.getLocation().getOffset(), s.getLocation().getExtent(), new StandardSymbolTable(), Res.MSG_CANNOT_ASSIGN_VALUE_TO_FINAL_VAR, varStmt.getSymbol().getName() ); s.addParseException( parseException ); } } } private void markErrorsOnBadAssignmentsAndReferences( IVarStatement varStmt, boolean bField, boolean bFinal, ArrayList<AssignmentOrReference> assignments ) { for( AssignmentOrReference assignment : assignments ) { ResourceKey resKey; if( assignment.isBad() ) { if( assignment.getStmt() instanceof IStatement ) { if( bFinal ) { resKey = assignment.isInLoop() ? Res.MSG_VAR_MIGHT_ALREADY_HAVE_BEEN_INIT_LOOP : Res.MSG_VAR_MIGHT_ALREADY_HAVE_BEEN_INIT; } else { continue; } } else if( bFinal || (!bField && !isFinalOnly()) ) { resKey = Res.MSG_VAR_MIGHT_NOT_HAVE_BEEN_INIT; } else { continue; } IParsedElement s = assignment.getStmt(); ParseException parseException = new ParseException( s.getLineNum(), 1, s.getLocation().getColumn(), s.getLocation().getOffset(), s.getLocation().getExtent(), new StandardSymbolTable(), resKey, varStmt.getSymbol().getName() ); s.addParseException( parseException ); } } } private void verifyStaticField( IGosuClassInternal gsClass, VarStatement varStmt ) { //## todo: introduce static constructors boolean bAssigned = varStmt.getHasInitializer(); if( !bAssigned ) { ParseException parseException = new ParseException( varStmt.getLineNum(), 1, varStmt.getLocation().getColumn(), varStmt.getLocation().getOffset(), varStmt.getLocation().getExtent(), new StandardSymbolTable(), Res.MSG_VAR_MIGHT_NOT_HAVE_BEEN_INIT, varStmt.getSymbol().getName() ); varStmt.addParseException( parseException ); } } private AssignedState getAssignedState( ISymbol sym, IParsedElement s, ArrayList<AssignmentOrReference> assignments, AssignedState localState ) { if( s == null ) { return AssignedState.Unassigned; } AssignedState retState = null; if( s instanceof IFunctionStatement ) { IStatement stmt = (IStatement)((IFunctionStatement)s).getDynamicFunctionSymbol().getValueDirectly(); retState = getAssignedState( sym, stmt, assignments, localState ); } else if( s instanceof IAssignmentStatement ) { IAssignmentStatement stmt = (IAssignmentStatement)s; if( stmt.getIdentifier().getSymbol().getName().equals( sym.getName() ) ) { assignments.add( new AssignmentOrReference( stmt, assignments, localState ) ); retState = AssignedState.Fully; } else { retState = AssignedState.Unassigned; } // Process the rhs for identifiers getAssignedState( sym, stmt.getExpression(), assignments, localState ); } else if( s instanceof IHideFieldNoOpStatement ) { IHideFieldNoOpStatement stmt = (IHideFieldNoOpStatement)s; IVarStatement varStmt = stmt.getVarStmt(); if( varStmt.getAsExpression() != null && varStmt.getSymbol().getName().equals( sym.getName() ) ) { assignments.add( new AssignmentOrReference( stmt, assignments, localState ) ); retState = AssignedState.Fully; } else { retState = AssignedState.Unassigned; } // Process the rhs for identifiers getAssignedState( sym, varStmt.getAsExpression(), assignments, localState ); } else if( s instanceof IMemberAssignmentStatement ) { IMemberAssignmentStatement stmt = (IMemberAssignmentStatement)s; IExpression rootExpr = stmt.getRootExpression(); if( rootExpr instanceof IIdentifierExpression && Keyword.KW_this.getName().equals( ((IIdentifierExpression)rootExpr).getSymbol().getName() ) && stmt.getMemberName().equals( sym.getName() ) ) { assignments.add( new AssignmentOrReference( stmt, assignments, localState ) ); retState = AssignedState.Fully; } else { retState = AssignedState.Unassigned; // Process the lhs root for identifiers getAssignedState( sym, stmt.getRootExpression(), assignments, localState ); } // Process the rhs for identifiers getAssignedState( sym, stmt.getExpression(), assignments, localState ); } else if( s instanceof IStatementList ) { IStatement[] statements = ((IStatementList)s).getStatements(); retState = getAssignedStateForStatements( sym, assignments, statements, localState ); } else if( s instanceof ILoopStatement ) { int iSize = assignments.size(); AssignedState state = getAssignedState( sym, ((ILoopStatement)s).getStatement(), assignments, localState ); if( iSize < assignments.size() ) { if( sym.isFinal() ) { // Re-evaluate whether or not the assignments in the loop are bad; they're only good if covered by a terminal statement for( int i = iSize; i < assignments.size(); i++ ) { AssignmentOrReference csr = assignments.get( i ); csr.determineBad( assignments, true ); } } } // Loops can't be fully assigned unless the condition is boolean literal true or it's a do-while loop retState = state == AssignedState.Fully && !((ILoopStatement)s).isConditionLiteralTrue() && !(s instanceof IDoWhileStatement) ? AssignedState.Partially : state; // Process the loop condition/expression for identifiers getAssignedState( sym, ((ILoopStatement)s).getExpression(), assignments, localState ); } else if( s instanceof IMethodCallStatement ) { AssignedState state = AssignedState.Unassigned; IMethodCallExpression methodCall = ((IMethodCallStatement)s).getMethodCall(); if( methodCall.getFunctionSymbol() instanceof ThisConstructorFunctionSymbol ) { IStatement stmt = (IStatement)((ThisConstructorFunctionSymbol)methodCall.getFunctionSymbol()).getDelegate().getValueDirectly(); state = getAssignedState( sym, stmt, new ArrayList<AssignmentOrReference>()/*don't want these*/, localState ); if( state != AssignedState.Unassigned ) { assignments.add( new AssignmentOrReference( (IStatement)s, assignments, localState ) ); } } retState = state; // Process the args for identifiers IMethodCallExpression methodCallExpr = ((IMethodCallStatement)s).getMethodCall(); if( methodCallExpr != null ) { IExpression[] args = methodCallExpr.getArgs(); if( args != null ) { for( IExpression expr: args ) { getAssignedState( sym, expr, assignments, localState ); } } } } else if( s instanceof IIfStatement ) { IIfStatement stmt = (IIfStatement)s; AssignedState mainStmtState = getAssignedState( sym, stmt.getStatement(), assignments, localState ); IStatement elseStmt = stmt.getElseStatement(); if( elseStmt != null ) { AssignedState elseStmtState = getAssignedState( sym, elseStmt, assignments, localState ); switch( mainStmtState ) { case Fully: retState = elseStmtState == AssignedState.Fully ? AssignedState.Fully : AssignedState.Partially; break; case Partially: retState = AssignedState.Partially; break; case Unassigned: retState = elseStmtState == AssignedState.Unassigned ? AssignedState.Unassigned : AssignedState.Partially; break; } } else { retState = mainStmtState == AssignedState.Unassigned ? AssignedState.Unassigned : AssignedState.Partially; } // Process condition expr for identifiers getAssignedState( sym, stmt.getExpression(), assignments, localState ); } else if( s instanceof ISwitchStatement ) { AssignedState state = null; IExpression switchExpression = ((ISwitchStatement)s).getSwitchExpression(); IType enumType = switchExpression != null && switchExpression.getType().isEnum() ? switchExpression.getType() : null; List<String> enumValues = enumType != null ? ((IEnumType)enumType).getEnumConstants() : null; for( ICaseClause caseClause : ((ISwitchStatement)s).getCases() ) { if( enumValues != null && caseClause.getExpression().isCompileTimeConstant() ) { try { Object value = caseClause.getExpression().evaluate(); if( value != null ) { enumValues.remove( value.toString() ); } } catch( Exception err ) { // ignore } } List<? extends IStatement> statements = caseClause.getStatements(); AssignedState csr = getAssignedStateForStatements( sym, assignments, statements.toArray( new IStatement[statements.size()] ), localState ); boolean bTerminal = doStatementsTerminate( statements ); if( bTerminal ) { state = state == null ? csr : state.ordinal() > csr.ordinal() ? csr : state; } } List<? extends IStatement> defaultStatements = ((ISwitchStatement)s).getDefaultStatements(); if( defaultStatements != null ) { AssignedState csr = getAssignedStateForStatements( sym, assignments, defaultStatements.toArray( new IStatement[defaultStatements.size()] ), localState ); state = state == null ? csr : state.ordinal() > csr.ordinal() ? csr : state; } else if( enumValues == null || !enumValues.isEmpty() ) { state = state == null || state == AssignedState.Unassigned ? AssignedState.Unassigned : AssignedState.Partially; } retState = state == null ? AssignedState.Unassigned : state; // Process switch expr for identifiers getAssignedState( sym, switchExpression, assignments, localState ); } else if( s instanceof ITryCatchFinallyStatement ) { ITryCatchFinallyStatement tryCatchFinallyStmt = (ITryCatchFinallyStatement)s; AssignedState state = getAssignedState( sym, tryCatchFinallyStmt.getTryStatement(), assignments, localState ); List<? extends ICatchClause> catchStatements = tryCatchFinallyStmt.getCatchStatements(); if( catchStatements != null ) { for( ICatchClause catchClause: catchStatements ) { AssignedState caseState = getAssignedState( sym, catchClause.getCatchStmt(), assignments, localState ); if( caseState != AssignedState.Unassigned && state == AssignedState.Unassigned ) { state = AssignedState.Partially; } } } IStatement finallyStmt = tryCatchFinallyStmt.getFinallyStatement(); if( finallyStmt != null ) { AssignedState finallyState = getAssignedState( sym, finallyStmt, assignments, localState ); state = state.ordinal() > finallyState.ordinal() ? state : finallyState; } retState = state; } else if( s instanceof IUsingStatement ) { retState = getAssignedState( sym, ((IUsingStatement)s).getStatement(), assignments, localState ); // Process expr for identifiers getAssignedState( sym, ((IUsingStatement)s).getExpression(), assignments, localState ); } else if( s instanceof IIdentifierExpression ) { ISymbol symbol = ((IIdentifierExpression)s).getSymbol(); if( !(symbol instanceof IInitializerSymbol) && symbol.getName().equals( sym.getName() ) ) { assignments.add( new AssignmentOrReference( (IIdentifierExpression)s, assignments, localState ) ); } retState = AssignedState.Unassigned; } else if( !(s instanceof IBlockExpression) && !(s instanceof IClassStatement) ) { IParseTree location = s.getLocation(); if( location != null ) { for( IParseTree child: location.getChildren() ) { getAssignedState( sym, child.getParsedElement(), assignments, localState ); } } retState = AssignedState.Unassigned; } return retState; } private boolean doStatementsTerminate( List<? extends IStatement> statements ) { for( IStatement stmt : statements ) { boolean[] bAbsolute = {false}; ITerminalStatement terminalStmt = stmt.getLeastSignificantTerminalStatement( bAbsolute ); if( terminalStmt != null && bAbsolute[0] ) { return true; } } return false; } private IParsedElement getTerminalParent( IParsedElement pe ) { IParsedElement parent = pe.getParent(); if( parent instanceof IStatementList ) { do { pe = parent; parent = parent.getParent(); } while( parent instanceof IStatementList ); parent = pe; // outermost statement-list } //## todo: !!!! this is wrong, we need to find all the case clauses that apply (fall thru) and those are the collective parent if( parent instanceof ICaseClause ) { parent = parent.getParent(); // switch-stmt is parent of terminals in case-clauses } return parent; } static boolean isStatementContainedIn( IParsedElement stmt, IParsedElement container ) { if( container == null ) { return false; } while( container != stmt ) { if( stmt == null ) { return false; } stmt = stmt.getParent(); } return true; } private AssignedState getAssignedStateForStatements( ISymbol sym, ArrayList<AssignmentOrReference> assignments, IStatement[] statements, AssignedState localState ) { AssignedState state = AssignedState.Unassigned; if( statements != null ) { for( IStatement stmt : statements ) { AssignedState csr = getAssignedState( sym, stmt, assignments, localState.ordinal() > state.ordinal() ? localState : state ); assignTerminalStatement( assignments, stmt ); state = csr.ordinal() > state.ordinal() ? csr : state; } } return state; } private void assignTerminalStatement( ArrayList<AssignmentOrReference> assignments, IStatement stmt ) { boolean[] bAbsolute = {false}; ITerminalStatement terminalStmt = stmt.getLeastSignificantTerminalStatement( bAbsolute ); if( terminalStmt != null && bAbsolute[0] ) { for( AssignmentOrReference assignment : assignments ) { if( !assignment.isReference() && isStatementContainedIn( assignment.getStmt(), getTerminalParent( stmt ) ) ) { if( assignment.getTerminal() == null || !isStatementContainedIn( terminalStmt, getTerminalContext( assignment.getTerminal() ) ) ) { assignment.setTerminal( terminalStmt ); } } } } } private IParsedElement getTerminalContext( ITerminalStatement terminal ) { if( terminal instanceof IBreakStatement ) { return findBreakStatementContext( terminal ); } if( terminal instanceof IContinueStatement ) { return findContinueStatementContext( terminal ); } return findEnclosingFunctionStatement( terminal ); } private IParsedElement findEnclosingFunctionStatement( ITerminalStatement terminal ) { return findFirstEnclosing( terminal, new Class[] {IFunctionStatement.class} ); } private IParsedElement findContinueStatementContext( ITerminalStatement terminal ) { return findFirstEnclosing( terminal, new Class[] {ILoopStatement.class} ); } private IParsedElement findBreakStatementContext( ITerminalStatement terminal ) { return findFirstEnclosing( terminal, new Class[] {ILoopStatement.class, ISwitchStatement.class} ); } private IParsedElement findFirstEnclosing( IParsedElement csr, Class[] classes ) { if( csr == null ) { return null; } for( Class cls : classes ) { //noinspection unchecked if( cls.isAssignableFrom( csr.getClass() ) ) { return csr; } } return findFirstEnclosing( csr.getParent(), classes ); } }