package org.overture.codegen.vdm2jml.predgen;
import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
import org.overture.codegen.ir.INode;
import org.overture.codegen.ir.IRGeneratedTag;
import org.overture.codegen.ir.IRInfo;
import org.overture.codegen.ir.SExpIR;
import org.overture.codegen.ir.SStmIR;
import org.overture.codegen.ir.STypeIR;
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.AFormalParamLocalParamIR;
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.patterns.AIdentifierPatternIR;
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.AForLoopStmIR;
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.trans.assistants.TransAssistantIR;
import org.overture.codegen.vdm2jml.JmlAnnotationHelper;
import org.overture.codegen.vdm2jml.JmlGenerator;
import org.overture.codegen.vdm2jml.predgen.info.AbstractTypeInfo;
import org.overture.codegen.vdm2jml.predgen.info.UnknownLeaf;
import org.overture.codegen.vdm2jml.util.IsValChecker;
public class TypePredHandler
{
public static final String RET_VAR_NAME_PREFIX = "ret_";
public static final String MAP_SEQ_NAME_PREFIX = "col_";
private TypePredDecorator decorator;
private TypePredUtil util;
private Logger log = Logger.getLogger(this.getClass().getName());
public TypePredDecorator getDecorator()
{
return decorator;
}
public TypePredHandler(TypePredDecorator decorator)
{
this.decorator = decorator;
this.util = new TypePredUtil(this);
}
public void handleClass(ADefaultClassDeclIR node) throws AnalysisException
{
// We want only to treat fields and methods specified by the user.
// This case helps us avoiding visiting invariant methods
for (AFieldDeclIR f : node.getFields())
{
f.apply(decorator);
}
for (AMethodDeclIR m : node.getMethods())
{
m.apply(decorator);
}
}
public void handleField(AFieldDeclIR node)
{
/**
* Values and record fields will be handled by this handler (not the state component field since its type is a
* record type) Example: val : char | Even = 5;
*/
ADefaultClassDeclIR encClass = decorator.getJmlGen().getUtil().getEnclosingClass(node);
if (encClass == null)
{
return;
}
if (!decorator.getRecInfo().isRecField(node) && node.getFinal())
{
/**
* So at this point it must be a value defined in a module. No need to check if invariant checks are
* enabled.
*/
AbstractTypeInfo typeInfo = util.findTypeInfo(node.getType());
/**
* Since we only allow value definitions to be initialized using literals they must be different from
* 'null'. However, there is a bug in OpenJML that sometimes cause the invariant check for a field to
* trigger before the field is properly initialized. As a work-around for this OpenJML bug, this trick
* guards against this bug, i.e. the static invariant check triggering pre-maturely. Since </br>
* fieldInitialised ==> invariant</br>
* =</br>
* !fieldInitialized || invariant</br>
* =</br>
* !(field != null) || invariant</br>
* =</br>
* field == null || invariant</br>
* </br>
* .. it suffices to simply consider the type as being optional.
*/
typeInfo.setOptional(true);
if (proceed(typeInfo))
{
AIdentifierVarExpIR var = getJmlGen().getJavaGen().getInfo().getExpAssistant().consIdVar(node.getName(), node.getType().clone());
List<String> invStrings = util.consJmlCheck(encClass, JmlGenerator.JML_PUBLIC, JmlGenerator.JML_STATIC_INV_ANNOTATION, false, typeInfo, var);
for (String invStr : invStrings)
{
getAnnotator().appendMetaData(node, getAnnotator().consMetaData(invStr));
}
} else
{
// Since value definitions can only be initialised with literals there is no
// need to guard against null (see JmlGenerator.initialIRConstructed)
// if(varMayBeNull(node.getType()) && rightHandSideMayBeNull(node.getInitial()))
// {
// getAnnotator().appendMetaData(node, util.consValNotNullInvariant(node.getName()));
// }
}
}
/**
* No need to assert type consistency of record fields since this is handled by the record setter
*/
}
private JmlAnnotationHelper getAnnotator()
{
return decorator.getJmlGen().getAnnotator();
}
public void handleBlock(ABlockStmIR node) throws AnalysisException
{
if (node.getLocalDefs().size() > 1)
{
LinkedList<AVarDeclIR> origDecls = new LinkedList<AVarDeclIR>(node.getLocalDefs());
for (int i = origDecls.size() - 1; i >= 0; i--)
{
AVarDeclIR nextDecl = origDecls.get(i);
ABlockStmIR block = new ABlockStmIR();
block.getLocalDefs().add(nextDecl);
node.getStatements().addFirst(block);
}
for (SStmIR stm : node.getStatements())
{
stm.apply(decorator);
}
} else
{
if (!node.getLocalDefs().isEmpty())
{
node.getLocalDefs().getFirst().apply(decorator);
}
for (SStmIR stm : node.getStatements())
{
stm.apply(decorator);
}
}
}
public void handleReturn(AReturnStmIR node) throws AnalysisException
{
/**
* The idea is to extract the return value to variable and return that variable. Then it becomes the
* responsibility of the variable declaration case to assert if the named invariant type is violated.
*/
SExpIR exp = node.getExp();
AMethodDeclIR encMethod = decorator.getJmlGen().getUtil().getEnclosingMethod(node);
if (encMethod == null)
{
return;
}
STypeIR returnType = encMethod.getMethodType().getResult();
AbstractTypeInfo typeInfo = util.findTypeInfo(returnType);
if (!proceed(typeInfo))
{
return;
}
String name = getInfo().getTempVarNameGen().nextVarName(RET_VAR_NAME_PREFIX);
AIdentifierPatternIR id = getInfo().getPatternAssistant().consIdPattern(name);
AIdentifierVarExpIR varExp = getInfo().getExpAssistant().consIdVar(name, returnType.clone());
getTransAssist().replaceNodeWith(exp, varExp);
AVarDeclIR varDecl = getInfo().getDeclAssistant().consLocalVarDecl(returnType.clone(), id, exp.clone());
ABlockStmIR replBlock = new ABlockStmIR();
replBlock.getLocalDefs().add(varDecl);
getTransAssist().replaceNodeWith(node, replBlock);
replBlock.getStatements().add(node);
varDecl.apply(decorator);
}
public void handleMethod(AMethodDeclIR node) throws AnalysisException
{
if (!treatMethod(node))
{
return;
}
// Upon entering the method, assert that the parameters are valid wrt. their named invariant types.
ABlockStmIR replBody = new ABlockStmIR();
for (AFormalParamLocalParamIR param : node.getFormalParams())
{
AbstractTypeInfo typeInfo = util.findTypeInfo(param.getType());
if (proceed(typeInfo))
{
ADefaultClassDeclIR encClass = decorator.getJmlGen().getUtil().getEnclosingClass(node);
if (encClass == null)
{
continue;
}
String varNameStr = decorator.getJmlGen().getUtil().getName(param.getPattern());
if (varNameStr == null)
{
continue;
}
SVarExpIR var = getInfo().getExpAssistant().consIdVar(varNameStr, param.getType().clone());
/**
* Upon entering a record setter it is necessary to check if invariants checks are enabled before
* checking the parameter
*/
List<AMetaStmIR> as = util.consAssertStm(typeInfo, encClass, var, node, decorator.getRecInfo());
for (AMetaStmIR a : as)
{
replBody.getStatements().add(a);
}
}
}
SStmIR body = node.getBody();
getTransAssist().replaceNodeWith(body, replBody);
replBody.getStatements().add(body);
body.apply(decorator);
}
public List<AMetaStmIR> handleMapSeq(AMapSeqUpdateStmIR node)
{
// TODO: Consider this for the atomic statement
SExpIR col = node.getCol();
if (!(col instanceof SVarExpIR))
{
log.error("Expected collection to be a variable expression at this point. Got: "
+ col);
return null;
}
SVarExpIR var = (SVarExpIR) col;
if (varMayBeNull(var.getType()))
{
// The best we can do is to assert that the map/seq subject to modification is
// not null although we eventually get the null pointer exception, e.g.
//
// //@ azzert m != null
// Utils.mapSeqUpdate(m,1L,1L);
AMetaStmIR assertNotNull = util.consVarNotNullAssert(var.getName());
ABlockStmIR replStm = new ABlockStmIR();
getTransAssist().replaceNodeWith(node, replStm);
replStm.getStatements().add(assertNotNull);
replStm.getStatements().add(node);
}
AbstractTypeInfo typeInfo = util.findTypeInfo(var.getType());
if (proceed(typeInfo))
{
ADefaultClassDeclIR enclosingClass = decorator.getJmlGen().getUtil().getEnclosingClass(node);
if (enclosingClass == null)
{
return null;
}
if (col instanceof SVarExpIR)
{
/**
* Updates to fields in record setters need to check if invariants checks are enabled
*/
return util.consAssertStm(typeInfo, enclosingClass, var, node, decorator.getRecInfo());
}
}
return null;
}
public List<AMetaStmIR> handleVarDecl(AVarDeclIR node)
{
// Examples:
// let x : Even = 1 in ...
// (dcl y : Even | nat := 2; ...)
if (getInfo().getExpAssistant().isUndefined(node.getExp()))
{
return null;
}
AbstractTypeInfo typeInfo = util.findTypeInfo(node.getType());
if (proceed(typeInfo))
{
String name = decorator.getJmlGen().getUtil().getName(node.getPattern());
if (name == null)
{
return null;
}
ADefaultClassDeclIR enclosingClass = node.getAncestor(ADefaultClassDeclIR.class);
if (enclosingClass == null)
{
return null;
}
AIdentifierVarExpIR var = getJmlGen().getJavaGen().getInfo().getExpAssistant().consIdVar(name, node.getType().clone());
/**
* We do not really need to check if invariant checks are enabled because local variable declarations are
* not expected to be found inside record accessors
*/
return util.consAssertStm(typeInfo, enclosingClass, var, node, decorator.getRecInfo());
}
return null;
}
public List<AMetaStmIR> handleCallObj(ACallObjectExpStmIR node)
{
/**
* Handling of setter calls to masked records. This will happen for cases like T = R ... ; R :: x : int;
*/
SExpIR recObj = node.getObj();
if (recObj instanceof ACastUnaryExpIR)
{
recObj = ((ACastUnaryExpIR) recObj).getExp();
}
if (recObj instanceof SVarExpIR)
{
SVarExpIR recObjVar = (SVarExpIR) recObj;
if (varMayBeNull(recObj.getType()))
{
// The best we can do is to assert that the record subject to modification is
// not null although we eventually get the null pointer exception, e.g.
//
// //@ azzert rec != null
// rec.set_x(5);
AMetaStmIR assertNotNull = util.consVarNotNullAssert(recObjVar.getName());
ABlockStmIR replStm = new ABlockStmIR();
getTransAssist().replaceNodeWith(node, replStm);
replStm.getStatements().add(assertNotNull);
replStm.getStatements().add(node);
return null;
}
AbstractTypeInfo typeInfo = util.findTypeInfo(recObj.getType());
if (proceed(typeInfo))
{
ADefaultClassDeclIR encClass = decorator.getJmlGen().getUtil().getEnclosingClass(node);
if (encClass == null)
{
return null;
}
/**
* Since setter calls can occur inside a record in the context of an atomic statement blocks we need to
* check if invariant checks are enabled
*/
return util.consAssertStm(typeInfo, encClass, recObjVar, node, decorator.getRecInfo());
}
} else
{
log.error("Found unexpected record object of call expression statement inside atomic statement block. Target found: "
+ recObj);
}
return null;
}
public void handleAssign(AAssignToExpStmIR node)
{
// <target> := atomic_tmp;
/*
* Note that assignment to targets that are of type AFieldNumberExpIR, i.e. tuples (e.g. tup.#1 := 5) is not
* allowed in VDM.
*/
SExpIR target = node.getTarget();
if (!(target instanceof SVarExpIR))
{
log.error("By now all assignments should have simple variable expression as target. Got: "
+ target);
return;
}
SVarExpIR var = (SVarExpIR) target;
AbstractTypeInfo typeInfo = util.findTypeInfo(node.getTarget().getType());
if (proceed(typeInfo))
{
ADefaultClassDeclIR encClass = decorator.getJmlGen().getUtil().getEnclosingClass(node);
if (encClass == null)
{
return;
}
/**
* Since assignments can occur inside record setters in the context of an atomic statement block we need to
* check if invariant checks are enabled
*/
List<AMetaStmIR> asserts = util.consAssertStm(typeInfo, encClass, var, node, decorator.getRecInfo());
for (AMetaStmIR a : asserts)
{
addAssert(node, a);
}
}
}
public ABlockStmIR getEncBlockStm(AVarDeclIR varDecl)
{
INode parent = varDecl.parent();
if (parent instanceof ABlockStmIR)
{
ABlockStmIR parentBlock = (ABlockStmIR) varDecl.parent();
if (!parentBlock.getLocalDefs().contains(varDecl))
{
log.error("Expected local variable declaration to be "
+ "one of the local variable declarations of "
+ "the parent statement block");
return null;
}
if (parentBlock.getLocalDefs().size() > 1)
{
// The block statement case method should have ensured that the size == 1
log.error("Expected only a single local declaration in "
+ "the parent block at this point");
return null;
}
return parentBlock;
} else if (parent instanceof AForLoopStmIR)
{
// Do nothing
return null;
} else
{
log.error("Expected parent of local variable "
+ "declaration to be a statement block. Got: "
+ varDecl.parent());
return null;
}
}
private TransAssistantIR getTransAssist()
{
return decorator.getJmlGen().getJavaGen().getTransAssistant();
}
private IRInfo getInfo()
{
return decorator.getJmlGen().getJavaGen().getInfo();
}
public JmlGenerator getJmlGen()
{
return decorator.getJmlGen();
}
public List<AMetaStmIR> consAsserts(AIdentifierVarExpIR var)
{
AbstractTypeInfo typeInfo = util.findTypeInfo(var.getType());
if (!proceed(typeInfo))
{
return null;
}
ADefaultClassDeclIR encClass = decorator.getStateDesInfo().getEnclosingClass(var);
if (encClass == null)
{
return null;
}
/**
* Normalisation of state designators will never occur inside record classes so really there is no need to check
* if invariant checks are enabled
*/
return util.consAssertStm(typeInfo, encClass, var, var, decorator.getRecInfo());
}
public boolean rightHandSideMayBeNull(SExpIR exp)
{
IsValChecker checker = new IsValChecker();
try
{
return !exp.apply(checker);
} catch (AnalysisException e)
{
e.printStackTrace();
return false;
}
}
private boolean varMayBeNull(STypeIR type)
{
return treatMethod(type)
&& !getInfo().getTypeAssistant().allowsNull(type);
}
private boolean treatMethod(INode node)
{
if (inModuleToStringMethod(node))
{
return false;
}
// Some of the record methods inherited from object use native java type that can never be null
if (decorator.getRecInfo().inRec(node))
{
if (!(decorator.getRecInfo().inAccessor(node)
|| decorator.getRecInfo().inRecConstructor(node)))
{
return false;
}
}
return true;
}
private boolean inModuleToStringMethod(INode type)
{
AMethodDeclIR m = type.getAncestor(AMethodDeclIR.class);
if (m == null)
{
return false;
}
if (m.getTag() instanceof IRGeneratedTag
&& m.getName().equals("toString"))
{
return true;
}
return false;
}
private void addAssert(AAssignToExpStmIR node, AMetaStmIR assertStm)
{
ABlockStmIR replStm = new ABlockStmIR();
getJmlGen().getJavaGen().getTransAssistant().replaceNodeWith(node, replStm);
replStm.getStatements().add(node);
replStm.getStatements().add(assertStm);
}
public TypePredUtil getTypePredUtil()
{
return util;
}
private boolean proceed(AbstractTypeInfo typeInfo)
{
return !(typeInfo instanceof UnknownLeaf);
}
}