package org.overture.codegen.vdm2jml.predgen; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.apache.log4j.Logger; import org.overture.codegen.ir.SStmIR; import org.overture.codegen.ir.analysis.AnalysisException; import org.overture.codegen.ir.declarations.ADefaultClassDeclIR; import org.overture.codegen.ir.declarations.AFieldDeclIR; import org.overture.codegen.ir.declarations.AMethodDeclIR; import org.overture.codegen.ir.declarations.AVarDeclIR; import org.overture.codegen.ir.expressions.ACastUnaryExpIR; import org.overture.codegen.ir.expressions.AIdentifierVarExpIR; import org.overture.codegen.ir.expressions.SVarExpIR; import org.overture.codegen.ir.statements.AAssignToExpStmIR; import org.overture.codegen.ir.statements.ABlockStmIR; import org.overture.codegen.ir.statements.ACallObjectExpStmIR; import org.overture.codegen.ir.statements.AMapSeqUpdateStmIR; import org.overture.codegen.ir.statements.AMetaStmIR; import org.overture.codegen.ir.statements.AReturnStmIR; import org.overture.codegen.traces.TraceMethodTag; import org.overture.codegen.vdm2jml.JmlGenerator; import org.overture.codegen.vdm2jml.data.RecClassInfo; import org.overture.codegen.vdm2jml.data.StateDesInfo; import org.overture.codegen.vdm2jml.trans.RecAccessorTrans; import org.overture.codegen.vdm2jml.trans.TargetNormaliserTrans; /** * This class is responsible for adding additional checks, like assertions, to the IR to preserve the semantics of the * contract-based notations of VDM-SL when they are translated to JML annotated Java. * * @see RecAccessorTrans * @see TargetNormaliserTrans */ public class TypePredDecorator extends AtomicAssertTrans { private RecModHandler recHandler; private TypePredHandler namedTypeHandler; private StateDesInfo stateDesInfo; private RecClassInfo recInfo; private boolean buildRecChecks = false; private Logger log = Logger.getLogger(this.getClass().getName()); public TypePredDecorator(JmlGenerator jmlGen, StateDesInfo stateDesInfo, RecClassInfo recInfo) { super(jmlGen); this.recHandler = new RecModHandler(this); this.namedTypeHandler = new TypePredHandler(this); this.stateDesInfo = stateDesInfo; this.recInfo = recInfo; } @Override public void caseACallObjectExpStmIR(ACallObjectExpStmIR node) throws AnalysisException { if (node.getObj() instanceof SVarExpIR) { SVarExpIR obj = (SVarExpIR) node.getObj(); handleStateUpdate(node, obj); } else if (node.getObj() instanceof ACastUnaryExpIR) { ACastUnaryExpIR cast = (ACastUnaryExpIR) node.getObj(); if (cast.getExp() instanceof SVarExpIR) { SVarExpIR obj = (SVarExpIR) cast.getExp(); handleStateUpdate(node, obj); } else { log.error("Expected subject of cast expression to be a variable expression at this point. Got: " + cast.getExp()); } } else { log.error("Expected object of call object statement " + " to be a variable or cast expression by now. Got: " + node.getObj()); } } private void handleStateUpdate(ACallObjectExpStmIR node, SVarExpIR obj) { handleStateUpdate(node, obj, stateDesInfo.getStateDesVars(node), recHandler.handleCallObj(node), namedTypeHandler.handleCallObj(node)); } @Override public void caseAFieldDeclIR(AFieldDeclIR node) throws AnalysisException { namedTypeHandler.handleField(node); } @Override public void caseABlockStmIR(ABlockStmIR node) throws AnalysisException { namedTypeHandler.handleBlock(node); } @Override public void caseAVarDeclIR(AVarDeclIR node) throws AnalysisException { /** * Variable declarations occurring inside an atomic statement do not need handling. The reason for this is that * the call statement and the map/seq update cases currently take care of generating the assertions for variable * expressions used to represent state designators. TODO: Make this case handle state designators */ if (inAtomic()) { return; } if (stateDesInfo.isStateDesDecl(node)) { return; } /** * Since the target of map/seq updates (e.g. Utils.mapsSeqUpdate(stateDes_3, 4, 'a')) and call object statements * (e.g. stateDes_3.set_x("a")) (i.e. assignments in the VDM-SL model) are split into variables named stateDes_ * <n> we can also expect local variable declarations in atomic statement blocks */ List<AMetaStmIR> as = namedTypeHandler.handleVarDecl(node); if (as == null || as.isEmpty()) { return; } ABlockStmIR encBlock = namedTypeHandler.getEncBlockStm(node); if (encBlock == null) { return; } /** * The variable declaration is a local declaration of the statement block. Therefore, making the assertion the * first statement in the statement block makes the assertion appear immediately right after the variable * declaration. */ if (inAtomic()) { for (AMetaStmIR a : as) { addPostAtomicCheck(a); } } else { for (int i = as.size() - 1; i >= 0; i--) { encBlock.getStatements().addFirst(as.get(i)); } } } @Override public void caseAAssignToExpStmIR(AAssignToExpStmIR node) throws AnalysisException { /** * Record modifications are now all on the form E.g. St = <recvalue>, i.e. node.getTarget() instanceof SVarExpIR * && node.getTarget().getType() instanceof ARecordTypeIR. Invariant violations will therefore be detected when * the record value is constructed or it appears in the temporary variable section if the assignment occurs in * the context of an atomic statement block. Therefore, no record invariant needs to be asserted. Note that more * complicated record modifications (e.g. rec1.rec2.f := 5) appear as nodes of type caseACallObjectExpStmIR */ if (!inAtomic()) { namedTypeHandler.handleAssign(node); } } @Override public void caseAMapSeqUpdateStmIR(AMapSeqUpdateStmIR node) throws AnalysisException { if (node.getCol() instanceof SVarExpIR) { SVarExpIR col = (SVarExpIR) node.getCol(); handleStateUpdate(node, col, stateDesInfo.getStateDesVars(node), null, namedTypeHandler.handleMapSeq(node)); } else { log.error("Expected collection of map/sequence" + " update to be a variable expression by now. Got: " + node.getCol()); } } @Override public void caseAMethodDeclIR(AMethodDeclIR node) throws AnalysisException { if (node.getTag() instanceof TraceMethodTag) { return; } namedTypeHandler.handleMethod(node); } @Override public void caseAReturnStmIR(AReturnStmIR node) throws AnalysisException { namedTypeHandler.handleReturn(node); } @Override public void caseADefaultClassDeclIR(ADefaultClassDeclIR node) throws AnalysisException { namedTypeHandler.handleClass(node); } private void handleStateUpdate(SStmIR node, SVarExpIR target, List<AIdentifierVarExpIR> objVars, AMetaStmIR recAssert, List<AMetaStmIR> namedTypeInvAssert) { if (recAssert == null && namedTypeInvAssert == null && objVars == null) { return; } if (!inAtomic()) { // NOT inside atomic statement block ABlockStmIR replBlock = new ABlockStmIR(); jmlGen.getJavaGen().getTransAssistant().replaceNodeWith(node, replBlock); replBlock.getStatements().add(node); addSubjectAsserts(recAssert, namedTypeInvAssert, replBlock); addStateDesAsserts(target, objVars, replBlock); } else { // We'll store the checks and let the atomic statement case handle the assert insertion addSubjectAssertAtomic(recAssert, namedTypeInvAssert); addStateDesAssertsAtomic(target, objVars); } } private void addSubjectAssertAtomic(AMetaStmIR recAssert, List<AMetaStmIR> namedTypeInvAssert) { for (AMetaStmIR a : consSubjectAsserts(recAssert, namedTypeInvAssert)) { addPostAtomicCheck(a); } } private void addSubjectAsserts(AMetaStmIR recAssert, List<AMetaStmIR> namedTypeInvAssert, ABlockStmIR replBlock) { for (AMetaStmIR a : consSubjectAsserts(recAssert, namedTypeInvAssert)) { replBlock.getStatements().add(a); } } private List<AMetaStmIR> consSubjectAsserts(AMetaStmIR recAssert, List<AMetaStmIR> namedTypeInvAsserts) { List<AMetaStmIR> allAsserts = new LinkedList<AMetaStmIR>(); add(allAsserts, recAssert); if (namedTypeInvAsserts != null) { for (AMetaStmIR a : namedTypeInvAsserts) { add(allAsserts, a); } } return allAsserts; } private void addStateDesAssertsAtomic(SVarExpIR target, List<AIdentifierVarExpIR> objVars) { for (AMetaStmIR a : consObjVarAsserts(target, objVars)) { addPostAtomicCheck(a); } } private void addStateDesAsserts(SVarExpIR target, List<AIdentifierVarExpIR> objVars, ABlockStmIR replBlock) { for (AMetaStmIR a : consObjVarAsserts(target, objVars)) { add(replBlock, a); } } private List<AMetaStmIR> consObjVarAsserts(SVarExpIR target, List<AIdentifierVarExpIR> objVars) { List<AMetaStmIR> objVarAsserts = new LinkedList<AMetaStmIR>(); if (objVars != null) { // All of them except the last for (int i = 0; i < objVars.size() - 1; i++) { addAsserts(objVarAsserts, objVars.get(i)); } if (!objVarAsserts.isEmpty()) { AIdentifierVarExpIR last = objVars.get(objVars.size() - 1); if (!target.getName().equals(last.getName())) { addAsserts(objVarAsserts, last); } } } Collections.reverse(objVarAsserts); return objVarAsserts; } private void addAsserts(List<AMetaStmIR> objVarAsserts, AIdentifierVarExpIR var) { buildRecChecks = true; List<AMetaStmIR> asserts = namedTypeHandler.consAsserts(var); buildRecChecks = false; if (asserts != null) { for (AMetaStmIR a : asserts) { add(objVarAsserts, a); } } } private void add(List<AMetaStmIR> asserts, AMetaStmIR as) { if (as != null) { asserts.add(as); } } private void add(ABlockStmIR block, AMetaStmIR as) { if (as != null) { block.getStatements().add(as); } } public RecClassInfo getRecInfo() { return recInfo; } public StateDesInfo getStateDesInfo() { return stateDesInfo; } public boolean buildRecValidChecks() { return buildRecChecks; } public TypePredUtil getTypePredUtil() { return this.namedTypeHandler.getTypePredUtil(); } }