/* * Copyright (C) 2013-2015 Daniel Dietsch (dietsch@informatik.uni-freiburg.de) * Copyright (C) 2015 University of Freiburg * Copyright (C) 2015 Vincent Langenfeld (langenfv@informatik.uni-freiburg.de) * * This file is part of the ULTIMATE LTL2Aut plug-in. * * The ULTIMATE LTL2Aut plug-in is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE LTL2Aut plug-in is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE LTL2Aut plug-in. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE LTL2Aut plug-in, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE LTL2Aut plug-in grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.ltl2aut; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; import de.uni_freiburg.informatik.ultimate.automata.nestedword.INestedWordAutomaton; import de.uni_freiburg.informatik.ultimate.boogie.DeclarationInformation; import de.uni_freiburg.informatik.ultimate.boogie.ExpressionFactory; import de.uni_freiburg.informatik.ultimate.boogie.annotation.LTLPropertyCheck; import de.uni_freiburg.informatik.ultimate.boogie.annotation.LTLPropertyCheck.CheckableExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.ArrayAccessExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.ArrayStoreExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.AssignmentStatement; import de.uni_freiburg.informatik.ultimate.boogie.ast.BinaryExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.BitVectorAccessExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.BitvecLiteral; import de.uni_freiburg.informatik.ultimate.boogie.ast.BooleanLiteral; import de.uni_freiburg.informatik.ultimate.boogie.ast.Expression; import de.uni_freiburg.informatik.ultimate.boogie.ast.GeneratedBoogieAstTransformer; import de.uni_freiburg.informatik.ultimate.boogie.ast.IdentifierExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.IfThenElseExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.IntegerLiteral; import de.uni_freiburg.informatik.ultimate.boogie.ast.Procedure; import de.uni_freiburg.informatik.ultimate.boogie.ast.RealLiteral; import de.uni_freiburg.informatik.ultimate.boogie.ast.StringLiteral; import de.uni_freiburg.informatik.ultimate.boogie.ast.StructAccessExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.UnaryExpression; import de.uni_freiburg.informatik.ultimate.boogie.ast.Unit; import de.uni_freiburg.informatik.ultimate.boogie.parser.BoogieSymbolFactory; import de.uni_freiburg.informatik.ultimate.boogie.preprocessor.PreprocessorAnnotation; import de.uni_freiburg.informatik.ultimate.boogie.symboltable.BoogieSymbolTable; import de.uni_freiburg.informatik.ultimate.boogie.type.BoogieType; import de.uni_freiburg.informatik.ultimate.boogie.typechecker.TypeCheckException; import de.uni_freiburg.informatik.ultimate.core.model.models.IElement; import de.uni_freiburg.informatik.ultimate.core.model.models.IBoogieType; import de.uni_freiburg.informatik.ultimate.core.model.models.ModelType; import de.uni_freiburg.informatik.ultimate.core.model.observers.IUnmanagedObserver; import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger; import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider; import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.AstNode; import de.uni_freiburg.informatik.ultimate.ltl2aut.never2nwa.NWAContainer; import de.uni_freiburg.informatik.ultimate.ltl2aut.never2nwa.Never2Automaton; import de.uni_freiburg.informatik.ultimate.ltl2aut.preferences.PreferenceInitializer; import de.uni_freiburg.informatik.ultimate.plugins.generator.rcfgbuilder.cfg.CodeBlock; import de.uni_freiburg.informatik.ultimate.plugins.generator.rcfgbuilder.cfg.CodeBlockFactory; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair; /** * This class reads a definition of a property in LTL and returns the AST of the description of the LTL formula as a * Buchi automaton. * * @author Langenfeld * @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de) */ public class LTL2autObserver implements IUnmanagedObserver { private static final String LTL_MARKER = "#LTLProperty:"; // alphabet without X, U, F, G private static final String ALPHABET = "ABCDEHIJKLMNOPQRSTVWYZ"; private final IUltimateServiceProvider mServices; private final ILogger mLogger; private String mInputFile; private NWAContainer mNWAContainer; private LTLPropertyCheck mCheck; private BoogieSymbolTable mSymbolTable; public LTL2autObserver(final IUltimateServiceProvider services) { mServices = services; mLogger = mServices.getLoggingService().getLogger(Activator.PLUGIN_ID); } @Override public void init(final ModelType modelType, final int currentModelIndex, final int numberOfModels) { mNWAContainer = null; mInputFile = null; mSymbolTable = null; mCheck = null; } @Override public void finish() throws Throwable { // if there is a check, there is already boogie code // the ltl string is in our ACSL format, we should convert it to // ltl2aut format see http://www.lsv.ens-cachan.fr/~gastin/ltl2ba/ if (mCheck == null) { // there is no check, so we either need to read the property from the boogie file or from the settings // both formats are in ltl2aut format and we need to create a check with boogie-code final String[] specification = getLTLPropertyString(); if (specification.length > 1) { throw new UnsupportedOperationException( "We currently support only one LTL property at a time, but found " + specification.length); } mCheck = createCheckFromPropertyString(specification[0], mSymbolTable); } final Map irs = mCheck.getCheckableAtomicPropositions(); final String ltl2baProperty = mCheck.getLTL2BALTLProperty(); final AstNode node = getNeverClaim(ltl2baProperty); final CodeBlockFactory cbf = CodeBlockFactory.getFactory(mServices.getStorage()); final INestedWordAutomaton nwa = createNWAFromNeverClaim(node, irs, mSymbolTable, cbf); mLogger.info("LTL Property is: " + mCheck.getUltimateLTLProperty()); mNWAContainer = new NWAContainer(nwa); mCheck.annotate(mNWAContainer); } private LTLPropertyCheck createCheckFromPropertyString(final String ltlProperty, final BoogieSymbolTable symbolTable) throws Throwable { final Map apIrs = parseAtomicPropositions(ltlProperty, symbolTable); if (apIrs.isEmpty()) { throw new IllegalArgumentException("No atomic propositions in " + ltlProperty); } // we need to rename the AP(...) expressions to symbols s.t. ltl2ba does not get confused final Map irs = new LinkedHashMap<>(); String newLtlProperty = ltlProperty; int i = 0; for (final Entry entry : apIrs.entrySet()) { final String freshSymbol = getAPSymbol(i); ++i; newLtlProperty = newLtlProperty.replaceAll(Pattern.quote(entry.getKey()), freshSymbol); irs.put(freshSymbol, entry.getValue()); } return new LTLPropertyCheck(newLtlProperty, irs, null); } // Parse atomic propositions while respecting proper parenthesis nesting. private Map parseAtomicPropositions(final String ltlProperty, final BoogieSymbolTable symbolTable) { final Map apIrs = new LinkedHashMap<>(); int pos = ltlProperty.indexOf("AP("); while (pos >= 0) { pos += "AP(".length(); final int start = pos; int numParens = 1; while (numParens > 0) { int firstOpen = ltlProperty.indexOf("(", pos); int firstClose = ltlProperty.indexOf(")", pos); assert firstOpen >= 0 || firstClose >= 0 : "Unmatched opening parenthesis"; if (firstOpen >= 0 && firstOpen < firstClose) { numParens++; pos = firstOpen + 1; } else /* if (firstClose >= 0 ) */ { numParens--; pos = firstClose + 1; } } final int end = pos - 1; final String code = ltlProperty.substring(start, end); final CheckableExpression expr = createCheckableExpression(code, symbolTable); apIrs.put("AP(" + code + ")", expr); pos = ltlProperty.indexOf("AP(", pos); } return apIrs; } private CheckableExpression createCheckableExpression(final String expr, final BoogieSymbolTable symbolTable) { final String niceProgram = "procedure main() { #thevar := %s ;}"; final String localProg = String.format(niceProgram, expr.trim()); final BoogieSymbolFactory symFac = new BoogieSymbolFactory(); final de.uni_freiburg.informatik.ultimate.boogie.parser.Lexer lexer = new de.uni_freiburg.informatik.ultimate.boogie.parser.Lexer(IOUtils.toInputStream(localProg)); lexer.setSymbolFactory(symFac); final de.uni_freiburg.informatik.ultimate.boogie.parser.Parser p = new de.uni_freiburg.informatik.ultimate.boogie.parser.Parser(lexer, symFac); try { final Unit x = (Unit) p.parse().value; final Procedure proc = (Procedure) x.getDeclarations()[0]; final AssignmentStatement stmt = (AssignmentStatement) proc.getBody().getBlock()[0]; final Expression bExpr = stmt.getRhs()[0]; // The bExpr has been parsed, but has not been type checked, yet. Type check it now. // We do so by inductively constructing a fully typed copy of the original expression // using the type information from the symbol table of the underlying Boogie program. final Expression apExpr = bExpr.accept(new TypeAdder(symbolTable)); return new CheckableExpression(apExpr, Collections.emptyList()); } catch (final Exception e) { mLogger.error(String.format("Exception while parsing the atomic proposition \"%s\": %s", expr, e)); throw new RuntimeException(e); } } public static String getAPSymbol(final int i) { if (i < ALPHABET.length()) { return String.valueOf(ALPHABET.charAt(i)); } String rtr = "A"; int idx = i; while (idx > ALPHABET.length()) { idx = idx - ALPHABET.length(); rtr += String.valueOf(ALPHABET.charAt(idx % ALPHABET.length())); } return rtr; } private String[] getLTLPropertyString() throws IOException { final String[] properties; if (mServices.getPreferenceProvider(Activator.PLUGIN_ID) .getBoolean(PreferenceInitializer.LABEL_PROPERTYFROMFILE) && mInputFile != null) { properties = extractPropertyFromInputFile(); if (properties.length > 0) { return properties; } throw new IllegalArgumentException("File " + mInputFile + " did not contain an LTL property"); } mLogger.info("Using LTL properties from settings"); properties = mServices.getPreferenceProvider(Activator.PLUGIN_ID) .getString(PreferenceInitializer.LABEL_PPROPERTY).split("\n"); if (properties.length > 0 && !properties[0].isEmpty()) { return properties; } throw new IllegalArgumentException("Settings did not contain an LTL property"); } private String[] extractPropertyFromInputFile() throws IOException { BufferedReader br; String line = null; final List properties = new ArrayList<>(); try { br = new BufferedReader(new FileReader(mInputFile)); while ((line = br.readLine()) != null) { if (line.contains(LTL_MARKER)) { properties.add(line.replaceFirst("//", "").replaceAll(LTL_MARKER, "").trim()); } } br.close(); } catch (final IOException e) { mLogger.error("Error while reading " + mInputFile + ": " + e); throw e; } return properties.toArray(new String[properties.size()]); } private AstNode getNeverClaim(final String property) throws Throwable { try { mLogger.debug("Parsing LTL property..."); return new LTLXBAExecutor(mServices).ltl2Ast(property); } catch (final Throwable e) { mLogger.fatal(String.format("Exception during LTL->BA execution: %s", e)); throw e; } } private INestedWordAutomaton createNWAFromNeverClaim(final AstNode neverclaim, final Map irs, final BoogieSymbolTable symbolTable, final CodeBlockFactory cbf) throws Exception { if (neverclaim == null) { throw new IllegalArgumentException("There is no never claim"); } if (irs == null) { throw new IllegalArgumentException("There are no CheckableExpressions"); } if (symbolTable == null) { throw new IllegalArgumentException("The BoogieSymbolTable is missing"); } if (cbf == null) { throw new IllegalArgumentException( "The CodeBlockFactory is missing. Did you run the RCFGBuilder before this plugin?"); } INestedWordAutomaton nwa; mLogger.debug("Transforming NeverClaim to NestedWordAutomaton..."); try { // Build NWA from LTL formula in NeverClaim representation nwa = new Never2Automaton(neverclaim, symbolTable, irs, mLogger, mServices, cbf).getAutomaton(); if (nwa == null) { throw new IllegalArgumentException("nwa is null"); } } catch (final Exception e) { mLogger.fatal("LTL2Aut encountered an error while transforming the NeverClaim to a NestedWordAutomaton"); throw e; } return nwa; } @Override public boolean performedChanges() { return false; } @Override public boolean process(final IElement root) throws Throwable { if (root instanceof Unit) { mInputFile = ((Unit) root).getLocation().getFileName(); mCheck = LTLPropertyCheck.getAnnotation(root); mSymbolTable = PreprocessorAnnotation.getAnnotation(root).getSymbolTable(); return false; } return true; } public NWAContainer getNWAContainer() { return mNWAContainer; } /* * Implements a post-order traversal of an expression that constructs a fully typed expression using * an existing symbol table. */ private static final class TypeAdder extends GeneratedBoogieAstTransformer { private Pair mTypeError; private BoogieSymbolTable mSymbolTable; public String toString() { return "Transformer that adds type information to atomic propositions"; } public Pair getTypeError() { return mTypeError; } public TypeAdder(BoogieSymbolTable symbolTable) { mSymbolTable = symbolTable; } @Override public Expression transform(final IntegerLiteral node) { return ExpressionFactory.createIntegerLiteral(node.getLoc(), node.getValue()); } @Override public Expression transform(final StringLiteral node) { return ExpressionFactory.createStringLiteral(node.getLoc(), node.getValue()); } @Override public Expression transform(final BooleanLiteral node) { return ExpressionFactory.createBooleanLiteral(node.getLoc(), node.getValue()); } @Override public Expression transform(final RealLiteral node) { return ExpressionFactory.createRealLiteral(node.getLoc(), node.getValue()); } @Override public Expression transform(final BitvecLiteral node) { return ExpressionFactory.createBitvecLiteral(node.getLoc(), node.getValue(), node.getLength()); } @Override public Expression transform(final IdentifierExpression node) { // Look up the Boogie type of the identifier in the underlying (real) Boogie program. final IBoogieType boogieType; boogieType = mSymbolTable.getTypeForVariableSymbol(node.getIdentifier(), DeclarationInformation.StorageClass.GLOBAL, "GLOBAL"); return new IdentifierExpression(node.getLoc(), boogieType, node.getIdentifier(), DeclarationInformation.DECLARATIONINFO_GLOBAL); } @Override public Expression transform(final BinaryExpression node) { final Expression lExpr = node.getLeft().accept(this); final Expression rExpr = node.getRight().accept(this); try { return ExpressionFactory.newBinaryExpression(node.getLoc(), node.getOperator(), lExpr, rExpr); } catch (final TypeCheckException ex) { setTypeError(ex.getMessage(), node); return new IdentifierExpression(node.getLoc(), BoogieType.TYPE_ERROR, "Error", DeclarationInformation.DECLARATIONINFO_GLOBAL); } } @Override public Expression transform(final IfThenElseExpression node) { final Expression condExpr = node.getCondition().accept(this); final Expression thenExpr = node.getThenPart().accept(this); final Expression elseExpr = node.getElsePart().accept(this); try { return ExpressionFactory.constructIfThenElseExpression(node.getLoc(), condExpr, thenExpr, elseExpr); } catch (final TypeCheckException ex) { setTypeError(ex.getMessage(), node); return new IdentifierExpression(node.getLoc(), BoogieType.TYPE_ERROR, "Error", DeclarationInformation.DECLARATIONINFO_GLOBAL); } } @Override public Expression transform(final UnaryExpression node) { final Expression argExpr = node.getExpr().accept(this); try { return ExpressionFactory.constructUnaryExpression(node.getLoc(), node.getOperator(), argExpr); } catch (final TypeCheckException ex) { setTypeError(ex.getMessage(), node); return new IdentifierExpression(node.getLoc(), BoogieType.TYPE_ERROR, "Error", DeclarationInformation.DECLARATIONINFO_GLOBAL); } } @Override public Expression transform(final BitVectorAccessExpression node) { final Expression bvExpr = node.getBitvec().accept(this); try { return ExpressionFactory.constructBitvectorAccessExpression(node.getLoc(), bvExpr, node.getEnd(), node.getStart()); } catch (final TypeCheckException ex) { setTypeError(ex.getMessage(), node); return new IdentifierExpression(node.getLoc(), BoogieType.TYPE_ERROR, "Error", DeclarationInformation.DECLARATIONINFO_GLOBAL); } } @Override public Expression transform(final ArrayAccessExpression node) { final Expression arrayExpr = node.getArray().accept(this); final Expression[] untypedIdxExprs = node.getIndices(); final Expression[] typedIdxExprs = new Expression[untypedIdxExprs.length]; for (int i = 0; i < untypedIdxExprs.length; ++i) { typedIdxExprs[i] = untypedIdxExprs[i].accept(this); } try { return ExpressionFactory.constructNestedArrayAccessExpression(node.getLoc(), arrayExpr, typedIdxExprs); } catch (final TypeCheckException ex) { setTypeError(ex.getMessage(), node); return new IdentifierExpression(node.getLoc(), BoogieType.TYPE_ERROR, "Error", DeclarationInformation.DECLARATIONINFO_GLOBAL); } } @Override public Expression transform(final ArrayStoreExpression node) { final Expression arrayExpr = node.getArray().accept(this); final Expression[] untypedIdxExprs = node.getIndices(); final Expression valExpr = node.getValue().accept(this); final Expression[] typedIdxExprs = new Expression[untypedIdxExprs.length]; for (int i = 0; i < untypedIdxExprs.length; ++i) { typedIdxExprs[i] = untypedIdxExprs[i].accept(this); } try { return ExpressionFactory.constructArrayStoreExpression(node.getLoc(), arrayExpr, typedIdxExprs, valExpr); } catch (final TypeCheckException ex) { setTypeError(ex.getMessage(), node); return new IdentifierExpression(node.getLoc(), BoogieType.TYPE_ERROR, "Error", DeclarationInformation.DECLARATIONINFO_GLOBAL); } } @Override public Expression transform(final StructAccessExpression node) { final Expression structExpr = node.getStruct().accept(this); try { return ExpressionFactory.constructStructAccessExpression(node.getLoc(), structExpr, node.getField()); } catch (final TypeCheckException ex) { setTypeError(ex.getMessage(), node); return new IdentifierExpression(node.getLoc(), BoogieType.TYPE_ERROR, "Error", DeclarationInformation.DECLARATIONINFO_GLOBAL); } } /** * Save first encountered type error. * * @param message * @param node */ private void setTypeError(final String message, final Expression node) { if (mTypeError != null) { return; } mTypeError = new Pair<>(message, node); } } }