diff --git a/sherrloc b/sherrloc index 1258900..ab96ffe 160000 --- a/sherrloc +++ b/sherrloc @@ -1 +1 @@ -Subproject commit 125890034012022280d595bcb6740795dd38b687 +Subproject commit ab96ffe03bc7a3666b42f993c40a2a0a5d15ca71 diff --git a/src/SCIF.cup b/src/SCIF.cup index 55fc384..ccc07fc 100644 --- a/src/SCIF.cup +++ b/src/SCIF.cup @@ -269,7 +269,6 @@ nonterminal List except_types; nonterminal Throw throw_stmt; nonterminal List constructors; nonterminal FunctionDef constructor; -//nonterminal EndorseStatement endorse_stmt; nonterminal EndorseIfStatement endorseIf_stmt; nonterminal Call call_expr; nonterminal Revert revert_stmt; @@ -666,10 +665,10 @@ plain_var_dec ::= // RESULT.setLoc(new CodeLocation(aleft, aright, filename)); // // :} - type:t NAME:a SEMI {: + maybeLabeledType:t NAME:a SEMI {: Name name = new Name((String)a); name.setLoc(new CodeLocation(aleft, aright, filename)); - StateVariableDeclaration rnt = new StateVariableDeclaration(name, new LabeledType(t, null), null, false, false, false); + StateVariableDeclaration rnt = new StateVariableDeclaration(name, t, null, false, false, false); RESULT = rnt; RESULT.setLoc(new CodeLocation(aleft, aright, filename)); :}; @@ -1158,9 +1157,6 @@ stmt ::= assign_stmt:a {: RESULT = a; :} | throw_stmt:a {: RESULT = a;:} | revert_stmt:a {: RESULT = a;:} | emit_stmt:a {: RESULT = a; :} -// | endorse_stmt:a {: -// RESULT = a; -// :} | endorseIf_stmt:a {: RESULT = a; :} @@ -1173,14 +1169,6 @@ stmt ::= assign_stmt:a {: RESULT = a; :} :} ; -//endorse_stmt ::= -// ENDORSEIF:x LPAR name_list:exprList COMMA ifLabel:from RIGHT_ARROW ifLabel:to RPAR IN suite:s {: -// EndorseStatement statement = new EndorseStatement(exprList, from, to, s); -// RESULT = statement; -// RESULT.setLoc(new CodeLocation(xleft, xright, filename)); -// :} -// ; - emit_stmt ::= EMIT:x call_expr:e SEMI {: if (!(e instanceof Call)) { diff --git a/src/TypeChecker.java b/src/TypeChecker.java index 6c5e885..0a26604 100644 --- a/src/TypeChecker.java +++ b/src/TypeChecker.java @@ -320,8 +320,10 @@ private static boolean ifcTypecheck(ContractFile contractFile, VisitEnv env, for (Constraint c : trustCons) System.err.println("DBGTRUST[" + methodName + "] " + c.toSherrlocFmt(true)); for (Sym s : env.principalSet()) System.err.println("DBGCTOR[" + methodName + "] " + s.toSHErrLocFmt()); for (String a : env.rigidAtoms()) System.err.println("DBGATOM[" + methodName + "] " + a); + for (String v : env.versionNames()) System.err.println("DBGVER[" + methodName + "] " + v); } - SherrlocDiagnoser sherrlocDiagnoser = Utils.createDiagnoser(env.principalSet(), trustCons, cons, + SherrlocDiagnoser sherrlocDiagnoser = Utils.createDiagnoser(env.principalSet(), + env.versionNames(), trustCons, cons, true, contractSym, env.rigidAtoms(), env.atomAssumptions()); boolean result; try { diff --git a/src/ast/AnnAssign.java b/src/ast/AnnAssign.java index 5dcb2f5..6bd07d9 100644 --- a/src/ast/AnnAssign.java +++ b/src/ast/AnnAssign.java @@ -151,42 +151,8 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic SLCNameVarLbl = varSym.labelNameSLC(); // // logger.debug(varSym.typeSym.getName()); - // If the declared variable is a principal (final address/contract), add it to the principal list - if ((varSym.isFinal && - (varSym.typeSym instanceof ContractSym || varSym.typeSym.getName().equals(Utils.ADDRESS_TYPE))) - // || varSym.typeSym.name().equals(Utils.PRINCIPAL_TYPE)) Invalid to declare a non-global principal - ) { + if (varSym.isPrincipalTyped()) { env.addPrincipal(varSym); - - VarSym equalPrincipal = null; - boolean correctInit = true; - if (value instanceof Name) { // assigned as another variable - // Nothing to check - equalPrincipal = env.getVar(((Name) value).id); - } else if (value instanceof Call call && call.isCast(env)) { // assigned as another contract - // check if it is a cast to a final address variable - Expression arg = call.getArgAt(0); - if (arg instanceof Name) { - VarSym sym = env.getVar(((Name) arg).id); - if (!sym.isFinal) { - correctInit = false; - } else { - equalPrincipal = sym; - } - } else { - correctInit = false; - } - } else if (!isBuiltIn) { - correctInit = false; - } - - if (correctInit) { - // add equivalence assumption to the trust set - env.checkTrustEqual(SLCNameVar, equalPrincipal.toSHErrLocFmt(), location, - "New principal declaration"); - } else { - throw new SemanticException("A final address/Contract must be initialized to another final address/Contract: " + id, location); - } } String ifNamePc = Utils.getLabelNamePc(scopeContext.getSHErrLocName()); @@ -201,6 +167,11 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic if (value != null) { env.inContext = beginContext; ExpOutcome valueOutcome = value.genIFConstraints(env, scopeContext.isContractLevel()); + // A principal-typed variable starts out naming the principal it is initialized to. + String init = varSym.isPrincipalTyped() ? value.principalName(env) : null; + if (init != null) { + env.assumeSamePrincipal(SLCNameVar, init); + } env.checkFlow(valueOutcome.valueLabelName, SLCNameVarLbl, value.location, "Integrity of the value being assigned must be trusted to allow this assignment"); diff --git a/src/ast/Arg.java b/src/ast/Arg.java index 8071700..e57f6eb 100644 --- a/src/ast/Arg.java +++ b/src/ast/Arg.java @@ -117,7 +117,7 @@ public void genConsVisit(VisitEnv env, boolean tail_position) throws SemanticExc if (annotation.label() != null) { varSym.setLabel(env.toLabel(annotation.label())); } - if (varSym.typeSym instanceof InterfaceSym || varSym.typeSym.getName().equals(Utils.ADDRESS_TYPE)) { + if (varSym.isPrincipalTyped()) { env.addPrincipal(varSym); } } diff --git a/src/ast/Assert.java b/src/ast/Assert.java index 1e43090..2c78a18 100644 --- a/src/ast/Assert.java +++ b/src/ast/Assert.java @@ -35,14 +35,19 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic VarSym l = env.getVar(left.id), r = env.getVar(right.id); // // logger.debug(l.toString()); // // logger.debug(r.toString()); - Inequality hypo = new Inequality(l.toSHErrLocFmt(), Relation.LEQ, - r.toSHErrLocFmt()); + Inequality hypo = new Inequality(env.principalName(l), Relation.LEQ, + env.principalName(r)); env.hypothesis().add(hypo); } } - return test.genIFConstraints(env, tail_position).psi; + PathOutcome result = test.genIFConstraints(env, tail_position).psi; + if (test instanceof ConstantExpr c && c.value == Constant.FALSE) { + // An assertion of false always fails, so it never completes normally. + return result.withoutNormalPath(); + } + return result; } @Override diff --git a/src/ast/Assign.java b/src/ast/Assign.java index b17d9c0..83f0797 100644 --- a/src/ast/Assign.java +++ b/src/ast/Assign.java @@ -16,15 +16,46 @@ public class Assign extends Statement { Expression target; Expression value; + /** Whether this assignment is a constructor's initialization of a final field. */ + private boolean initializesFinalField = false; public Assign(Expression target, Expression value) { this.target = target; this.value = value; } + /** The names of the variables assigned by assignments within {@code nodes}. */ + static java.util.Set assignedNames(List nodes) { + java.util.Set names = new java.util.HashSet<>(); + for (Node node : nodes) { + collectAssignedNames(node, names); + } + return names; + } + + private static void collectAssignedNames(Node node, java.util.Set names) { + if (node instanceof Assign a && a.target instanceof Name n) { + names.add(n.id); + } + for (Node child : node.children()) { + if (child != null) { + collectAssignedNames(child, names); + } + } + } + + /** Marks this assignment as a constructor's initialization of a final field. */ + void markFinalFieldInit() { + initializesFinalField = true; + } + @Override public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { ScopeContext now = new ScopeContext(this, parent); + if (target instanceof Name n && env.getCurSym(n.id) instanceof VarSym v && v.isFinal + && !(initializesFinalField && v.isGlobal())) { + throw new SemanticException("cannot assign to final variable " + n.id, location); + } if (target instanceof Attribute att && att.value.getVarInfo(env) != null && att.value.getVarInfo(env).typeSym instanceof typecheck.ClosureTypeSym && att.attr.id.equals(typecheck.Utils.CLOSURE_TARGET_MEMBER)) { @@ -58,7 +89,7 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // env.prevContext = valueContext; /*env.cons.add(new Constraint(new Inequality(prevLockName, CompareOperator.Eq, valueContext.lambda), env.hypothesis, value.location, env.curContractSym.name, "Lock should be maintained before execution of this operation"));*/ - to = target.genIFConstraints(env, false); + to = target.genLocationConstraints(env); ifNameTgt = to.valueLabelName; // env.inContext = new Context(to.psi.getNormalPath().c.lambda, beginContext.lambda); env.inContext = Utils.genNewContextAndConstraints(env, false, to.psi.getNormalPath().c, beginContext.lambda, target.nextPcSHL(), location); @@ -83,6 +114,16 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic value.location, "Closure value flowing into the assignment target must subtype its slot"); } + // The target names the assigned principal from here on. + if (target instanceof Name tgtName && env.getVar(tgtName.id) instanceof VarSym tgtSym + && tgtSym.isPrincipalTyped()) { + String source = value.principalName(env); + String dest = tgtSym.isMutablePrincipal() ? env.newVersion(tgtSym) : tgtSym.toSHErrLocFmt(); + if (source != null) { + env.assumeSamePrincipal(dest, source); + } + } + typecheck.Utils.contextFlow(env, vo.psi.getNormalPath().c, endContext, value.location); // env.outContext = endContext; diff --git a/src/ast/Atomic.java b/src/ast/Atomic.java index 3bc9db7..e624a1b 100644 --- a/src/ast/Atomic.java +++ b/src/ast/Atomic.java @@ -20,14 +20,14 @@ public class Atomic extends Statement { - List body; + Block body; List handlers; // ArrayList orelse; // ArrayList finalbody; public Atomic(List body, List handlers) {//, ArrayList orelse, ArrayList finalbody) { - this.body = body; + this.body = new Block(body); this.handlers = handlers; // this.orelse = orelse; // this.finalbody = finalbody; @@ -53,10 +53,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S // now.addException(t, false); // } - for (Statement s : body) { -// assert !now.getSHErrLocName().startsWith("null"); - tmp = s.genTypeConstraints(env, atomicScope); - } + body.genTypeConstraints(env, atomicScope); env.exitNewScope(); env.exitAtomic(); @@ -107,10 +104,11 @@ public List solidityCodeGen(CompileEnv code) { readMap = new HashMap<>(); writeMap = new HashMap<>(); - if (body.size() == 1 && body.get(0) instanceof CallStatement && ((CallStatement) body.get(0)).call.value instanceof Attribute) { + List stmts = body.statements(); + if (stmts.size() == 1 && stmts.get(0) instanceof CallStatement callStmt && callStmt.call.value instanceof Attribute) { // if it is a single call - Call call = ((CallStatement) body.get(0)).call; - List result = body.get(0).solidityCodeGen(code); + Call call = callStmt.call; + List result = callStmt.solidityCodeGen(code); if (result.size() == 1 && result.get(0) instanceof ExternalCall) { ExternalCall externalCall = (ExternalCall) result.get(0); // LowLevelCall lowLevelCall = new LowLevelCall(); @@ -137,14 +135,11 @@ public List solidityCodeGen(CompileEnv code) { if (lowLevelCall == null) { // find out read and written local variables - for (Statement s : body) { - readMap.putAll(s.readMap(code)); - writeMap.putAll(s.writeMap(code)); - // solBody.addAll(s.solidityCodeGen(code)); - } + readMap.putAll(body.readMap(code)); + writeMap.putAll(body.writeMap(code)); readMap.putAll(writeMap); code.enterNewVarScope(); - Function newTempFunction = code.makeMethod(body, readMap, writeMap, ScopeType.ATOMIC); + Function newTempFunction = code.makeMethod(body.statements(), readMap, writeMap, ScopeType.ATOMIC); lowLevelCall = new LowLevelCall(newTempFunction, compile.Utils.THIS_ADDRESS, newTempFunction.funcName(), newTempFunction.argNames().stream().map(name -> new SingleVar(name)).collect( Collectors.toList())); @@ -247,17 +242,17 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic //env.psi = newPsi; PathOutcome psi = new PathOutcome(); PathOutcome input = new PathOutcome(); + Map versionsBefore = env.saveVersions(); env.incScopeLayer(); - PathOutcome so = new PathOutcome(new PsiUnit(beginContext)); - Utils.genConsStmtsWithException(body, env, so, psi, tail_position); + psi.join(body.IFCVisit(env, tail_position)); env.decScopeLayer(); for (ExceptHandler h : handlers) { - ExceptionTypeSym expSym = env.getExp(h.name()); + ExceptionTypeSym expSym = h.exceptionType(env); PsiUnit u = psi.psi.get(expSym); if (u != null) { env.checkFlow(u.c.lambda, beginContext.lambda, location, - "Try clause should maintain locks when throwing exception " + h.name()); + "Try clause should maintain locks when throwing exception " + h.type().name()); /*env.cons.add(new Constraint(new Inequality(u.c.pc, h.), env.hypothesis, location, env.curContractSym.name, "Try clause should maintain locks when throwing exception " + h.name));*/ @@ -269,19 +264,34 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic //Context cTry = env.outContext; //env.psi = oldPsi; + // A handler may be entered from any point in the body. + List> exitVersions = new ArrayList<>(); + exitVersions.add(env.saveVersions()); + java.util.Set assignedInBody = ast.Assign.assignedNames(body.statements()); for (ExceptHandler h : handlers) { + env.restoreVersions(versionsBefore); + env.havoc(assignedInBody); env.incScopeLayer(); //env.inContext = new Context(h.getHandlerPcLabelName(), beginContext.lambda); - ExceptionTypeSym expSym = env.getExp(h.name()); - PsiUnit expUnit = input.psi.get(expSym); - if (expUnit != null) { + ExceptionTypeSym expSym = h.exceptionType(env); + if (expSym == null) { + // Nothing is known about where a failure came from, so a handler for + // failures runs with untrusted control flow. + env.inContext = new Context(env.curContractSym().any().toSHErrLocFmt(), + beginContext.lambda); + PathOutcome ho = h.genConsVisit(env, tail_position); + psi.join(ho); + exitVersions.add(env.saveVersions()); + } else if (input.psi.get(expSym) instanceof PsiUnit expUnit) { env.inContext = new Context(expUnit.c.pc, beginContext.lambda); PathOutcome ho = h.genConsVisit(env, tail_position); psi.join(ho); + exitVersions.add(env.saveVersions()); } env.decScopeLayer(); // cTry = new Context(Utils.makeJoin(cTry.pc, env.outContext.outPcName), Utils.makeJoin(cTry.lambda, env.outContext.lockName)); } + env.mergeVersions(exitVersions); Utils.contextFlow(env, psi.getNormalPath().c, endContext, location); @@ -306,7 +316,7 @@ public void passScopeContext(ScopeContext parent) { @Override public List children() { List rtn = new ArrayList<>(); - rtn.addAll(body); + rtn.add(body); rtn.addAll(handlers); return rtn; } @@ -318,10 +328,7 @@ public boolean exceptionHandlingFree() { @Override protected Map readMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } + Map result = new HashMap<>(body.readMap(code)); for (ExceptHandler s: handlers) { result.putAll(s.readMap(code)); } @@ -330,10 +337,7 @@ protected Map readMap(CompileEnv code) { @Override protected Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } + Map result = new HashMap<>(body.writeMap(code)); for (ExceptHandler s: handlers) { result.putAll(s.writeMap(code)); } diff --git a/src/ast/Attribute.java b/src/ast/Attribute.java index 03ff066..e15f15d 100644 --- a/src/ast/Attribute.java +++ b/src/ast/Attribute.java @@ -86,39 +86,83 @@ public VarSym getVarInfo(NTCEnv env) throws SemanticException { } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { - //TODO: assuming only one-level attribute access - // to add support to multi-level access - String varName = ((Name) value).id; - // TODO: steph: needs to check instanceof - - VarSym varSym = env.getVar(varName); - if (varSym != null && varSym.typeSym instanceof ClosureTypeSym - && attr.id.equals(Utils.CLOSURE_TARGET_MEMBER)) { - return value.genIFConstraints(env, tail_position); - } - if (!(varSym.typeSym instanceof StructTypeSym structType)) { - //TODO: throw errors: variable not struct -// return null; - throw new RuntimeException("Variable not a struct: " + varName); - } - - //String prevLockName = env.prevContext.lambda; + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { ExpOutcome vo = value.genIFConstraints(env, tail_position); + TypeSym baseType = value.type(); + if (baseType instanceof ClosureTypeSym && attr.id.equals(Utils.CLOSURE_TARGET_MEMBER)) { + return vo; + } + // A member is trusted at its label and no more than the value it belongs to. String attrValueLabel = vo.valueLabelName; -// String ifAttLabel = structType.getMemberLabel(attr.id); + if (baseType instanceof StructTypeSym structType) { + attrValueLabel = Utils.joinLabels(attrValueLabel, + memberLabel(env, structType.member(attr.id).ifl, attrValueLabel)); + } else if (baseType instanceof ExceptionTypeSym expType) { + // Each principal parameter in the label stands for the principal in that field. + java.util.Map mapping = new java.util.HashMap<>(); + for (VarSym param : expType.parameters()) { + if (param.isPrincipalTyped()) { + mapping.put(param.toSHErrLocFmt(), value instanceof Name n + ? exceptionFieldPrincipal(env, env.getVar(n.id), param.getName()) + : env.curContractSym().any().toSHErrLocFmt()); + } + } + VarSym member = expType.getMemberVarInfo("", attr.id); + attrValueLabel = Utils.joinLabels(attrValueLabel, member.ifl.toSHErrLocFmt(mapping)); + } else { + throw new AssertionError("no member " + attr.id + " in type " + baseType.getName()); + } String ifNameRnt = scopeContext.getSHErrLocName() + ".struct" + location.toString(); -// env.cons.add(new Constraint(new Inequality(ifNameRnt, ifAttLabel), env.hypothesis(), location, -// env.curContractSym().getName(), -// "Integrity of the member")); -// if (!ifAttLabel.equals(attrValueLabel)) { - env.checkEqual(attrValueLabel, ifNameRnt, location, - "Integrity of the index value must be trusted to indicate the attribute"); -// } - + env.checkEqual(attrValueLabel, ifNameRnt, location, + "Integrity of the index value must be trusted to indicate the attribute"); return new ExpOutcome(ifNameRnt, vo.psi); } + @Override + public String principalName(VisitEnv env) { + if (value instanceof Name n && env.getCurSym(n.id) instanceof VarSym v + && v.typeSym instanceof ExceptionTypeSym expType) { + for (VarSym param : expType.parameters()) { + if (param.getName().equals(attr.id) && param.isPrincipalTyped()) { + return exceptionFieldPrincipal(env, v, attr.id); + } + } + } + return null; + } + + /** + * The label {@code label} of a struct member, as this contract reads it in a struct value + * labeled {@code structLabel}. A struct declared by another contract labels its members in + * that contract's terms: its {@code this} is trusted only as much as the value, its top and + * bottom principals are this contract's, and any other principal it names is unknown here. + */ + private static String memberLabel(VisitEnv env, Label label, String structLabel) { + String own = env.thisSym().toSHErrLocFmt(); + String namespace = own.substring(0, own.length() - Utils.LABEL_THIS.length()); + java.util.Map mapping = new java.util.HashMap<>(); + for (VarSym p : label.principalSyms()) { + String name = p.toSHErrLocFmt(); + if (name.startsWith(namespace)) { + continue; + } + mapping.put(name, switch (p.getName()) { + case Utils.LABEL_THIS -> structLabel; + case Utils.LABEL_TOP -> env.curContractSym().top().toSHErrLocFmt(); + default -> env.curContractSym().any().toSHErrLocFmt(); + }); + } + return label.toSHErrLocFmt(mapping); + } + + /** + * The principal held by the principal-typed field {@code field} of the caught exception + * {@code exception}, registered in {@code env}. Nothing is known about it. + */ + private static String exceptionFieldPrincipal(VisitEnv env, VarSym exception, String field) { + return env.freshPrincipal(exception.toSHErrLocFmt() + "." + field); + } + @Override public compile.ast.Expression solidityCodeGen(List result, CompileEnv code) { if (isClosureTarget) { @@ -172,4 +216,42 @@ public java.util.Map readMap(CompileEnv code) { public Map writeMap(CompileEnv code) { return value.writeMap(code); } + + @Override + protected TypeSym staticType(VisitEnv env) { + TypeSym base = value.type(); + if (base instanceof ClosureTypeSym && attr.id.equals(Utils.CLOSURE_TARGET_MEMBER)) { + return env.builtinType(BuiltInT.ADDRESS); + } else if (base instanceof StructTypeSym structType) { + return structType.member(attr.id).typeSym; + } else if (base instanceof ExceptionTypeSym expType) { + return expType.getMemberVarInfo("", attr.id).typeSym; + } + throw new AssertionError("no member " + attr.id + " in type " + base.getName()); + } + + @Override + protected ExpOutcome genLocationFlow(VisitEnv env) throws SemanticException { + ExpOutcome bo = value.genLocationConstraints(env); + TypeSym baseType = value.type(); + String location = bo.valueLabelName; + if (baseType instanceof StructTypeSym structType) { + location = Utils.joinLabels(location, + memberLabel(env, structType.member(attr.id).ifl, bo.valueLabelName)); + } else if (baseType instanceof ExceptionTypeSym expType) { + java.util.Map mapping = new java.util.HashMap<>(); + for (VarSym param : expType.parameters()) { + if (param.isPrincipalTyped()) { + mapping.put(param.toSHErrLocFmt(), value instanceof Name n + ? exceptionFieldPrincipal(env, env.getVar(n.id), param.getName()) + : env.curContractSym().any().toSHErrLocFmt()); + } + } + location = Utils.joinLabels(location, + expType.getMemberVarInfo("", attr.id).ifl.toSHErrLocFmt(mapping)); + } else { + throw new SemanticException("member " + attr.id + " cannot be assigned", this.location); + } + return new ExpOutcome(location, bo.psi); + } } diff --git a/src/ast/BinOp.java b/src/ast/BinOp.java index 182a3b4..fd65f60 100644 --- a/src/ast/BinOp.java +++ b/src/ast/BinOp.java @@ -43,7 +43,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); @@ -110,4 +110,9 @@ public java.util.Map readMap(CompileEnv code) { result.putAll(right.readMap(code)); return result; } + + @Override + protected TypeSym staticType(VisitEnv env) { + return env.builtinType(BuiltInT.UINT); + } } diff --git a/src/ast/Block.java b/src/ast/Block.java new file mode 100644 index 0000000..b228647 --- /dev/null +++ b/src/ast/Block.java @@ -0,0 +1,124 @@ +package ast; + +import compile.CompileEnv; +import compile.ast.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import typecheck.NTCEnv; +import typecheck.PathOutcome; +import typecheck.PsiUnit; +import typecheck.ScopeContext; +import typecheck.Utils; +import typecheck.VisitEnv; +import typecheck.exceptions.SemanticException; + +/** + * A sequence of statements executed in order, such as the body of a method or a branch of a + * conditional. A block does not introduce a scope; the construct that contains it decides + * which scopes its statements are in. + */ +public class Block extends Statement { + + private final List statements; + + public Block(List statements) { + this.statements = new ArrayList<>(statements); + } + + /** The statements of this block, in order. */ + public List statements() { + return Collections.unmodifiableList(statements); + } + + /** Whether this block has no statements. */ + public boolean isEmpty() { + return statements.isEmpty(); + } + + /** + * Generates the type constraints of each statement in the scope context {@code parent}, + * and returns the scope context of the last statement, or {@code parent} if there is none. + */ + @Override + public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) + throws SemanticException { + ScopeContext last = parent; + for (Statement s : statements) { + last = s.genTypeConstraints(env, parent); + } + return last; + } + + /** + * Generates the information-flow constraints for this block, which starts in the context + * {@code env.inContext}, and returns every way it can terminate: its exception and return + * paths, and its normal path if it can complete normally. The last statement is in tail + * position if {@code tail_position} is. On return, {@code env.inContext} is the context + * after the last statement that completed normally. + */ + @Override + public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws SemanticException { + PathOutcome result = new PathOutcome(); + int index = 0; + for (Statement s : statements) { + ++index; + String prevLambda = env.inContext.lambda; + boolean isTail = index == statements.size() && tail_position; + PathOutcome so = s.IFCVisit(env, isTail); + PsiUnit normal = so.getNormalPath(); + result.join(so.withoutNormalPath()); + if (normal == null) { + return result; + } + env.inContext = Utils.genNewContextAndConstraints(env, isTail, normal.c, prevLambda, + s.nextPcSHL(), s.location()); + } + result.setNormalPath(env.inContext); + return result; + } + + @Override + public List solidityCodeGen(CompileEnv code) { + List result = new ArrayList<>(); + for (Statement s : statements) { + result.addAll(s.solidityCodeGen(code)); + } + return result; + } + + @Override + public List children() { + return new ArrayList<>(statements); + } + + @Override + public boolean exceptionHandlingFree() { + for (Statement s : statements) { + if (!s.exceptionHandlingFree()) { + return false; + } + } + return true; + } + + @Override + protected Map readMap(CompileEnv code) { + Map result = new HashMap<>(); + for (Statement s : statements) { + result.putAll(s.readMap(code)); + } + return result; + } + + @Override + protected Map writeMap(CompileEnv code) { + Map result = new HashMap<>(); + for (Statement s : statements) { + result.putAll(s.writeMap(code)); + } + return result; + } +} diff --git a/src/ast/BoolOp.java b/src/ast/BoolOp.java index c257648..ec514ba 100644 --- a/src/ast/BoolOp.java +++ b/src/ast/BoolOp.java @@ -36,7 +36,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); @@ -103,4 +103,9 @@ public java.util.Map readMap(CompileEnv code) { result.putAll(right.readMap(code)); return result; } + + @Override + protected TypeSym staticType(VisitEnv env) { + return env.builtinType(BuiltInT.BOOL); + } } diff --git a/src/ast/Call.java b/src/ast/Call.java index bcc8ea1..99f8c56 100644 --- a/src/ast/Call.java +++ b/src/ast/Call.java @@ -32,7 +32,10 @@ public class Call extends TrailerExpr { List args; - // store the called func symbol after regular typechecking + /** + * The method this call invokes, as resolved by the regular type check, or null if it + * invokes none. Code generation uses it; the IFC check resolves the callee separately. + */ FuncSym funcSym = null; private boolean isCast; boolean builtIn = false, ntced = false; @@ -45,11 +48,20 @@ ClosureTypeSym.RealLabels realLabels() { return realLabels; } + /** Whether this call invokes a closure, as determined by the regular type check. */ private boolean isClosureInvoke = false; - // Closure's external begin label (lextbef) + /** + * For a closure invocation, the principals in the closure's external begin label (lextbef), + * set by the IFC check. Code generation requires it. + */ private List closurePcExLeaves = null; - /** The invoked closure's declared type, kept for codegen. */ + /** + * For a closure invocation, the invoked closure's declared type with its labels, set by the + * IFC check. Code generation requires it. + */ private ClosureTypeSym closureSym = null; + /** The type of this call's result, once it has been IFC-checked. */ + private TypeSym resultType; public Call() { this.args = new ArrayList<>(); @@ -102,22 +114,22 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S // a.b(c), a must be a contract or an array extern = true; - if (!(att.value instanceof Name)) { - throw new SemanticException( - "the receiver of a method call must be a variable", att.value.location); - } - String varName = ((Name) att.value).id; // a - // System.out.println(varName); funcName = att.attr.id; // b - Sym s = env.getCurSym(varName); - if (s == null) { - throw new SemanticException("variable not found: " + varName, att.value.location); + if (att.value instanceof Name n && env.getCurSym(n.id) == null) { + throw new SemanticException("variable not found: " + n.id, att.value.location); } - // logger.debug("var " + varName + ": " + s.getName()); - - if (!(s instanceof VarSym varSym)) { - throw new SemanticException("type error in call (callee must be a variable): ", this.location); + // The receiver is a variable, or a member or element of one. + VarSym varSym = att.value.getVarInfo(env); + if (varSym == null) { + throw new SemanticException( + "the receiver of a method call must be a variable, a member, or an element", + att.value.location); } + if (!(att.value instanceof Name)) { + att.value.genTypeConstraints(env, now); + } + String varName = varSym.getName(); + Sym s; if (varSym.typeSym instanceof InterfaceSym contractSym) { s = contractSym.getFunc(funcName); @@ -163,6 +175,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } else if (varSym.typeSym instanceof ClosureTypeSym closureTypeSym) { // c.invoke(args): args must match the still-unbound parameters; the call's type is the closure's return. this.builtIn = true; + this.isClosureInvoke = true; if (!funcName.equals("invoke")) { throw new SemanticException("type error: unknown closure operator " + funcName, this.location); } @@ -246,7 +259,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S String rtnTypeName = funcSym.returnType.toSHErrLocFmt(); env.addCons(now.genTypeConstraints(rtnTypeName, Relation.EQ, env, location)); - for (Map.Entry tl : funcSym.exceptions.entrySet()) { + for (Map.Entry tl : funcSym.exceptions.entrySet()) { if (!parent.isCheckedException(tl.getKey(), extern)) { throw new SemanticException("Unchecked exception: " + tl.getKey().getName(), location); @@ -276,7 +289,19 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S * operator other than {@code invoke} */ @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) + throws SemanticException { + ExpOutcome result = callIFConstraints(env, tail_position); + // A call may assign to any field, so fields hold new principals afterwards. + boolean cast = value instanceof Name n && (env.getCurSym(n.id) instanceof InterfaceSym + || env.getCurSym(n.id) instanceof BuiltinTypeSym); + if (!cast) { + env.havocFields(); + } + return result; + } + + private ExpOutcome callIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { //TODO: Assuming value is a Name for now Context beginContext = env.inContext; // (pc, lambda) @@ -285,6 +310,8 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) Map dependentLabelMapping = new HashMap<>(); List argValueLabelNames = new ArrayList<>(); + // The principal each argument holds when it is evaluated, or null. + List argPrincipalNames = new ArrayList<>(); PathOutcome psi = new PathOutcome(new PsiUnit(endContext)); @@ -294,6 +321,7 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) ao = arg.genIFConstraints(env, false); psi.joinExe(ao.psi); argValueLabelNames.add(ao.valueLabelName); + argPrincipalNames.add(arg.principalName(env)); env.inContext = Utils.genNewContextAndConstraints(env, false, ao.psi.getNormalPath().c, beginContext.lambda, arg.nextPcSHL(), arg.location); @@ -304,7 +332,7 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) String funcName; String ifNamePc; // currentMethod.PC - FuncSym funcSym; + FuncSym calleeSym; String namespace = ""; Label ifFuncCallPcBefore, ifFuncCallPcAfter, ifFuncGammaLock; @@ -312,8 +340,8 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) boolean externalCall = false; InterfaceSym externalContractSym = null; - VarSym externalTargetSym = null; String ifContRtn = null; + String receiverPrincipal = null; // the principal the receiver holds, if it is a variable if (!(value instanceof Name)) { if (!(value instanceof Attribute att)) { @@ -329,13 +357,12 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) vo = att.value.genIFConstraints(env, false); psi.joinExe(vo.psi); ifContRtn = vo.valueLabelName; // a..lbl + receiverPrincipal = att.value.principalName(env); - //TODO: assuming a's depth is 1 - String varName = ((Name) att.value).id; // a funcName = (att.attr).id; // b - VarSym var = env.getVar(varName); + TypeSym receiverType = att.value.type(); - if (var.typeSym instanceof ArrayTypeSym arrayTypeSym) { + if (receiverType instanceof ArrayTypeSym arrayTypeSym) { // case 1a: a is an array (terminating branch) TypeSym arrayTSym = arrayTypeSym.valueType; @@ -343,35 +370,39 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) //TODO: change the hard-code style if (funcName.equals("pop")) { - // requires pc => integrity of the array var + // pop modifies the array, so the control flow must be trusted at its location. + String arrayLocation = att.value.genLocationConstraints(env).valueLabelName; Utils.contextFlow(env, psi.getNormalPath().c, endContext, location); ifNamePc = Utils.getLabelNamePc(scopeContext.getSHErrLocName()); - env.checkFlow(ifNamePc, ifContRtn, location, + env.checkFlow(ifNamePc, arrayLocation, location, "Current control flow must be trusted to call this method"); // pc => l if (!tail_position) { env.checkFlow(psi.getNormalPath().c.lambda, beginContext.lambda, location, Utils.ERROR_MESSAGE_LOCK_IN_NONLAST_OPERATION); } + resultType = env.builtinType(BuiltInT.VOID); return new ExpOutcome(ifNamePc, psi); } else if (funcName.equals("push")) { - // require pc => integrity of the array var - // require the element => integrity of the array var + // push modifies the array, so the control flow and the element must be + // trusted at its location. + String arrayLocation = att.value.genLocationConstraints(env).valueLabelName; Expression arg = args.get(0); ExpOutcome argOutcome = arg.genIFConstraints(env, false); psi.join(argOutcome.psi); String argLabel = argOutcome.valueLabelName; Utils.contextFlow(env, psi.getNormalPath().c, endContext, location); ifNamePc = Utils.getLabelNamePc(scopeContext.getSHErrLocName()); - env.checkFlow(ifNamePc, var.ifl.toSHErrLocFmt(), location, + env.checkFlow(ifNamePc, arrayLocation, location, "Current control flow must be trusted to call this method"); // pc => ? - env.checkFlow(argLabel, var.ifl.toSHErrLocFmt(), location, + env.checkFlow(argLabel, arrayLocation, location, "Current control flow must be trusted to call this method"); if (!tail_position) { env.checkFlow(psi.getNormalPath().c.lambda, beginContext.lambda, location, Utils.ERROR_MESSAGE_LOCK_IN_NONLAST_OPERATION); } + resultType = env.builtinType(BuiltInT.VOID); return new ExpOutcome(ifNamePc, psi); } else if (funcName.equals("length")) { // return uint the same as @@ -381,13 +412,14 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) env.checkFlow(psi.getNormalPath().c.lambda, beginContext.lambda, location, Utils.ERROR_MESSAGE_LOCK_IN_NONLAST_OPERATION); } - return new ExpOutcome(var.ifl.toSHErrLocFmt(), psi); + resultType = env.builtinType(BuiltInT.UINT); + return new ExpOutcome(ifContRtn, psi); } else { throw new SemanticException("Unrecognized operator", location); } } - if (var.typeSym instanceof ClosureTypeSym cs) { + if (receiverType instanceof ClosureTypeSym cs) { // case 1b: a is a closure value; a.invoke(args) (terminating branch) if (!funcName.equals("invoke")) { @@ -395,8 +427,6 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) "type error: unknown closure operator " + funcName, location); } - this.builtIn = true; - this.isClosureInvoke = true; this.closureSym = cs; cs.registerAtoms(env); @@ -415,17 +445,12 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) if (binder == null) { continue; } - Expression principalExp = args.get(i); - if (principalExp instanceof Call cast && cast.isCast(env)) { - principalExp = cast.getArgAt(0); - } - VarSym actual = principalExp instanceof Name an ? env.getVar(an.id) : null; - if (actual != null && actual.isPrincipalVar()) { - dependentLabelMapping.put(binder.toSHErrLocFmt(), - actual.toSHErrLocFmt()); + String actual = argPrincipalNames.get(i); + if (actual != null) { + dependentLabelMapping.put(binder.toSHErrLocFmt(), actual); } else if (cs.referencesBinder(binder)) { throw new SemanticException( - "must use a final address/contract for the " + "must use an address or contract variable for the " + Utils.ordNumString(i + 1) + " invoke argument: the closure's labels depend on it", args.get(i).location); @@ -517,31 +542,31 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) "Post-invocation control flow must carry the closure's value label"); psi.setNormalPath(endContext); env.checkFlow(psi.getNormalPath().c.pc, ifNameFuncRtnValue, location, "ln_to_t"); + resultType = cs.returnType; return new ExpOutcome(ifNameFuncRtnValue, psi); } // case 1c: external call - // look up funcSym, update dependentLabelMapping, and read labels from funcSym + // look up calleeSym, update dependentLabelMapping, and read labels from calleeSym - externalTargetSym = var; - namespace = var.toSHErrLocFmt(); - TypeSym conType = var.typeSym; + TypeSym conType = receiverType; + namespace = conType.getName(); externalContractSym = env.getContract(conType.getName()); env.addSigReq(namespace, conType.getName()); ifNamePc = Utils.getLabelNamePc(scopeContext.getSHErrLocName()); InterfaceSym contractSym = env.getContract(conType.getName()); - funcSym = contractSym.getFunc(funcName); - if (funcSym == null) + calleeSym = contractSym.getFunc(funcName); + if (calleeSym == null) throw new SemanticException("not found: " + conType.getName() + "." + funcName, location); - dependentLabelMapping.put(funcSym.sender().toSHErrLocFmt(), env.thisSym().toSHErrLocFmt()); + dependentLabelMapping.put(calleeSym.sender().toSHErrLocFmt(), env.thisSym().toSHErrLocFmt()); dependentLabelMapping.put(contractSym.any().toSHErrLocFmt(), env.curContractSym().any().toSHErrLocFmt()); dependentLabelMapping.put(contractSym.invoker().toSHErrLocFmt(), env.curContractSym().invoker().toSHErrLocFmt()); - ifFuncCallPcBefore = funcSym.externalPc(); - ifFuncCallPcAfter = funcSym.internalPc(); - ifFuncGammaLock = funcSym.callGamma(); + ifFuncCallPcBefore = calleeSym.externalPc(); + ifFuncCallPcAfter = calleeSym.internalPc(); + ifFuncGammaLock = calleeSym.callGamma(); } else { // case 2: a(b) - local contract call or type cast funcName = ((Name) value).id; @@ -559,6 +584,14 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) } String ifNameArgValue = argValueLabelNames.get(0); + if (env.containsContract(funcName)) { + // A contract reference is trusted no more than the contract it + // names, which is the principal the cast address holds. + String principal = argPrincipalNames.get(0); + ifNameArgValue = principal != null + ? Utils.joinLabels(ifNameArgValue, principal) + : env.curContractSym().any().toSHErrLocFmt(); + } Utils.contextFlow(env, psi.getNormalPath().c, endContext, args.get(0).location); // env.outContext = endContext; @@ -566,6 +599,8 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) env.checkFlow(psi.getNormalPath().c.lambda, beginContext.lambda, location, Utils.ERROR_MESSAGE_LOCK_IN_NONLAST_OPERATION); } + resultType = env.containsContract(funcName) ? env.getContract(funcName) + : (TypeSym) env.getCurSym(funcName); return new ExpOutcome(ifNameArgValue, psi); } else { throw new SemanticException("method not found: " + funcName, location); @@ -574,20 +609,20 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) // case 2b: local call - funcSym = env.getFunc(funcName); + calleeSym = env.getFunc(funcName); -// dependentLabelMapping.put(funcSym.sender().toSHErrLocFmt(), env.sender().toSHErrLocFmt()); - dependentLabelMapping.put(funcSym.sender().toSHErrLocFmt(), env.inContext.pc); +// dependentLabelMapping.put(calleeSym.sender().toSHErrLocFmt(), env.sender().toSHErrLocFmt()); + dependentLabelMapping.put(calleeSym.sender().toSHErrLocFmt(), env.inContext.pc); - ifFuncCallPcBefore = funcSym.externalPc(); - ifFuncCallPcAfter = funcSym.internalPc(); - ifFuncGammaLock = funcSym.callGamma(); + ifFuncCallPcBefore = calleeSym.externalPc(); + ifFuncCallPcAfter = calleeSym.internalPc(); + ifFuncGammaLock = calleeSym.callGamma(); } // build hypothesis for sender and this // make sender equal to this // Inequality senderHypo = new Inequality( -// funcSym.sender().toSHErrLocFmt(), +// calleeSym.sender().toSHErrLocFmt(), // CompareOperator.Eq, // env.curContractSym.toSHErrLocFmt() // ); @@ -601,50 +636,50 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) env.inContext = Utils.genNewContextAndConstraints(env, false, co.getNormalPath().c, beginContext.lambda, callSpec.nextPcSHL(), callSpec.location); } - // if external call and the target address is final, make callee.this equal to the target address - if (externalCall) { - if (externalTargetSym.isFinal) { - dependentLabelMapping.put( - externalContractSym.thisSym().toSHErrLocFmt(), - externalTargetSym.toSHErrLocFmt()); - } else { - dependentLabelMapping.put( - externalContractSym.thisSym().toSHErrLocFmt(), - ifContRtn); // XXX is this correct? Seems like this is the label of the contract value. + // A principal parameter of the callee names the principal passed for it. + for (int i = 0; i < args.size(); ++i) { + VarSym argSym = calleeSym.parameters.get(i); + if (argSym.isPrincipalTyped() && argPrincipalNames.get(i) != null) { + dependentLabelMapping.put(argSym.toSHErrLocFmt(), argPrincipalNames.get(i)); } } + // The callee's labels are instantiated differently in input and output positions. + // Its inputs (argument labels, external pc) are demands it makes of this caller, + // so there the callee's `this` is the callee itself: the principal the receiver + // holds. This contract can meet those demands only with what it trusts itself, + // since it is the top principal here. Its outputs (return label, post-call pc, + // lock label) are promises trusted only as much as the callee, which acts for the + // label of the reference used to call it, so there its `this` becomes that label. + Map inputMapping = new HashMap<>(dependentLabelMapping); + Map outputMapping = new HashMap<>(dependentLabelMapping); + if (externalCall) { + String calleeThis = externalContractSym.thisSym().toSHErrLocFmt(); + inputMapping.put(calleeThis, receiverPrincipal != null + ? receiverPrincipal : env.thisSym().toSHErrLocFmt()); + outputMapping.put(calleeThis, ifContRtn); + } + // Whatever the callee's labels say, its results are trusted no more than the + // reference used to call it. + String calleeTrust = externalCall ? ifContRtn : null; + // Generate constraints on arguments -// System.err.println("Call: " + funcName); for (int i = 0; i < args.size(); ++i) { Expression arg = args.get(i); - VarSym argSym = funcSym.parameters.get(i); - if (argSym.isPrincipalVar()) { - - if (arg instanceof Name) { - VarSym valueSym = (VarSym) env.getVar(((Name) arg).id); - if (valueSym.isPrincipalVar()) { -// System.err.println("dependent " + argSym.typeSym + " -> " + valueSym.toSHErrLocFmt()); - dependentLabelMapping.put(argSym.toSHErrLocFmt(), valueSym.toSHErrLocFmt()); - } - } - // TODO: Do we throw exception if arg is not a Name? - } - - // env.prevContext = prevContext = tmp; + VarSym argSym = calleeSym.parameters.get(i); String ifNameArgValue = argValueLabelNames.get(i); // argument's label name - Label ifArgLabel = funcSym.getLabelArg(i); // parameter's label + Label ifArgLabel = calleeSym.getLabelArg(i); // parameter's label assert ifArgLabel != null : argSym.getName(); - env.checkFlow(ifNameArgValue, ifArgLabel.toSHErrLocFmt(dependentLabelMapping), + env.checkFlow(ifNameArgValue, ifArgLabel.toSHErrLocFmt(inputMapping), arg.location, "Input to the " + Utils.ordNumString(i + 1) + " argument must be trusted enough"); // arglbl => paramlbl - env.checkFlow(ifNamePc, ifArgLabel.toSHErrLocFmt(dependentLabelMapping), arg.location, + env.checkFlow(ifNamePc, ifArgLabel.toSHErrLocFmt(inputMapping), arg.location, "Current control flow must be trusted to feed the " + Utils.ordNumString(i + 1) + "-th argument value"); // pc => paramlbl if (argSym.typeSym instanceof ClosureTypeSym paramCs) { - ClosureCreation.checkFlowInto(arg, paramCs, dependentLabelMapping, env, arg.location, + ClosureCreation.checkFlowInto(arg, paramCs, inputMapping, env, arg.location, "Closure value flowing into the " + Utils.ordNumString(i + 1) + " argument must subtype its slot"); } } @@ -652,46 +687,35 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) // Generate pre-call and post-call constraints and contexts if (externalCall) { -// String tem = ((Attribute) value).value.toSHErrLocFmt(); - env.checkFlow(ifContRtn, ifFuncCallPcBefore.toSHErrLocFmt(dependentLabelMapping), + env.checkFlow(ifContRtn, ifFuncCallPcBefore.toSHErrLocFmt(inputMapping), location, "Target contract must be trusted to call this method"); // target address..lbl => pc_ex } + String postCallLock = attenuate(ifFuncGammaLock.toSHErrLocFmt(outputMapping), calleeTrust); PathOutcome expPsi = new PathOutcome(new PsiUnit(new Context( - Utils.joinLabels(psi.getNormalPath().c.pc, funcSym.endPc().toSHErrLocFmt(dependentLabelMapping)), -// funcSym.getLabelNameCallGamma() - ifFuncGammaLock.toSHErrLocFmt(dependentLabelMapping) - ))); // post-call psi = (current normal path pc ⊔ function return label, gamma) - - for (Entry exp : funcSym.exceptions.entrySet()) { - ExceptionTypeSym curSym = exp.getKey(); - String expLabelName = exp.getValue(); - // pc_2 dropped - // expPsi.set(curSym, new PsiUnit( - // new Context( - // Utils.joinLabels(expLabelName, funcSym.externalPcSLC()), - // Utils.joinLabels(ifFuncGammaLock.toSHErrLocFmt(dependentLabelMapping), - // ifFuncCallPcAfter.toSHErrLocFmt(dependentLabelMapping))), - // true) - // ); // exception-path post-call psi = (function exception-path pc ⊔ pc_ex, - // // gamma ⊔ pc_in) - expPsi.set(curSym, new PsiUnit( + attenuate(Utils.joinLabels(psi.getNormalPath().c.pc, + calleeSym.endPc().toSHErrLocFmt(outputMapping)), calleeTrust), + postCallLock + ))); // post-call psi = (current normal path pc ⊔ function end pc, gamma), attenuated + + if (externalCall) { + checkExceptionData(env, calleeSym, outputMapping); + } + for (Entry exp : calleeSym.exceptions.entrySet()) { + // exception-path post-call psi = (current pc ⊔ declared exception label, gamma), + // attenuated like the normal path + expPsi.set(exp.getKey(), new PsiUnit( new Context( - Utils.joinLabels(expLabelName, funcSym.externalPcSLC()), - ifFuncGammaLock.toSHErrLocFmt(dependentLabelMapping)), - true) - ); // exception-path post-call psi = (function exception-path pc ⊔ pc_ex, gamma) - // TODO: should pc label get joined by pc_ex, or psi.normal.pc? - //TODO: dependent - //PsiUnit psiUnit = env.psi.get(curSym); - //env.cons.add(new Constraint(new Inequality(Utils.makeJoin(expLabelName, ifNameFuncCallPcAfter), psiUnit.pc), env.hypothesis, location, env.curContractSym.name, - //"Exception " + curSym.name + " is not trusted enough to throw")); + attenuate(Utils.joinLabels(psi.getNormalPath().c.pc, + exp.getValue().toSHErrLocFmt(outputMapping)), calleeTrust), + postCallLock), + true)); } //TODO - env.checkFlow(ifNamePc, ifFuncCallPcBefore.toSHErrLocFmt(dependentLabelMapping), location, + env.checkFlow(ifNamePc, ifFuncCallPcBefore.toSHErrLocFmt(inputMapping), location, "Current control flow must be trusted to call this method"); // current pc => pc_ex // pc_2 dropped // env.cons.add(new Constraint(new Inequality(ifFuncCallPcBefore.toSHErrLocFmt(dependentLabelMapping), @@ -701,10 +725,8 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) // ); // pc_ex => pc_in ⊔ caller's lambda if (externalCall) { - // target address..lbl ⊔ gamma == post-call lambda - env.checkEqual( - Utils.joinLabels(ifContRtn, ifFuncGammaLock.toSHErrLocFmt(dependentLabelMapping)), - endContext.lambda, location, + // callee trust ⊔ gamma == post-call lambda + env.checkEqual(postCallLock, endContext.lambda, location, "Calling this function does not respect static reentrancy locks"); } @@ -724,11 +746,12 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) // Finalize - String ifNameFuncRtnValue = funcSym.rtn.toSHErrLocFmt(dependentLabelMapping); + String ifNameFuncRtnValue = attenuate(calleeSym.rtn.toSHErrLocFmt(outputMapping), + calleeTrust); // realLabels - if (funcSym.returnType instanceof ClosureTypeSym retCs) { - this.realLabels = retCs.renderLabels(dependentLabelMapping); + if (calleeSym.returnType instanceof ClosureTypeSym retCs) { + this.realLabels = retCs.renderLabels(outputMapping); } psi.joinExe(expPsi); @@ -737,9 +760,54 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) // post-call pc (normal) => function return label (l_n) env.checkFlow(psi.getNormalPath().c.pc, ifNameFuncRtnValue, location, "Control flow after this call must be trusted by the function's return value label"); + resultType = calleeSym.returnType; return new ExpOutcome(ifNameFuncRtnValue, psi); } + /** + * Checks that the data of each exception the external callee {@code calleeSym} may throw is + * trusted at the labels this contract's declaration of that exception gives it, since + * handlers here read it at those labels. The callee's labels are instantiated with + * {@code outputMapping}, with its exception's principal parameters standing for the + * corresponding parameters of this contract's declaration. + */ + private void checkExceptionData(VisitEnv env, FuncSym calleeSym, + Map outputMapping) { + // A handler reads each field at its label joined with the label of the exception's + // path, which is attenuated by the callee's trust, so the data is too. + for (ExceptionTypeSym calleeExp : calleeSym.exceptions.keySet()) { + ExceptionTypeSym localExp = env.getExp(calleeExp.getName()); + if (localExp == null) { + throw new AssertionError("exception " + calleeExp.getName() + + " of an external call has no local declaration"); + } + Map mapping = new HashMap<>(outputMapping); + for (int i = 0; i < calleeExp.parameters().size(); ++i) { + VarSym calleeParam = calleeExp.parameters().get(i); + if (calleeParam.isPrincipalTyped()) { + mapping.put(calleeParam.toSHErrLocFmt(), env.freshPrincipal( + localExp.parameters().get(i).toSHErrLocFmt())); + } + } + for (int i = 0; i < calleeExp.parameters().size(); ++i) { + VarSym calleeParam = calleeExp.parameters().get(i); + VarSym localParam = localExp.parameters().get(i); + env.checkFlow(calleeParam.ifl.toSHErrLocFmt(mapping), + localParam.ifl.toSHErrLocFmt(), location, + "Data of exception " + calleeExp.getName() + + " from this call must be trusted at its declared label here"); + } + } + } + + /** + * The label {@code label} trusted no more than {@code trust}, or {@code label} itself if + * {@code trust} is null. {@code trust} may be null. + */ + private static String attenuate(String label, String trust) { + return trust == null ? label : Utils.joinLabels(label, trust); + } + /** * Reject the closure-invocation shapes whose code generation is not * implemented, rather than emitting partial code: results the @@ -763,6 +831,11 @@ private void checkInvokeSupported() { @Override public compile.ast.Expression solidityCodeGen(List result, CompileEnv code) { if (isClosureInvoke) { + if (closureSym == null || closurePcExLeaves == null) { + throw new AssertionError( + "code generation for a closure invocation requires the IFC check: " + + location); + } assert value instanceof Attribute; compile.ast.Expression closureExp = ((Attribute) value).value.solidityCodeGen(result, code); @@ -890,7 +963,7 @@ public compile.ast.Expression solidityCodeGen(List result, CompileEnv // map exceptionID IfStatement mapExpIds = null; int i = 1; - for (Entry entry: funcSym.exceptions.entrySet()) { + for (Entry entry: funcSym.exceptions.entrySet()) { IfStatement ifexp = new IfStatement( new BinaryExpression(compile.Utils.SOL_BOOL_EQUAL, statVar, new Literal(String.valueOf(i))), List.of(new Assign(statVar, new Literal(String.valueOf(code.getExceptionId(entry.getKey()))))), @@ -980,6 +1053,17 @@ public boolean typeMatch(Expression expression) { return true; } + @Override + public String principalName(VisitEnv env) { + if (args.size() == 1 && isCast(env)) { + String type = ((Name) value).id; + if (env.containsContract(type) || type.equals(Utils.ADDRESS_TYPE)) { + return args.get(0).principalName(env); + } + } + return null; + } + /** * Check if a type cast */ @@ -1022,4 +1106,9 @@ public void checkAndRenameSuper(String extendsContractName) { } } } + + @Override + protected TypeSym staticType(VisitEnv env) { + return resultType; + } } diff --git a/src/ast/ClosureCreation.java b/src/ast/ClosureCreation.java index 30c6129..c0dda8f 100644 --- a/src/ast/ClosureCreation.java +++ b/src/ast/ClosureCreation.java @@ -222,16 +222,10 @@ private ExpOutcome bindIFConstraints(VisitEnv env, PathOutcome psi, // A slot filled with a principal becomes that principal // everywhere the result's labels named the slot. - Expression principalExp = a; - if (a instanceof Call cast && cast.isCast(env)) { - principalExp = cast.getArgAt(0); - } VarSym binder = i < binders.size() ? binders.get(i) : null; - if (binder != null && principalExp instanceof Name an) { - VarSym valueSym = env.getVar(an.id); - if (valueSym != null && valueSym.isPrincipalVar()) { - bindMapping.put(binder.toSHErrLocFmt(), valueSym.toSHErrLocFmt()); - } + String principal = a.principalName(env); + if (binder != null && principal != null) { + bindMapping.put(binder.toSHErrLocFmt(), principal); } ExpOutcome ao = a.genIFConstraints(env, false); @@ -292,7 +286,7 @@ private ExpOutcome bindIFConstraints(VisitEnv env, PathOutcome psi, } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { Context beginContext = env.inContext; Context endContext = new Context(Utils.getLabelNamePc(toSHErrLocFmt()), @@ -357,17 +351,9 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) VarSym argSym = funcSym.parameters.get(i); - Expression principalExp = a; - if (a instanceof Call cast && cast.isCast(env)) { - principalExp = cast.getArgAt(0); - } - - if (argSym.isPrincipalVar() && principalExp instanceof Name an) { - VarSym valueSym = env.getVar(an.id); - if (valueSym != null && valueSym.isPrincipalVar()) { - dependentLabelMapping.put(argSym.toSHErrLocFmt(), - valueSym.toSHErrLocFmt()); - } + String principal = a.principalName(env); + if (argSym.isPrincipalTyped() && principal != null) { + dependentLabelMapping.put(argSym.toSHErrLocFmt(), principal); } ExpOutcome ao = a.genIFConstraints(env, false); @@ -575,4 +561,30 @@ public List children() { rtn.addAll(args); return rtn; } + + @Override + protected TypeSym staticType(VisitEnv env) { + List declaredParams = new ArrayList<>(); + TypeSym retSym; + if (callee instanceof Attribute att && att.value instanceof Name recvName) { + FuncSym target = ((InterfaceSym) env.getVar(recvName.id).typeSym) + .getFunc(att.attr.id); + for (VarSym p : target.parameters) { + declaredParams.add(p.typeSym); + } + retSym = target.returnType; + } else { + ClosureTypeSym source = + (ClosureTypeSym) env.getVar(((Name) callee).id).typeSym; + declaredParams.addAll(source.unboundParams); + retSym = source.returnType; + } + List stillUnbound = new ArrayList<>(); + for (int i = 0; i < args.size(); ++i) { + if (args.get(i) instanceof Hole) { + stillUnbound.add(declaredParams.get(i)); + } + } + return new ClosureTypeSym(stillUnbound, retSym, new ArrayList<>(), scopeContext); + } } diff --git a/src/ast/Compare.java b/src/ast/Compare.java index e2a3c34..60c0b46 100644 --- a/src/ast/Compare.java +++ b/src/ast/Compare.java @@ -44,7 +44,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); @@ -110,4 +110,9 @@ public java.util.Map readMap(CompileEnv code) { result.putAll(right.readMap(code)); return result; } + + @Override + protected TypeSym staticType(VisitEnv env) { + return env.builtinType(BuiltInT.BOOL); + } } diff --git a/src/ast/ComplexIfLabel.java b/src/ast/ComplexIfLabel.java index ac9ae0f..d29045c 100644 --- a/src/ast/ComplexIfLabel.java +++ b/src/ast/ComplexIfLabel.java @@ -100,7 +100,7 @@ public String toSHErrLocFmt(ScopeContext defContext) { // return rnt; // } - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) { // TODO: deal with dynamic labels return null; diff --git a/src/ast/ConstantExpr.java b/src/ast/ConstantExpr.java index ccec8dc..3985d26 100644 --- a/src/ast/ConstantExpr.java +++ b/src/ast/ConstantExpr.java @@ -25,7 +25,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) { } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) { Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), @@ -60,4 +60,9 @@ public List children() { List rtn = new ArrayList<>(); return rtn; } + + @Override + protected TypeSym staticType(VisitEnv env) { + return env.builtinType(BuiltInT.BOOL); + } } diff --git a/src/ast/Contract.java b/src/ast/Contract.java index ae551b9..f162720 100644 --- a/src/ast/Contract.java +++ b/src/ast/Contract.java @@ -177,6 +177,8 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S def.genTypeConstraints(env, now); } + markFinalFieldInits(); + for (FunctionDef fDef : methodDeclarations) { fDef.genTypeConstraints(env, now); } @@ -185,6 +187,41 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S return now; } + /** + * Checks that each final field, other than a built-in one, is assigned exactly once by a + * top-level statement of a constructor (this contract's own, or a superclass constructor + * inherited as a method), and marks those assignments as final-field initializations. + */ + private void markFinalFieldInits() throws SemanticException { + Map finalFields = new HashMap<>(); + for (StateVariableDeclaration dec : varDeclarations) { + if (dec.isFinal() && !dec.isBuiltIn()) { + finalFields.put(dec.name().id, dec); + } + } + Set initialized = new HashSet<>(); + for (FunctionDef fDef : methodDeclarations) { + if (!fDef.isConstructor() && !Utils.isSuperConstructor(fDef.getName())) continue; + for (Statement stmt : fDef.body.statements()) { + if (stmt instanceof Assign a && a.target instanceof Name n + && finalFields.containsKey(n.id)) { + if (!initialized.add(n.id)) { + throw new SemanticException("final field " + n.id + + " is initialized more than once", a.location); + } + a.markFinalFieldInit(); + } + } + } + for (Map.Entry e : finalFields.entrySet()) { + if (!initialized.contains(e.getKey())) { + throw new SemanticException("final field " + e.getKey() + + " is not initialized by a top-level statement of the constructor", + e.getValue().location); + } + } + } + @Override public void globalInfoVisit(InterfaceSym contractSym) throws SemanticException { // contractSym.name = contractName; @@ -207,6 +244,9 @@ public void globalInfoVisit(InterfaceSym contractSym) throws SemanticException { for (StateVariableDeclaration dec : varDeclarations) { dec.globalInfoVisit(contractSym); } + for (StructDef def: structDefs) { + def.resolveMemberLabels(contractSym); + } trustSetting.globalInfoVisit(contractSym); @@ -628,7 +668,7 @@ public void codePasteContract(Map contractMap, Map solidityCodeGen(CompileEnv code) { @Override public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws SemanticException { - // TODO placeholder + Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); - return new PathOutcome(new PsiUnit(endContext)); + EventTypeSym eventTypeSym = (EventTypeSym) env.getCurSym(((Name) event.value).id); + + // Evaluate the arguments from left to right. + PathOutcome psi = new PathOutcome(new PsiUnit(beginContext)); + List argValueLabelNames = new ArrayList<>(); + List argPrincipalNames = new ArrayList<>(); + for (Expression arg : event.args) { + ExpOutcome ao = arg.genIFConstraints(env, false); + psi.joinExe(ao.psi); + argValueLabelNames.add(ao.valueLabelName); + argPrincipalNames.add(arg.principalName(env)); + env.inContext = Utils.genNewContextAndConstraints(env, false, + ao.psi.getNormalPath().c, beginContext.lambda, arg.nextPcSHL(), arg.location); + } + + // A parameter label naming another parameter names the principal passed for it. + Map dependentLabelMapping = new HashMap<>(); + for (int i = 0; i < event.args.size(); i++) { + VarSym param = eventTypeSym.parameters().get(i); + if (param.isPrincipalTyped() && argPrincipalNames.get(i) != null) { + dependentLabelMapping.put(param.toSHErrLocFmt(), argPrincipalNames.get(i)); + } + } + + // Each argument, and the control flow that emits it, must be trusted at the + // parameter's label, as for the arguments of a call. + String ifNamePc = env.inContext.pc; + for (int i = 0; i < event.args.size(); i++) { + Expression arg = event.args.get(i); + String paramLabel = eventTypeSym.parameters().get(i).ifl.toSHErrLocFmt(dependentLabelMapping); + env.checkFlow(argValueLabelNames.get(i), paramLabel, arg.location, + "Input to the " + Utils.ordNumString(i + 1) + + " argument of the event must be trusted enough"); + env.checkFlow(ifNamePc, paramLabel, arg.location, + "Current control flow must be trusted to emit the " + + Utils.ordNumString(i + 1) + " argument of the event"); + } + + Utils.contextFlow(env, env.inContext, endContext, location); + if (!tail_position) { + env.checkFlow(endContext.lambda, beginContext.lambda, location, + Utils.ERROR_MESSAGE_LOCK_IN_NONLAST_OPERATION); + } + psi.setNormalPath(endContext); + return psi; } @Override diff --git a/src/ast/Endorse.java b/src/ast/Endorse.java index a6e6254..82a9a79 100644 --- a/src/ast/Endorse.java +++ b/src/ast/Endorse.java @@ -30,23 +30,19 @@ public Endorse(Expression value, IfLabel from, IfLabel to) { } public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { + from.genTypeConstraints(env, parent); + to.genTypeConstraints(env, parent); return value.genTypeConstraints(env, parent); } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { - //TODO: change to conditioned form - Context beginContext = env.inContext; - Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), - typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { ExpOutcome vo = value.genIFConstraints(env, tail_position); String ifNameValue = vo.valueLabelName; String ifNameRtn = scopeContext.getSHErrLocName() + "." + "endorse" + location.toString(); - String fromLabel = fromNameSLC(); - //from.toSherrlocFmt(scopeContext); - String toLabel = toNameSLC(); - //to.toSherrlocFmt(scopeContext); + String fromLabel = env.toLabel(from).toSHErrLocFmt(); + String toLabel = env.toLabel(to).toSHErrLocFmt(); env.checkFlow(ifNameValue, fromLabel, location, "The expression must be trusted to be endorsed"); @@ -54,10 +50,9 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws S env.checkFlow(toLabel, ifNameRtn, location, "The integrity level of this expression would be endorsed"); - //env.outContext = endContext; - - env.checkTrustedBy(beginContext.pc, endContext.pc, location, - "The control flow of this expression would be endorsed"); + // Code can endorse data only up to the integrity of its own control flow. + env.checkFlow(vo.psi.getNormalPath().c.pc, toLabel, location, + "Control flow must be trusted at the label this expression is endorsed to"); // if (value instanceof Name n && env.getVar(n.id) != null // && env.getVar(n.id).typeSym instanceof ClosureTypeSym cs) { @@ -84,13 +79,6 @@ public compile.ast.Expression solidityCodeGen(List result, CompileEnv return value.solidityCodeGen(result, code); } - private String fromNameSLC() { - return scopeContext + ".endorse" + "." + "from" + from.location; - } - private String toNameSLC() { - return scopeContext + ".endorse" + "." + "to" + from.location; - } - @Override public boolean typeMatch(Expression expression) { return expression instanceof Endorse && @@ -112,4 +100,9 @@ public ArrayList children() { public java.util.Map readMap(CompileEnv code) { return value.readMap(code); } + + @Override + protected TypeSym staticType(VisitEnv env) { + return value.type(); + } } diff --git a/src/ast/EndorseIfStatement.java b/src/ast/EndorseIfStatement.java index 79f0927..e057ef6 100644 --- a/src/ast/EndorseIfStatement.java +++ b/src/ast/EndorseIfStatement.java @@ -52,7 +52,7 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic String rtnValueLabel; ExpOutcome toOutCome = ifStatement.test.genIFConstraints(env, - ifStatement.body.size() == 0 && ifStatement.orelse.size() == 0 && tail_position); + ifStatement.body.isEmpty() && ifStatement.orelse.isEmpty() && tail_position); //Context condContext = to.psi rtnValueLabel = toOutCome.valueLabelName; String IfNameTest = toOutCome.valueLabelName; @@ -75,8 +75,8 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // // logger.debug(l.toString()); // // logger.debug(r.toString()); createdHypo = true; - Inequality hypo = new Inequality(l.toSHErrLocFmt(), Relation.LEQ, - r.toSHErrLocFmt()); + Inequality hypo = new Inequality(env.principalName(l), Relation.LEQ, + env.principalName(r)); env.hypothesis().add(hypo); // // logger.debug("testing label"); @@ -93,9 +93,10 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic String IfTestNormalPc = beginContext.pc; String IfTestNormalLambda = beginContext.lambda; - if (ifStatement.body.size() > 0 || ifStatement.orelse.size() > 0) { + if (!ifStatement.body.isEmpty() || !ifStatement.orelse.isEmpty()) { Utils.genSequenceConstraints(env, beginContext.lambda, IfTestNormalPc, IfTestNormalLambda, IfNamePcAfter, ifStatement.test.location); } + Map versionsBefore = env.saveVersions(); env.incScopeLayer(); Label fromLabel = env.toLabel(from); @@ -116,9 +117,9 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic newSym.ifl = toLabel; env.checkEqual(newSym.labelNameSLC(), newSym.labelValueSLC(), location, "Variable " + newSym.getName() + " may be endorsed incorrectly"); - if (sym.isPrincipalVar()) { - env.checkTrustEqual(sym.toSHErrLocFmt(), newSym.toSHErrLocFmt(), location, - "final variable " + newSym.getName() + " may be endorsed incorrectly"); + if (sym.isPrincipalTyped()) { + env.addPrincipal(newSym); + env.assumeSamePrincipal(env.principalName(sym), newSym.toSHErrLocFmt()); } try { env.addVar(newSym.getName(), newSym); @@ -129,9 +130,9 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // Context leftContext = new Context(curContext), rightContext = new Context(curContext); CodeLocation loc = null; - PathOutcome ifo = toOutCome.psi; + PathOutcome ifo = toOutCome.psi.withoutNormalPath(); env.inContext = new Context(IfNamePcAfter, beginContext.lambda); - Utils.genConsStmts(ifStatement.body, env, ifo, tail_position); + ifo.join(ifStatement.body.IFCVisit(env, tail_position)); // int index = 0; // for (Statement stmt : ifStatement.body) { @@ -157,11 +158,12 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // // logger.debug("finished if branch"); //System.err.println("finished if branch"); // env.prevContext.lambda = curContext.lambda; + Map versionsThen = env.saveVersions(); + env.restoreVersions(versionsBefore); env.incScopeLayer(); // index = 0; - PathOutcome elseo = toOutCome.psi; env.inContext = new Context(IfNamePcAfter, beginContext.lambda); - Utils.genConsStmts(ifStatement.orelse, env, elseo, tail_position); + PathOutcome elseo = ifStatement.orelse.IFCVisit(env, tail_position); // for (Statement stmt : ifStatement.orelse) { // ++index; // elseo = stmt.genConsVisit(env, index == ifStatement.orelse.size() && tail_position); @@ -173,6 +175,10 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // // env.prevContext.lambda = rightContext.lambda; // } env.decScopeLayer(); + env.mergeVersions(List.of(versionsThen, env.saveVersions())); + // Assignments in the then branch go to the endorsed copies, which share storage + // with the original variables. + env.havoc(Assign.assignedNames(ifStatement.body.statements())); ifo.join(elseo); // env.cons.add(new Constraint(new Inequality(IfNameLock, Relation.REQ, Utils.joinLabels(leftContext.lambda, rightContext.lambda)), env.hypothesis, location)); @@ -191,8 +197,8 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic @Override public List children() { ArrayList rtn = new ArrayList<>(); - rtn.addAll(ifStatement.body); - rtn.addAll(ifStatement.orelse); + rtn.add(ifStatement.body); + rtn.add(ifStatement.orelse); return rtn; } diff --git a/src/ast/EndorseStatement.java b/src/ast/EndorseStatement.java deleted file mode 100644 index bf3b175..0000000 --- a/src/ast/EndorseStatement.java +++ /dev/null @@ -1,89 +0,0 @@ -package ast; - -import compile.CompileEnv; -import compile.Utils; -import compile.ast.IfStatement; -import compile.ast.Literal; -import compile.ast.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.swing.plaf.nimbus.State; -import typecheck.NTCEnv; -import typecheck.PathOutcome; -import typecheck.ScopeContext; -import typecheck.VisitEnv; - -public class EndorseStatement extends Statement { - - List expressionList; - IfLabel from, to; - List body; - - public EndorseStatement(List expressionList, IfLabel from, IfLabel to, - List body) { - this.expressionList = expressionList; - this.from = from; - this.to = to; - this.body = body; - } - - public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) { - // TODO - return parent; - } - - @Override - public List solidityCodeGen(CompileEnv code) { - code.countEndorse(expressionList.size()); - List result = new ArrayList<>(); - for (Statement s: body) { - result.addAll(s.solidityCodeGen(code)); - } - return List.of(new IfStatement(new Literal(Utils.SOL_TRUE), result)); - } - - @Override - public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) { - //TODO - - return null; - } - - @Override - public ArrayList children() { - ArrayList rtn = new ArrayList<>(); - rtn.addAll(expressionList); - rtn.add(from); - rtn.add(to); - return rtn; - } - - @Override - public boolean exceptionHandlingFree() { - for (Statement s: body) { - if (!s.exceptionHandlingFree()) { - return false; - } - } - return true; - } - - @Override - protected java.util.Map readMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } - return result; - } - - protected Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } - return result; - } -} diff --git a/src/ast/ExceptHandler.java b/src/ast/ExceptHandler.java index 3a9262a..2de8879 100644 --- a/src/ast/ExceptHandler.java +++ b/src/ast/ExceptHandler.java @@ -19,13 +19,13 @@ public class ExceptHandler extends Node { private LabeledType labeledType; private String name; - private List body; + private Block body; final private boolean acceptall; public ExceptHandler(LabeledType type, String name, List body) { this.labeledType = type; this.name = name; - this.body = body; + this.body = new Block(body); acceptall = false; } @@ -33,13 +33,10 @@ public ExceptHandler(LabeledType type, String name, List body) { * Create a handler that accept all other types of exceptions(try/catch) or failures(atomic/rescue) */ public ExceptHandler(List body) { - this.body = body; + this.body = new Block(body); acceptall = true; } - public void setBody(List body) { - this.body = body; - } @Override public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { @@ -59,9 +56,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } } - for (Statement s : body) { - ScopeContext tmp = s.genTypeConstraints(env, now); - } + body.genTypeConstraints(env, now); env.exitNewScope(); return now; } @@ -69,9 +64,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S public IfStatement solidityCodeGen(CompileEnv code, Map writeMap, SingleVar dataVar) { List solBody = new ArrayList<>(); if (acceptall) { - for (Statement s: body) { - solBody.addAll(s.solidityCodeGen(code)); - } + solBody.addAll(body.solidityCodeGen(code)); return code.testNonNormalPath(solBody); } @@ -101,9 +94,7 @@ public IfStatement solidityCodeGen(CompileEnv code, Map children() { List rtn = new ArrayList<>(); rtn.add(labeledType); - rtn.addAll(body); + rtn.add(body); return rtn; } @@ -129,9 +120,11 @@ public PathOutcome genConsVisit(VisitEnv env, boolean tail_position) throws Sema Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); + if (!acceptall) { + declareCaughtVar(env, beginContext.pc); + } int index = 0; - PathOutcome so = new PathOutcome(new PsiUnit(beginContext)); - Utils.genConsStmts(body, env, so, tail_position); + PathOutcome so = body.IFCVisit(env, tail_position); // for (Statement stmt : body) { // ++index; // so = stmt.genConsVisit(env, index == body.size() && tail_position); @@ -149,23 +142,48 @@ public Type type() { return labeledType.type(); } + /** + * Declares this handler's variable in the current scope of {@code env}, bound by control + * flow at {@code handlerPc}. If the variable's type has a label, that label is its label, + * and the control flow must be trusted at it; otherwise its label is {@code handlerPc}. + * An exception has a single label, so its fields are read at the variable's label. + */ + private void declareCaughtVar(VisitEnv env, String handlerPc) throws SemanticException { + Label declared = labeledType.label() == null ? null : env.toLabel(labeledType.label()); + VarSym var = new VarSym(name, exceptionType(env), declared, location, scopeContext, + false, true, false); + try { + env.addVar(name, var); + } catch (SymTab.AlreadyDefined e) { + throw new SemanticException("Already defined: " + name, location); + } + if (declared == null) { + env.checkEqual(var.labelNameSLC(), handlerPc, location, + "Label of " + name + " may be incorrect"); + } else { + env.checkEqual(var.labelNameSLC(), declared.toSHErrLocFmt(), location, + "Label of " + name + " may be incorrect"); + env.checkFlow(handlerPc, declared.toSHErrLocFmt(), location, + "The caught exception must be trusted at the label of " + name); + } + } + public String name() { return name; } + /** + * The exception type this handler catches, or null if it catches every failure. + */ + public ExceptionTypeSym exceptionType(VisitEnv env) { + return acceptall ? null : env.getExp(labeledType.type().name()); + } + protected Map readMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } - return result; + return body.readMap(code); } protected Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } - return result; + return body.writeMap(code); } } diff --git a/src/ast/ExceptionDef.java b/src/ast/ExceptionDef.java index e53e392..c50c6b6 100644 --- a/src/ast/ExceptionDef.java +++ b/src/ast/ExceptionDef.java @@ -16,16 +16,14 @@ public class ExceptionDef extends TopLayerNode { String namespace; Arguments arguments; boolean isBuiltIn = false; - public ExceptionDef(String name, Arguments members) { - exceptionName = name; - this.arguments = members; - arguments.setToDefault(new PrimitiveIfLabel(new Name(Utils.LABEL_THIS))); + this(name, members, false); } public ExceptionDef(String name, Arguments members, Boolean isBuiltIn) { exceptionName = name; this.arguments = members; this.isBuiltIn = isBuiltIn; + // Like fields, unlabeled data is trusted by this contract. arguments.setToDefault(new PrimitiveIfLabel(new Name(Utils.LABEL_THIS))); } @@ -34,13 +32,27 @@ public void ntcGlobalInfo(NTCEnv env, ScopeContext parent) throws SemanticException { // exceptionType.setContractName(env.curContractSym().getName()); + ExceptionTypeSym expSym = env.newExceptionType(namespace, exceptionName, arguments, parent); try { - env.addSym(exceptionName, - env.newExceptionType(namespace, exceptionName, arguments, parent)); + env.addSym(exceptionName, expSym); } catch (SymTab.AlreadyDefined e) { throw new SemanticException("Exception already defined: " + exceptionName, location); } + // A parameter's label may name principal-typed parameters of the exception. + ScopeContext now = new ScopeContext(this, parent); + env.enterNewScope(); + try { + for (VarSym param : expSym.parameters()) { + env.addSym(param.getName(), param); + } + } catch (SymTab.AlreadyDefined e) { + throw new SemanticException("Parameter already defined: " + e.id, location); + } + for (Arg arg : arguments.args()) { + arg.annotation.label().genTypeConstraints(env, now); + } + env.exitNewScope(); } /** diff --git a/src/ast/Expression.java b/src/ast/Expression.java index 309301a..e612d77 100644 --- a/src/ast/Expression.java +++ b/src/ast/Expression.java @@ -31,9 +31,88 @@ public VarSym getVarInfo(NTCEnv env) throws SemanticException { // return "unknown"; // } - public abstract ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) + /** The static type of this expression, once it has been checked for information flow. */ + private TypeSym type; + + /** + * Generates the information-flow constraints for this expression, evaluated in the context + * {@code env.inContext}, and returns the label of its value and the paths by which its + * evaluation can end. Afterwards {@link #type()} is defined. + */ + public final ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) + throws SemanticException { + ExpOutcome outcome = genFlowConstraints(env, tail_position); + type = staticType(env); + if (type == null) { + throw new AssertionError("no static type for " + getClass().getSimpleName() + + " at " + location); + } + return outcome; + } + + /** + * Generates the information-flow constraints for this expression, as specified for + * {@link #genIFConstraints}. + */ + protected abstract ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException; + /** + * The static type of this expression in {@code env}, called once its information-flow + * constraints have been generated in {@code env}. + */ + protected abstract TypeSym staticType(VisitEnv env) throws SemanticException; + + /** + * Generates the information-flow constraints for evaluating this expression as the target + * of an assignment, in the context {@code env.inContext}. Returns the label of the location + * it denotes, which a value must flow to in order to be stored there, with the paths by + * which the evaluation can end. Afterwards {@link #type()} is defined. + * + * @throws SemanticException if this expression does not denote a location + */ + public final ExpOutcome genLocationConstraints(VisitEnv env) throws SemanticException { + ExpOutcome outcome = genLocationFlow(env); + type = staticType(env); + if (type == null) { + throw new AssertionError("no static type for " + getClass().getSimpleName() + + " at " + location); + } + return outcome; + } + + /** + * Generates the information-flow constraints for this expression as the target of an + * assignment, as specified for {@link #genLocationConstraints}. + * + * @throws SemanticException if this expression does not denote a location + */ + protected ExpOutcome genLocationFlow(VisitEnv env) throws SemanticException { + throw new SemanticException("not an assignable location", location); + } + + /** + * The static type of this expression. + * + * @throws AssertionError if this expression has not been checked for information flow + */ + public TypeSym type() { + if (type == null) { + throw new AssertionError("no static type for " + getClass().getSimpleName() + + " at " + location); + } + return type; + } + + /** + * If this expression is a principal-typed variable, or a cast of one to a contract or + * address type, the SHErrLoc name of the principal it holds at the current program point. + * Otherwise null. + */ + public String principalName(VisitEnv env) { + return null; + } + /* Check whether there are non-exception-free calls inside the expression. If so, create temporary variables and statements to eventually evaluate the expression. diff --git a/src/ast/FlowsToExp.java b/src/ast/FlowsToExp.java index f3b88dd..b52bcbb 100644 --- a/src/ast/FlowsToExp.java +++ b/src/ast/FlowsToExp.java @@ -14,6 +14,7 @@ import typecheck.ExpOutcome; import typecheck.NTCEnv; import typecheck.ScopeContext; +import typecheck.TypeSym; import typecheck.VarSym; import typecheck.VisitEnv; import typecheck.exceptions.SemanticException; @@ -28,7 +29,8 @@ public FlowsToExp(Name a, Name b) { } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) + throws SemanticException { Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); @@ -82,9 +84,9 @@ public boolean typeMatch(Expression expression) { public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { ScopeContext now = new ScopeContext(this, parent); for (Name operand : List.of((Name) lhs, (Name) rhs)) { - if (!(env.getCurSym(operand.id) instanceof VarSym v) || !v.isPrincipalVar()) { + if (!(env.getCurSym(operand.id) instanceof VarSym v) || !v.isPrincipalTyped()) { throw new SemanticException( - "dynamic trust queries can only happen between final address variables: " + "dynamic trust queries can only happen between address or contract variables: " + operand.id, location); } } @@ -105,4 +107,9 @@ public java.util.Map readMap(CompileEnv code) { result.putAll(rhs.readMap(code)); return result; } + + @Override + protected TypeSym staticType(VisitEnv env) { + return env.builtinType(BuiltInT.BOOL); + } } diff --git a/src/ast/For.java b/src/ast/For.java index 7688fab..74e5484 100644 --- a/src/ast/For.java +++ b/src/ast/For.java @@ -19,14 +19,14 @@ public class For extends Statement { AnnAssign newVars; Expression test; - List iter; - List body; + Block iter; + Block body; public For(AnnAssign newVars, Expression test, List iter, List body) { this.newVars = newVars; this.test = test; - this.iter = iter; - this.body = body; + this.iter = new Block(iter); + this.body = new Block(body); } public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { @@ -40,12 +40,8 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S // the test condition must be boolean env.addCons(rtn.genTypeConstraints(Utils.BuiltinType2ID(BuiltInT.BOOL), Relation.EQ, env, location)); - for (Statement s: body) { - s.genTypeConstraints(env, now); - } - for (Statement s: iter) { - s.genTypeConstraints(env, now); - } + body.genTypeConstraints(env, now); + iter.genTypeConstraints(env, now); env.exitNewScope(); @@ -61,6 +57,9 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic env.incScopeLayer(); PathOutcome newVarOut = newVars.IFCVisit(env, false); beginContext = newVarOut.getNormalPath().c; + // The loop head is reached from before the loop and from the end of an iteration. + java.util.Set assigned = Assign.assignedNames(List.of(body, iter)); + env.havoc(assigned); ExpOutcome to = test.genIFConstraints(env, false); String ifNameTest = to.valueLabelName; @@ -76,26 +75,21 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic env.inContext = new Context(ifNamePcAfter, beginContext.lambda); CodeLocation loc = null; - PathOutcome ifo = to.psi; - for (Statement stmt : body) { - ifo = stmt.IFCVisit(env, false); - - PsiUnit normalUnit = ifo.getNormalPath(); - if (normalUnit == null) break; - - env.inContext = normalUnit.c; - loc = stmt.location; - } - for (Statement s: iter) { - ifo = s.IFCVisit(env, false); - - PsiUnit normalUnit = ifo.getNormalPath(); - if (normalUnit == null) break; - - env.inContext = normalUnit.c; - loc = s.location; + List iteration = new ArrayList<>(body.statements()); + iteration.addAll(iter.statements()); + PathOutcome iterOutcome = new Block(iteration).IFCVisit(env, false); + // The loop exits only from its head, which is also reached from the end of an + // iteration. + PathOutcome ifo = to.psi.withoutNormalPath(); + ifo.join(iterOutcome.withoutNormalPath()); + if (iterOutcome.getNormalPath() instanceof PsiUnit iterEnd) { + env.checkFlow(iterEnd.c.pc, ifNamePcAfter, location, + "Control flow at the end of the loop body contributes to the control flow of this for statement"); } + ifo.setNormalPath(new Context(ifNamePcAfter, to.psi.getNormalPath().c.lambda)); + // The loop exits from its head or from a break in the body. + env.havoc(assigned); env.decScopeLayer(); return ifo; @@ -120,18 +114,13 @@ public ArrayList children() { ArrayList rtn = new ArrayList<>(); rtn.add(newVars); rtn.add(test); - rtn.addAll(iter); - rtn.addAll(body); + rtn.add(iter); + rtn.add(body); return rtn; } @Override public boolean exceptionHandlingFree() { - for (Statement s: body) { - if (!s.exceptionHandlingFree()) { - return false; - } - } - return true; + return body.exceptionHandlingFree(); } } diff --git a/src/ast/FunctionDef.java b/src/ast/FunctionDef.java index bd382dc..c3451e9 100644 --- a/src/ast/FunctionDef.java +++ b/src/ast/FunctionDef.java @@ -28,24 +28,24 @@ public class FunctionDef extends FunctionSig { private static boolean DEBUG = false; - List body; + Block body; public FunctionDef(String name, FuncLabels funcLabels, Arguments args, List body, List decoratorList, LabeledType rtn, boolean isConstructor, CodeLocation location) { super(name, funcLabels, args, decoratorList, rtn, isConstructor, location); - this.body = body; + this.body = new Block(body); } public FunctionDef(String name, FuncLabels funcLabels, Arguments args, List body, List decoratorList, LabeledType rtn, boolean isConstructor, boolean isBuiltIn, CodeLocation location) { super(name, funcLabels, args, decoratorList, rtn, isConstructor, isBuiltIn, location); - this.body = body; + this.body = new Block(body); } public FunctionDef(FunctionSig funcSig, List body) { super(funcSig); - this.body = body; + this.body = new Block(body); } @@ -56,10 +56,10 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S env.enterNewScope(); // add args to local sym; String funcName = this.name; - if (DEBUG) System.err.println("entering method: " + name + " " + body.size()); + if (DEBUG) System.err.println("entering method: " + name + " " + body.statements().size()); FuncSym funcSym = ((FuncSym) env.getCurSym(funcName)); Map exceptionTypeSyms = new HashMap<>(); - for (Map.Entry t : funcSym.exceptions.entrySet()) { + for (Map.Entry t : funcSym.exceptions.entrySet()) { exceptionTypeSyms.put(t.getKey(), true); } @@ -89,10 +89,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } //if (!isBuiltIn()) { // TODO: add support for signatures - for (Statement stmt : body) { - // // logger.debug("stmt: " + stmt); - stmt.genTypeConstraints(env, now); - } + body.genTypeConstraints(env, now); if (isConstructor()) { // TODO(steph): fix assert if (!env.superCalled()) throw new SemanticException("constructor of super contract is not called in the constructor of " + env.currentSourceFileFullName(), location); @@ -115,13 +112,17 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_info) throws SemanticExce if (!returnVoid()) { addBuiltInResult(env.curSymTab, scopeContext); } - VarSym sender = env.getVar(Utils.LABEL_SENDER); - env.checkEqual(sender.labelNameSLC(), sender.labelValueSLC(), location, - "Label of sender may be incorrect"); + List builtInNames = new ArrayList<>(List.of(Utils.LABEL_SENDER, Utils.LABEL_PAYVALUE)); + builtInNames.addAll(Utils.BUILTIN_INMETHOD_VARS.keySet()); + for (String builtInName : builtInNames) { + VarSym builtIn = env.getVar(builtInName); + env.checkEqual(builtIn.labelNameSLC(), builtIn.labelValueSLC(), location, + "Label of " + builtInName + " may be incorrect"); + } for (Entry entry: env.curSymTab.getVars().entrySet()) { VarSym varSym = entry.getValue(); - if (varSym.isFinal && (varSym.typeSym instanceof ContractSym || varSym.typeSym.getName().equals(Utils.ADDRESS_TYPE))) { + if (varSym.isPrincipalTyped()) { env.addPrincipal(varSym); } } @@ -154,13 +155,6 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_info) throws SemanticExce funcLabels.gamma_label.location, "This function does not maintain reentrancy locks as specified in signature"); - HashMap psi = new HashMap<>(); - for (Map.Entry exp : funcSym.exceptions.entrySet()) { - psi.put(exp.getKey(), new PsiUnit( - //new Context(funcSym.getLabelNameException(exp.getKey()), ifNameGamma), true)); - new Context(exp.getKey().labelNameSLC(), ifNameGamma), true)); - } - Context funcBeginContext = curContext; PsiUnit funcEndContext = new PsiUnit(outPcName, outLockName); // env.inContext = funcBeginContext; @@ -168,31 +162,33 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_info) throws SemanticExce args.genConsVisit(env, false); // Context prev = new Context(env.prevContext);//, prev2 = null; - CodeLocation loc = null; - PathOutcome CO = null; env.inContext = funcBeginContext; -// Utils.genConsStatments(body, env, CO, true); - int index = 0; - for (Statement stmt : body) { - // Context CO = new Context(Utils.getLabelNamePc(stmt.location), Utils.getLabelNameLock(stmt.location)); - // env.outContext = CO; - ++index; - String prevLambda = env.inContext.lambda; - boolean isTail = index == body.size() && tail_info; - CO = stmt.IFCVisit(env, isTail); - //Context CO = env.outContext; - if (CO.existsNormalPath()) { - env.inContext = Utils.genNewContextAndConstraints(env, isTail, CO.getNormalPath().c, prevLambda, stmt.nextPcSHL(), stmt.location()); - -// env.inContext = new Context(CO.getNormalPath().c()); - } else { - break; - } - } - if (body.size() > 0 && CO.existsNormalPath()) { + PathOutcome CO = body.IFCVisit(env, tail_info); + PathOutcome escaping = CO.withoutNormalPath(); // exception paths leaving the body + escaping.psi.remove(Utils.getReturnPathException()); + if (!body.isEmpty() && CO.existsNormalPath()) { if (DEBUG) System.err.println("method: " + funcLocalName); + List stmts = body.statements(); Utils.contextFlow(env, CO.getNormalPath().c(), funcEndContext.c(), - body.get(body.size() - 1).location); + stmts.get(stmts.size() - 1).location); + } + + // Each exception that escapes must be trusted at its declared label, and must + // maintain the declared locks. + for (Map.Entry exp : escaping.psi.entrySet()) { + Label declared = funcSym.exceptions.get(exp.getKey()); + if (declared == null) { + throw new AssertionError("undeclared exception escapes " + funcLocalName + + ": " + exp.getKey().getName()); + } + Context c = exp.getValue().c; + env.checkFlow(c.pc, declared.toSHErrLocFmt(), declared.location(), + "Control flow that throws " + exp.getKey().getName() + + " must be trusted at its declared label"); + env.checkFlow(Utils.joinLabels(inLockName, c.lambda), ifNameGamma, + funcLabels.gamma_label.location, + "This function does not maintain reentrancy locks as specified in signature" + + " when it throws " + exp.getKey().getName()); } env.decScopeLayer(); @@ -236,9 +232,7 @@ public Function solidityCodeGen(CompileEnv code) { List arguments = args.solidityCodeGen(code); List statements = new ArrayList<>(); compile.Utils.addBuiltInVars(true, statements, code); - for (Statement s: body) { - statements.addAll(s.solidityCodeGen(code)); - } + statements.addAll(body.solidityCodeGen(code)); code.exitVarScope(); code.popScope(); return new Constructor(arguments, statements); @@ -275,10 +269,7 @@ public Function solidityCodeGen(CompileEnv code) { wapperStatements.addAll(code.enterFuncCheck(funcLabels, args)); // statements.addAll(code.enterFuncCheck(funcLabels, args)); } - for (Statement s: body) { - List tmp = s.solidityCodeGen(code); - statements.addAll(tmp); - } + statements.addAll(body.solidityCodeGen(code)); if (!exceptionFree()) { statements.addAll(code.compileReturn(rtn.type().isVoid() ? null : new SingleVar( @@ -323,23 +314,19 @@ public Function solidityCodeGen(CompileEnv code) { } private boolean exceptionHandlingFree() { - if (!exceptionFree()) return false; - for (Statement s: body) { - if (!s.exceptionHandlingFree()) return false; - } - return true; + return exceptionFree() && body.exceptionHandlingFree(); } @Override public List children() { List rtn = super.children(); - rtn.addAll(body); + rtn.add(body); return rtn; } public void renameSuperCall(String extendsContractName) { List newBody = new ArrayList<>(); - for (Statement s: body) { + for (Statement s: body.statements()) { if (s instanceof ast.CallStatement callStatement) { CallStatement newS = new CallStatement(callStatement); newS.call.checkAndRenameSuper(extendsContractName); diff --git a/src/ast/FunctionSig.java b/src/ast/FunctionSig.java index 0927196..ee14859 100644 --- a/src/ast/FunctionSig.java +++ b/src/ast/FunctionSig.java @@ -202,13 +202,13 @@ public void ntcGlobalInfo(NTCEnv env, ScopeContext parent) throws SemanticExcept VarSym sender = (VarSym) env.getCurSym(typecheck.Utils.LABEL_SENDER); List argsInfo = args.parseArgs(env, now); - Map exceptions = new HashMap<>(); + Map exceptions = new HashMap<>(); for (LabeledType t : exceptionList) { ExceptionTypeSym t1 = env.getExceptionTypeSym(t.type()); if (t1 == null) { throw new SemanticException("exception not found: " + t.type().name, t.location); } - exceptions.put(t1, null); + exceptions.put(t1, env.newLabel(t.label())); } funcSym = new FuncSym(name, isPublic, @@ -245,16 +245,14 @@ public void globalInfoVisit(InterfaceSym contractSym) throws SemanticException { if (rtn != null) { ifl = contractSym.newLabel(rtn.label()); } - Map exceptions = new HashMap<>(); + Map exceptions = new HashMap<>(); for (LabeledType t : exceptionList) { IfLabel label = null; /*if (t.type instanceof LabeledType) label = ((LabeledType) t.type).ifl;*/ ExceptionTypeSym exceptionTypeSym = contractSym.getExceptionSym(t.type().name); assert exceptionTypeSym != null; - exceptions.put(exceptionTypeSym, - typecheck.Utils.getLabelNameFuncExpLabel(scopeContext.getSHErrLocName(), - t.type().name())); + exceptions.put(exceptionTypeSym, contractSym.newLabel(t.label())); } // System.err.println("adding method: " + name); funcSym = @@ -409,10 +407,13 @@ public boolean isBuiltIn() { protected void addBuiltInVars(SymTab curSymTab, ScopeContext now) { - // final address{TOP} sender; - // The address is trusted data because the runtime authenticates it; - // the contract it names is not thereby trusted. + // final address{TOP} sender; final uint{TOP} value; final uint{TOP} now; + // These values are established by the runtime, so they are trusted data. + // The contract that sender names is not thereby trusted. try { + PrimitiveLabel labelTop = new PrimitiveLabel( + (VarSym) curSymTab.lookup(typecheck.Utils.LABEL_TOP), + typecheck.Utils.BUILTIN_LOCATION); VarSym varSender = new VarSym( typecheck.Utils.LABEL_SENDER, @@ -424,31 +425,26 @@ protected void addBuiltInVars(SymTab curSymTab, ScopeContext now) { true, true ); - PrimitiveLabel labelSender = new PrimitiveLabel(varSender, typecheck.Utils.BUILTIN_LOCATION); - varSender.setLabel(new PrimitiveLabel( - (VarSym) curSymTab.lookup(typecheck.Utils.LABEL_TOP), - typecheck.Utils.BUILTIN_LOCATION)); + varSender.setLabel(labelTop); curSymTab.add(typecheck.Utils.LABEL_SENDER, varSender); - // final uint{sender} value; curSymTab.add(typecheck.Utils.LABEL_PAYVALUE, new VarSym( typecheck.Utils.LABEL_PAYVALUE, (TypeSym) curSymTab.lookup(typecheck.Utils.BuiltinType2ID(BuiltInT.UINT)), - labelSender, + labelTop, typecheck.Utils.BUILTIN_LOCATION, now, true, true, true )); - // final uint{sender} value; // other built-in vars for (Entry entry : typecheck.Utils.BUILTIN_INMETHOD_VARS.entrySet()) { String varName = entry.getKey(); String typeName = entry.getValue(); - VarSym varSym = new VarSym(varName, (TypeSym) curSymTab.lookup(typeName), labelSender, + VarSym varSym = new VarSym(varName, (TypeSym) curSymTab.lookup(typeName), labelTop, typecheck.Utils.BUILTIN_LOCATION, now, true, true, true); curSymTab.add(varName, varSym); } diff --git a/src/ast/GuardBlock.java b/src/ast/GuardBlock.java index 291c5e7..e21d4e2 100644 --- a/src/ast/GuardBlock.java +++ b/src/ast/GuardBlock.java @@ -15,12 +15,12 @@ public class GuardBlock extends Statement { PrimitiveIfLabel l; - List body; + Block body; Expression target; public GuardBlock(PrimitiveIfLabel l, List body, Expression target) { this.l = l; - this.body = body; + this.body = new Block(body); this.target = target; } @@ -30,10 +30,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S ScopeContext now = new ScopeContext(this, parent); env.enterNewScope(); - ScopeContext rtn = null; - for (Statement s : body) { - rtn = s.genTypeConstraints(env, now); - } + body.genTypeConstraints(env, now); env.exitNewScope(); return now; @@ -54,20 +51,17 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // env.prevContext.lambda = newLockLabel; - Label label = env.curContractSym().newLabel(l); + Label label = env.toLabel(l); String guardLabel = label.toSHErrLocFmt(); env.checkFlow(Utils.meetLabels(guardLabel, curContext.lambda), beginContext.lambda, location, "Cannot grant a dynamic reentrancy lock"); // String newAfterLockLabel = Utils.getLabelNameLock(scopeContext.getSHErrLocName() + ".after"); - PathOutcome psi = new PathOutcome(new PsiUnit(curContext)); - //Context lastContext = new Context(curContext);//, prev2 = null; env.incScopeLayer(); - PathOutcome so = null; - env.inContext = psi.getNormalPath().c(); - Utils.genConsStmtsWithException(body, env, so, psi, tail_position); + env.inContext = curContext; + PathOutcome psi = body.IFCVisit(env, tail_position); // int index = 0; // for (Statement stmt : body) { // ++index; @@ -147,9 +141,7 @@ public List solidityCodeGen(CompileEnv code) { code.enterNewVarScope(); String lockP = l.value().id; result.add(new Assert(code.lock(lockP))); - for (Statement stmt : body) { - result.addAll(stmt.solidityCodeGen(code)); - } + result.addAll(body.solidityCodeGen(code)); result.add(new Assert(code.unlock(lockP))); code.exitVarScope(); return result; @@ -167,7 +159,7 @@ public void passScopeContext(ScopeContext parent) { public ArrayList children() { ArrayList rtn = new ArrayList<>(); rtn.add(l); - rtn.addAll(body); + rtn.add(body); if (target != null) { rtn.add(target); } @@ -175,29 +167,16 @@ public ArrayList children() { } @Override public boolean exceptionHandlingFree() { - for (Statement s: body) { - if (!s.exceptionHandlingFree()) { - return false; - } - } - return true; + return body.exceptionHandlingFree(); } @Override protected java.util.Map readMap(CompileEnv code) { - java.util.Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } - return result; + return body.readMap(code); } @Override protected java.util.Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } - return result; + return body.writeMap(code); } } diff --git a/src/ast/Hole.java b/src/ast/Hole.java index 5ed8efd..3e56634 100644 --- a/src/ast/Hole.java +++ b/src/ast/Hole.java @@ -7,6 +7,7 @@ import typecheck.ExpOutcome; import typecheck.NTCEnv; import typecheck.ScopeContext; +import typecheck.TypeSym; import typecheck.VisitEnv; import typecheck.exceptions.SemanticException; @@ -25,7 +26,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { throw new UnsupportedOperationException( "hole `_` has no IFC constraints outside closure creation"); @@ -46,4 +47,9 @@ public boolean typeMatch(Expression expression) { public List children() { return new ArrayList<>(); } + + @Override + protected TypeSym staticType(VisitEnv env) { + throw new UnsupportedOperationException("hole `_` has no type outside closure creation"); + } } diff --git a/src/ast/If.java b/src/ast/If.java index 3e29926..a77f147 100644 --- a/src/ast/If.java +++ b/src/ast/If.java @@ -18,19 +18,19 @@ public class If extends Statement { Expression test; - List body; - List orelse; + Block body; + Block orelse; public If(Expression test, List body, List orelse) { this.test = test; - this.body = body; - this.orelse = orelse; + this.body = new Block(body); + this.orelse = new Block(orelse); } public If(Expression test, List body) { this.test = test; - this.body = body; - this.orelse = new ArrayList<>(); + this.body = new Block(body); + this.orelse = new Block(new ArrayList<>()); } public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { @@ -42,14 +42,13 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S env.addCons(testCon); env.enterNewScope(); - for (Statement s : body) { - assert !now.getSHErrLocName().startsWith("null"): now.getSHErrLocName(); - rtn = s.genTypeConstraints(env, now); + if (!body.isEmpty()) { + rtn = body.genTypeConstraints(env, now); } env.exitNewScope(); env.enterNewScope(); - for (Statement s : orelse) { - rtn = s.genTypeConstraints(env, now); + if (!orelse.isEmpty()) { + rtn = orelse.genTypeConstraints(env, now); } env.exitNewScope(); env.addCons(now.genTypeConstraints(rtn, Relation.EQ, env, location)); @@ -67,7 +66,7 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic String rtnValueLabel; ExpOutcome to = test.genIFConstraints(env, - body.size() == 0 && orelse.size() == 0 && tail_position); + body.isEmpty() && orelse.isEmpty() && tail_position); //Context condContext = to.psi rtnValueLabel = to.valueLabelName; String IfNameTest = to.valueLabelName; @@ -90,8 +89,8 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // // logger.debug(l.toString()); // // logger.debug(r.toString()); createdHypo = true; - Inequality hypo = new Inequality(l.toSHErrLocFmt(), Relation.LEQ, - r.toSHErrLocFmt()); + Inequality hypo = new Inequality(env.principalName(l), Relation.LEQ, + env.principalName(r)); env.hypothesis().add(hypo); // logger.debug("testing label"); @@ -102,33 +101,19 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic env.checkFlow(IfNameTest, IfNamePcAfter, location, "Integrity of this condition doesn't flow to the control flow in this if branch"); - if (body.size() > 0 || orelse.size() > 0) { + if (!body.isEmpty() || !orelse.isEmpty()) { Utils.genSequenceConstraints(env, beginContext.lambda, IfTestNormalPc, IfTestNormalLambda, IfNamePcAfter, test.location); // env.cons.add(new Constraint(new Inequality(IfTestNormalPc, IfNamePcAfter), env.hypothesis(), test.location, env.curContractSym().getName(), // Utils.ERROR_MESSAGE_LOCK_IN_NONLAST_OPERATION)); } + Map versionsBefore = env.saveVersions(); env.incScopeLayer(); // Context leftContext = new Context(curContext), rightContext = new Context(curContext); CodeLocation loc = null; - PathOutcome ifo = to.psi; + PathOutcome ifo = to.psi.withoutNormalPath(); env.inContext = new Context(IfNamePcAfter, beginContext.lambda); - int index = 0; - for (Statement stmt : body) { - ++index; - String prevLambda = env.inContext.lambda; - ifo = stmt.IFCVisit(env, index == body.size() && tail_position); - // env.prevContext = tmp; - // prev2 = leftContext; - //env.context = context; - PsiUnit normalUnit = ifo.getNormalPath(); - if (normalUnit == null) { - break; - } - env.inContext = Utils.genNewContextAndConstraints(env, index == body.size() && tail_position, normalUnit.c, prevLambda, stmt.nextPcSHL(), stmt.location); - loc = stmt.location; - // leftContext = new Context(tmp); - } + ifo.join(body.IFCVisit(env, tail_position)); env.decScopeLayer(); if (createdHypo) { @@ -138,22 +123,13 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // logger.debug("finished if branch"); //System.err.println("finished if branch"); // env.prevContext.lambda = curContext.lambda; + Map versionsThen = env.saveVersions(); + env.restoreVersions(versionsBefore); env.incScopeLayer(); - index = 0; - PathOutcome elseo = to.psi; env.inContext = new Context(IfNamePcAfter, beginContext.lambda); - for (Statement stmt : orelse) { - ++index; - String prevLambda = env.inContext.lambda; - elseo = stmt.IFCVisit(env, index == orelse.size() && tail_position); - PsiUnit normalUnit = elseo.getNormalPath(); - if (normalUnit == null) { - break; - } - env.inContext = Utils.genNewContextAndConstraints(env, index == body.size() && tail_position, normalUnit.c, prevLambda, stmt.nextPcSHL(), stmt.location); - // env.prevContext.lambda = rightContext.lambda; - } + PathOutcome elseo = orelse.IFCVisit(env, tail_position); env.decScopeLayer(); + env.mergeVersions(List.of(versionsThen, env.saveVersions())); ifo.join(elseo); // env.cons.add(new Constraint(new Inequality(IfNameLock, Relation.REQ, Utils.joinLabels(leftContext.lambda, rightContext.lambda)), env.hypothesis, location)); @@ -174,16 +150,12 @@ public List solidityCodeGen(CompileEnv code) { List ifBranch = new ArrayList<>(); compile.ast.Expression cond = test.solidityCodeGen(result, code); code.enterNewVarScope(); - for (Statement stmt : body) { - ifBranch.addAll(stmt.solidityCodeGen(code)); - } + ifBranch.addAll(body.solidityCodeGen(code)); code.exitVarScope(); if (!orelse.isEmpty()) { List elseBranch = new ArrayList<>(); code.enterNewVarScope(); - for (Statement stmt : orelse) { - elseBranch.addAll(stmt.solidityCodeGen(code)); - } + elseBranch.addAll(orelse.solidityCodeGen(code)); code.exitVarScope(); result.add(new IfStatement(cond, ifBranch, elseBranch)); } else { @@ -204,46 +176,26 @@ public void passScopeContext(ScopeContext parent) { @Override public List children() { ArrayList rtn = new ArrayList<>(); - rtn.addAll(body); - rtn.addAll(orelse); + rtn.add(body); + rtn.add(orelse); return rtn; } @Override public boolean exceptionHandlingFree() { - for (Statement s: body) { - if (!s.exceptionHandlingFree()) { - return false; - } - } - for (Statement s: orelse) { - if (!s.exceptionHandlingFree()) { - return false; - } - } - return true; + return body.exceptionHandlingFree() && orelse.exceptionHandlingFree(); } @Override protected java.util.Map readMap(CompileEnv code) { - java.util.Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } - for (Statement s: orelse) { - result.putAll(s.readMap(code)); - } + java.util.Map result = new HashMap<>(body.readMap(code)); + result.putAll(orelse.readMap(code)); return result; } @Override protected java.util.Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } - for (Statement s: orelse) { - result.putAll(s.writeMap(code)); - } + Map result = new HashMap<>(body.writeMap(code)); + result.putAll(orelse.writeMap(code)); return result; } } diff --git a/src/ast/IfLabel.java b/src/ast/IfLabel.java index 55b6cb4..909ffbe 100644 --- a/src/ast/IfLabel.java +++ b/src/ast/IfLabel.java @@ -1,7 +1,9 @@ package ast; import java.util.Map; +import typecheck.BuiltInT; import typecheck.Label; +import typecheck.TypeSym; import typecheck.ScopeContext; import typecheck.VarSym; import typecheck.VisitEnv; @@ -33,4 +35,8 @@ public VarSym getVarInfo(VisitEnv env, boolean tail_position, Map children() { } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) { VarSym var = env.getVar(id); String ifNameRtn = var.labelNameSLC(); return new ExpOutcome(ifNameRtn, new PathOutcome(new PsiUnit(env.inContext))); } + @Override + public String principalName(VisitEnv env) { + if (env.getCurSym(id) instanceof VarSym v && v.isPrincipalTyped()) { + return env.principalName(v); + } + return null; + } + @Override public compile.ast.Expression solidityCodeGen(List result, CompileEnv code) { return new SingleVar(id); @@ -106,4 +114,15 @@ public java.util.Map readMap(CompileEnv code) { public Map writeMap(CompileEnv code) { return readMap(code); } + + @Override + protected TypeSym staticType(VisitEnv env) { + return env.getVar(id).typeSym; + } + + @Override + protected ExpOutcome genLocationFlow(VisitEnv env) { + return new ExpOutcome(env.getVar(id).labelNameSLC(), + new PathOutcome(new PsiUnit(env.inContext))); + } } diff --git a/src/ast/New.java b/src/ast/New.java index 74ac4f3..49c251c 100644 --- a/src/ast/New.java +++ b/src/ast/New.java @@ -28,6 +28,8 @@ public class New extends Expression { Call constructor_call; boolean isConstructor = false; + /** The principal of the contract this expression creates, once IFC-visited. */ + private String createdPrincipal = null; public New(Call constructor_call) { this.constructor_call = constructor_call; @@ -99,31 +101,51 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { + ExpOutcome result = newIFConstraints(env, tail_position); + // A contract's constructor may call back and assign to any field, so fields hold + // new principals afterwards. + if (isConstructor) { + env.havocFields(); + } + return result; + } + + @Override + public String principalName(VisitEnv env) { + return createdPrincipal; + } + + private ExpOutcome newIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { if (!isConstructor) { - // creating a struct var returns a join of its arg values + // Each member is checked like an argument of a call, against the member's label. + // The struct value itself is trusted as much as the control flow creating it. Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); - - List argValueLabelNames = new ArrayList<>(); - String ifNameRtn = scopeContext.getSHErrLocName() + "." + "newStruct" + location.toString(); + StructTypeSym structSym = + (StructTypeSym) env.getCurSym(((Name) constructor_call.value).id); PathOutcome psi = new PathOutcome(new PsiUnit(endContext)); ExpOutcome ao = null; - for (Expression arg : constructor_call.args) { + for (int i = 0; i < constructor_call.args.size(); ++i) { + Expression arg = constructor_call.args.get(i); ao = arg.genIFConstraints(env, false); psi.joinExe(ao.psi); - argValueLabelNames.add(ao.valueLabelName); env.inContext = new Context( Utils.joinLabels(ao.psi.getNormalPath().c.pc, beginContext.pc), beginContext.lambda); - env.checkTrustedBy(ao.valueLabelName, ifNameRtn, location, - "Integrity of left hand expression doesn't flow to value of this binary operation"); + String memberLabel = structSym.members().get(i).ifl.toSHErrLocFmt(); + env.checkFlow(ao.valueLabelName, memberLabel, arg.location, + "Input to member " + structSym.members().get(i).getName() + + " must be trusted at its label"); + env.checkFlow(env.inContext.pc, memberLabel, arg.location, + "Current control flow must be trusted to set member " + + structSym.members().get(i).getName()); } - return new ExpOutcome(ifNameRtn, psi); + return new ExpOutcome(env.inContext.pc, psi); } else { // creating contract requires pc and arguments to be of integirty level `this` Context beginContext = env.inContext; @@ -136,10 +158,12 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws S PathOutcome psi = new PathOutcome(new PsiUnit(endContext)); ExpOutcome ao = null; + List argPrincipalNames = new ArrayList<>(); for (Expression arg : constructor_call.args) { ao = arg.genIFConstraints(env, false); psi.joinExe(ao.psi); argValueLabelNames.add(ao.valueLabelName); + argPrincipalNames.add(arg.principalName(env)); env.inContext = new Context( Utils.joinLabels(ao.psi.getNormalPath().c.pc, beginContext.pc), beginContext.lambda); @@ -163,6 +187,12 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws S ifNamePc = Utils.getLabelNamePc(scopeContext.getSHErrLocName()); dependentLabelMapping.put(funcSym.sender().toSHErrLocFmt(), env.thisSym().toSHErrLocFmt()); + // The new contract is a principal about which nothing is known; in the + // constructor's labels, its `this` is that principal. + createdPrincipal = env.freshPrincipal(toSHErrLocFmt() + "..new"); + typecheck.InterfaceSym createdSym = + env.getContract(((Name) constructor_call.value).id); + dependentLabelMapping.put(createdSym.thisSym().toSHErrLocFmt(), createdPrincipal); ifFuncCallPcBefore = funcSym.externalPc(); ifFuncCallPcAfter = funcSym.internalPc(); @@ -171,15 +201,8 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws S for (int i = 0; i < constructor_call.args.size(); ++i) { Expression arg = constructor_call.args.get(i); VarSym argSym = funcSym.parameters.get(i); - if (argSym.isPrincipalVar()) { - - if (arg instanceof Name) { - VarSym valueSym = (VarSym) env.getVar(((Name) arg).id); - if (valueSym.isPrincipalVar()) { - dependentLabelMapping.put(argSym.toSHErrLocFmt(), - valueSym.toSHErrLocFmt()); - } - } + if (argSym.isPrincipalTyped() && argPrincipalNames.get(i) != null) { + dependentLabelMapping.put(argSym.toSHErrLocFmt(), argPrincipalNames.get(i)); } // env.prevContext = prevContext = tmp; @@ -234,11 +257,13 @@ public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws S typecheck.Utils.ERROR_MESSAGE_LOCK_IN_NONLAST_OPERATION); } - String ifNameFuncRtnValue = funcSym.rtn.toSHErrLocFmt(dependentLabelMapping); - // String ifNameFuncRtnLock = funcSym.getLabelNameRtnLock(); + // Creating a contract does not make it trusted: the reference is trusted + // no more than the new contract, and no more than the control flow that + // decided to create it. + String ifNameRtn = Utils.joinLabels(ifNamePc, createdPrincipal); psi.joinExe(expPsi); - return new ExpOutcome(ifNameFuncRtnValue, psi); + return new ExpOutcome(ifNameRtn, psi); } } @@ -263,4 +288,10 @@ public boolean typeMatch(Expression expression) { public List children() { return List.of(constructor_call); } + + @Override + protected TypeSym staticType(VisitEnv env) { + String name = ((Name) constructor_call.value).id; + return isConstructor ? env.getContract(name) : (StructTypeSym) env.getCurSym(name); + } } diff --git a/src/ast/PrimitiveIfLabel.java b/src/ast/PrimitiveIfLabel.java index a4f4a2f..a8f232b 100644 --- a/src/ast/PrimitiveIfLabel.java +++ b/src/ast/PrimitiveIfLabel.java @@ -107,7 +107,7 @@ public String toSherrlocFmtApply(HashSet strSet, int no) { } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) { return null; } diff --git a/src/ast/StateVariableDeclaration.java b/src/ast/StateVariableDeclaration.java index ff9f3dc..c36ec43 100644 --- a/src/ast/StateVariableDeclaration.java +++ b/src/ast/StateVariableDeclaration.java @@ -91,8 +91,16 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S if (value != null) { ScopeContext v = value.genTypeConstraints(env, now); env.addCons(tgt.genTypeConstraints(v, Relation.LEQ, env, location)); - } else if (isFinal && !isBuiltin) { - throw new SemanticException("final variable " + name.id + " not initialized", location); + } + // The label must mean the same thing wherever the field is used, so it may not + // name a principal that can change. + if (env.getCurSym(name.id) instanceof VarSym field && field.ifl != null) { + for (VarSym p : field.ifl.principalSyms()) { + if (p.isMutablePrincipal()) { + throw new SemanticException("the label of field " + name.id + + " names " + p.getName() + ", which is not final", field.ifl.location()); + } + } } return now; } @@ -127,45 +135,14 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic SLCNameVar = varSym.toSHErrLocFmt(); SLCNameVarLbl = varSym.labelNameSLC(); // logger.debug(varSym.typeSym.getName()); - if ((varSym.isFinal && - (varSym.typeSym instanceof ContractSym || varSym.typeSym.getName().equals(Utils.ADDRESS_TYPE)))) { + // A field initialized here starts out naming the principal it is initialized to. + // Only built-in fields have initializers; others are initialized in a constructor. + if (varSym.isPrincipalTyped()) { env.addPrincipal(varSym); - - VarSym equalPrincipal = null; - boolean correctInit = true; - if (value instanceof Name) { // assigned as another variable - // Nothing to check - equalPrincipal = env.getVar(((Name) value).id); - assert equalPrincipal != null : ((Name) value).id; - } else if (value instanceof Call && ((Call) value).isCast(env)) { // assigned as another contract - // check if it is a cast to a final address variable - Call cast = (Call) value; - Expression arg = cast.getArgAt(0); - if (arg instanceof Name) { - VarSym sym = env.getVar(((Name) arg).id); - if (!sym.isFinal) { - correctInit = false; - } else { - equalPrincipal = sym; - } - } else { - correctInit = false; - } - } else { - correctInit = false; - } - - - if (correctInit) { - // add equivalence assumption to the trust set - env.checkTrustEqual(SLCNameVar, equalPrincipal.toSHErrLocFmt(), location, - "New principal declaration"); - } else if (!isBuiltin) { - throw new SemanticException("A final address/Contract must be initialized to another final address/Contract: " + id, - location); + String init = value == null ? null : value.principalName(env); + if (init != null) { + env.assumeSamePrincipal(SLCNameVar, init); } - - } else if (varSym.typeSym.getName().equals(Utils.PRINCIPAL_TYPE)) { env.addPrincipal(varSym); } @@ -226,6 +203,11 @@ public Name name() { return name; } + /** Whether this field is final. */ + public boolean isFinal() { + return isFinal; + } + public boolean isBuiltIn() { return isBuiltin; } @@ -247,6 +229,11 @@ public VarDec solidityCodeGenStruct(CompileEnv code) { return new VarDec(varType, varName); } + /** The declared label of this variable. */ + IfLabel label() { + return type.label(); + } + public VarSym toVarInfo(InterfaceSym interfaceSym) throws SemanticException { IfLabel ifl = null; if (type != null) { diff --git a/src/ast/StructDef.java b/src/ast/StructDef.java index 0d27d17..24c8828 100644 --- a/src/ast/StructDef.java +++ b/src/ast/StructDef.java @@ -9,6 +9,7 @@ import typecheck.ScopeContext; import typecheck.StructTypeSym; import typecheck.Sym; +import typecheck.VarSym; import typecheck.exceptions.SemanticException; import java.util.ArrayList; @@ -34,6 +35,17 @@ public void globalInfoVisit(InterfaceSym contractSym) throws SemanticException { location); } + /** + * Resolves the labels of the members of this struct's type in {@code contractSym}, whose + * state variables, including the built-in principals, must already be declared. + */ + public void resolveMemberLabels(InterfaceSym contractSym) throws SemanticException { + StructTypeSym structSym = (StructTypeSym) contractSym.lookupSym(structName); + for (int i = 0; i < members.size(); ++i) { + structSym.members().get(i).setLabel(contractSym.newLabel(members.get(i).label())); + } + } + @Override public void ntcGlobalInfo(NTCEnv env, ScopeContext parent) throws SemanticException { if (members.isEmpty()) { @@ -46,9 +58,28 @@ public void ntcGlobalInfo(NTCEnv env, ScopeContext parent) throws SemanticExcept env.addType(structName, env.toStructType(structName, members)); } - public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) { -// assert false; - return null; + @Override + public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) + throws SemanticException { + ScopeContext now = new ScopeContext(this, parent); + for (StateVariableDeclaration member : members) { + member.label().genTypeConstraints(env, now); + // Like the label of a field, a member's label must mean the same thing wherever + // the struct is used, so it may not name a principal that can change. + checkNamesFinal(member.label(), env); + } + return now; + } + + private void checkNamesFinal(IfLabel label, NTCEnv env) throws SemanticException { + if (label instanceof ComplexIfLabel c) { + checkNamesFinal(c.getLeft(), env); + checkNamesFinal(c.getRight(), env); + } else if (label instanceof PrimitiveIfLabel p + && env.getCurSym(p.value().id) instanceof VarSym v && v.isMutablePrincipal()) { + throw new SemanticException("the label of a member of struct " + structName + + " names " + v.getName() + ", which is not final", label.location()); + } } public compile.ast.StructDef solidityCodeGen(CompileEnv code) { diff --git a/src/ast/Subscript.java b/src/ast/Subscript.java index 3ef6b4b..34163be 100644 --- a/src/ast/Subscript.java +++ b/src/ast/Subscript.java @@ -65,19 +65,12 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S value.genTypeConstraints(env, now); if (valueVarSym.typeSym instanceof DepMapTypeSym) { - // index must match, and must be a final address/contract or a principal - boolean validIndex = true; - if (index instanceof Name) { - VarSym indexSym = index.getVarInfo(env); - if (!indexSym.isPrincipalVar()) { - validIndex = false; - } - } else { - validIndex = false; - } - if (!validIndex) { + // The index must be a variable holding a principal, which the element's label + // may name. + if (!(index instanceof Name && index.getVarInfo(env) instanceof VarSym indexSym + && indexSym.isPrincipalTyped())) { throw new SemanticException( - "the index of a dependent map must be a final address or contract variable", + "the index of a dependent map must be an address or contract variable", index.location); } return now; @@ -99,58 +92,35 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { Context beginContext = env.inContext; Context endContext = new Context(typecheck.Utils.getLabelNamePc(toSHErrLocFmt()), typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); Map dependentMapping = new HashMap<>(); - VarSym valueVarSym = value.getVarInfo(env, false, dependentMapping); + ExpOutcome vo = value.genIFConstraints(env, false); + TypeSym collectionType = value.type(); - String ifNameValue = valueVarSym.labelNameSLC(); + String ifNameValue = vo.valueLabelName; // String ifNameRtnValue = ifNameValue + "." + "Subscript" + location.toString(); String ifNameRtnValue = toSHErrLocFmt(); ExpOutcome io = index.genIFConstraints(env, tail_position); // String ifNameRtnLock = ""; - if (valueVarSym.typeSym instanceof DepMapTypeSym depMapTypeSym) { - // value[index] where value is of dependent map type - // precondition: the type of index and value match; index is a final address/contract or a principal - // the result value of this expression has the label dependent to index - - // DepMapTypeSym typeSym = (DepMapTypeSym) valueVarSym.typeSym; - Map indexMapping = new HashMap<>(); - VarSym indexVarSym = index.getVarInfo(env, tail_position, indexMapping); - // logger.debug("subscript/DepMap:"); - // logger.debug("lookup at: " + index.toString()); - // logger.debug(indexVarSym.toString()); - String ifNameIndex = indexVarSym.toSHErrLocFmt(); - - if (indexVarSym.isPrincipalVar()) { -// // logger.debug("typename {} to {}", valueVarSym.typeSym.getName(), ifNameIndex); - System.err.println("typename " + depMapTypeSym.label() + " to " + ifNameIndex); - - dependentMapping.put(depMapTypeSym.key().toSHErrLocFmt(), ifNameIndex); - String ifDepMapValue = depMapTypeSym.label().toSHErrLocFmt(dependentMapping); -// String ifDepMapValue = depMapTypeSym.label().toSHErrLocFmt(depMapTypeSym.key().toSHErrLocFmt(), -// ifNameIndex); -// -// env.addTrustConstraint( -// new Constraint(new Inequality(ifDepMapValue, Relation.EQ, ifNameRtnValue), -// env.hypothesis(), location, env.curContractSym().getName(), -// "Integrity level of the subscripted value is not expected")); - return new ExpOutcome(ifDepMapValue, io.psi); - - } else { - throw new AssertionError(); - } + if (collectionType instanceof DepMapTypeSym depMapTypeSym) { + String elementLabel = elementLabel(depMapTypeSym, indexPrincipal(env)); + // Unless the element's label names its key, the index chooses which element is + // read, so the result is trusted no more than the index. + return new ExpOutcome(namesKey(depMapTypeSym) ? elementLabel + : Utils.joinLabels(elementLabel, io.valueLabelName), io.psi); } else { Context indexContext = io.psi.getNormalPath().c; String ifNameIndex = io.valueLabelName; //ifNameRtnValue = // scopeContext.getSHErrLocName() + "." + "Subscript" + location.toString(); - env.checkEqual(ifNameValue, ifNameRtnValue, location, + // The index chooses which element is read, so the result is trusted no more than it. + env.checkEqual(Utils.joinLabels(ifNameValue, ifNameIndex), ifNameRtnValue, location, "Integrity level of the subscript value is not trustworthy enough"); // env.cons.add(new Constraint(new Inequality(ifNameIndex, ifNameRtnValue), env.hypothesis, location)); @@ -173,37 +143,21 @@ public VarSym getVarInfo(VisitEnv env, boolean tail_position, Map indexMapping = new HashMap<>(); - VarSym indexVarSym = index.getVarInfo(env, tail_position, indexMapping); - String ifNameIndex = indexVarSym.toSHErrLocFmt(); - if (indexVarSym.isPrincipalVar()) { - - TypeSym rtnTypeSym = depMapTypeSym.valueType; - dependentMapping.put(depMapTypeSym.key().toSHErrLocFmt(), ifNameIndex); - rtnVarSym = new VarSym(ifNameRtn, rtnTypeSym, valueVarSym.ifl, location, - rtnTypeSym.defContext(), false, false, false); - env.checkFlow(depMapTypeSym.label().toSHErrLocFmt(dependentMapping), - rtnVarSym.labelNameSLC(), location, "Label of the subscript value"); - -// String ifDepMapValue = (valueVarSym).ifl.toSHErrLocFmt(valueVarSym.typeSym.name(), -// ifNameIndex); - -// env.cons.add( -// new Constraint(new Inequality(ifDepMapValue, ifNameRtn), env.hypothesis, -// location, env.curContractSym.name(), -// "Label of the subscript variable")); - - } else { -// logger.error(, -// locToString()); - throw new SemanticException( - "the index of a dependent map must be a final address variable", index.location); + TypeSym rtnTypeSym = depMapTypeSym.valueType; + dependentMapping.put(depMapTypeSym.key().toSHErrLocFmt(), indexPrincipal(env)); + rtnVarSym = new VarSym(ifNameRtn, rtnTypeSym, valueVarSym.ifl, location, + rtnTypeSym.defContext(), false, false, false); + env.checkFlow(elementLabel(depMapTypeSym, indexPrincipal(env)), + rtnVarSym.labelNameSLC(), location, "Label of the subscript value"); + if (!namesKey(depMapTypeSym)) { + env.checkFlow(index.genIFConstraints(env, tail_position).valueLabelName, + rtnVarSym.labelNameSLC(), location, "Label of the subscript index value"); } } else { String ifNameIndex = index.genIFConstraints(env, tail_position).valueLabelName; - TypeSym rtnTypeSym = new BuiltinTypeSym(ifNameRtn); + TypeSym rtnTypeSym = valueVarSym.typeSym instanceof ArrayTypeSym arrayType + ? arrayType.valueType : ((MapTypeSym) valueVarSym.typeSym).valueType; rtnVarSym = new VarSym(ifNameRtn, rtnTypeSym, valueVarSym.ifl, location, valueVarSym.defContext(), false, false, false); env.checkFlow(ifNameValue, rtnVarSym.labelNameSLC(), location, @@ -249,4 +203,68 @@ public java.util.Map readMap(CompileEnv code) { public Map writeMap(CompileEnv code) { return value.writeMap(code); } + + @Override + protected TypeSym staticType(VisitEnv env) { + TypeSym collectionType = value.type(); + if (collectionType instanceof ArrayTypeSym arrayType) { + return arrayType.valueType; + } else if (collectionType instanceof MapTypeSym mapType) { + return mapType.valueType; + } + throw new AssertionError("subscript of " + collectionType.getName()); + } + + @Override + protected ExpOutcome genLocationFlow(VisitEnv env) throws SemanticException { + ExpOutcome bo = value.genLocationConstraints(env); + TypeSym collectionType = value.type(); + ExpOutcome io = index.genIFConstraints(env, false); + PathOutcome psi = io.psi; + psi.join(bo.psi.withoutNormalPath()); + if (collectionType instanceof DepMapTypeSym depMapTypeSym) { + String elementLabel = elementLabel(depMapTypeSym, indexPrincipal(env)); + // If the element's label names its key, it is trusted by the principal the index + // holds, whoever chose it. Otherwise the index chooses which element is + // overwritten, so it must be trusted at the element's label. + if (!namesKey(depMapTypeSym)) { + env.checkFlow(io.valueLabelName, elementLabel, index.location, + "The index of an assignment target must be trusted at the label of the element"); + } + return new ExpOutcome(elementLabel, psi); + } + // The index chooses which element is overwritten, so it must be trusted as much as + // the collection. + env.checkFlow(io.valueLabelName, bo.valueLabelName, index.location, + "The index of an assignment target must be trusted at the label of the collection"); + return new ExpOutcome(bo.valueLabelName, psi); + } + + /** + * The principal the index of this subscript holds, which must be a principal-typed + * variable. + */ + private String indexPrincipal(VisitEnv env) throws SemanticException { + String principal = index.principalName(env); + if (principal == null) { + throw new SemanticException( + "the index of a dependent map must be an address or contract variable", + index.location); + } + return principal; + } + + /** The label of the element of a map of type {@code type} whose key holds {@code key}. */ + private static String elementLabel(DepMapTypeSym type, String key) { + Map mapping = new HashMap<>(); + mapping.put(type.key().toSHErrLocFmt(), key); + return type.label().toSHErrLocFmt(mapping); + } + + /** Whether the label of the elements of a map of type {@code type} names its key. */ + private static boolean namesKey(DepMapTypeSym type) { + String key = type.key().toSHErrLocFmt(); + return type.label().principalSyms().stream() + .anyMatch(p -> p.toSHErrLocFmt().equals(key)); + } } diff --git a/src/ast/Throw.java b/src/ast/Throw.java index e6d9679..4becf79 100644 --- a/src/ast/Throw.java +++ b/src/ast/Throw.java @@ -146,6 +146,10 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic typecheck.Utils.getLabelNameLock(toSHErrLocFmt())); PathOutcome psi = new PathOutcome(new PsiUnit(beginContext)); ExpOutcome ao = null; + String expName = ((Name) exception.value).id; + ExceptionTypeSym expSym = env.getExp(expName); + java.util.List argLabels = new java.util.ArrayList<>(); + java.util.List pcs = new java.util.ArrayList<>(); for (int i = 0; i < exception.args.size(); ++i) { Expression arg = exception.args.get(i); @@ -154,10 +158,23 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic env.inContext = new Context( Utils.joinLabels(ao.psi.getNormalPath().c.pc, beginContext.pc), beginContext.lambda); + argLabels.add(ao.valueLabelName); + pcs.add(env.inContext.pc); } - String expName = ((Name) exception.value).id; - ExceptionTypeSym expSym = env.getExp(expName); + // Each argument is checked like an argument of a call, with the exception's + // principal parameters standing for the principals passed for them. + java.util.Map mapping = paramMapping(env, expSym); + for (int i = 0; i < exception.args.size(); ++i) { + String paramLabel = expSym.parameters().get(i).ifl.toSHErrLocFmt(mapping); + CodeLocation argLoc = exception.args.get(i).location; + env.checkFlow(argLabels.get(i), paramLabel, argLoc, + "Input to the " + Utils.ordNumString(i + 1) + + " argument of the exception must be trusted enough"); + env.checkFlow(pcs.get(i), paramLabel, argLoc, + "Current control flow must be trusted to feed the " + + Utils.ordNumString(i + 1) + " argument of the exception"); + } PsiUnit expUnit = new PsiUnit( new Context(env.inContext.pc, @@ -172,6 +189,23 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic return psi; } + /** + * Maps each principal-typed parameter of {@code expSym} to the principal passed for it + * here, or to the top principal if the argument does not name one. + */ + private java.util.Map paramMapping(VisitEnv env, ExceptionTypeSym expSym) { + java.util.Map mapping = new java.util.HashMap<>(); + for (int i = 0; i < exception.args.size(); ++i) { + VarSym param = expSym.parameters().get(i); + if (param.isPrincipalTyped()) { + String principal = exception.args.get(i).principalName(env); + mapping.put(param.toSHErrLocFmt(), principal != null + ? principal : env.curContractSym().top().toSHErrLocFmt()); + } + } + return mapping; + } + @Override protected java.util.Map readMap(CompileEnv code) { return exception.readMap(code); diff --git a/src/ast/Try.java b/src/ast/Try.java index 5323359..1988c1f 100644 --- a/src/ast/Try.java +++ b/src/ast/Try.java @@ -25,14 +25,14 @@ public class Try extends Statement { - List body; + Block body; List handlers; // ArrayList orelse; // ArrayList finalbody; public Try(List body, List handlers) {//, ArrayList orelse, ArrayList finalbody) { - this.body = body; + this.body = new Block(body); this.handlers = handlers; // this.orelse = orelse; // this.finalbody = finalbody; @@ -58,9 +58,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S now.addException(t, true); } - for (Statement s : body) { - tmp = s.genTypeConstraints(env, now); - } + body.genTypeConstraints(env, now); env.exitNewScope(); for (ExceptHandler h : handlers) { @@ -79,14 +77,11 @@ public List solidityCodeGen(CompileEnv code) { writeMap = new HashMap<>(); // find out read and written local variables - for (Statement s : body) { - readMap.putAll(s.readMap(code)); - writeMap.putAll(s.writeMap(code)); - // solBody.addAll(s.solidityCodeGen(code)); - } + readMap.putAll(body.readMap(code)); + writeMap.putAll(body.writeMap(code)); readMap.putAll(writeMap); code.enterNewVarScope(); - Function newTempFunction = code.makeMethod(body, readMap, writeMap, ScopeType.TRY); + Function newTempFunction = code.makeMethod(body.statements(), readMap, writeMap, ScopeType.TRY); code.exitVarScope(); code.addTemporaryFunction(newTempFunction); @@ -159,9 +154,9 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic //env.psi = newPsi; PathOutcome psi = new PathOutcome(); PathOutcome input = new PathOutcome(); + Map versionsBefore = env.saveVersions(); env.incScopeLayer(); - PathOutcome so = new PathOutcome(new PsiUnit(beginContext)); - Utils.genConsStmtsWithException(body, env, so, psi, false); + psi.join(body.IFCVisit(env, false)); // for (Statement s : body) { // so = s.genConsVisit(env, false); // psi.joinExe(so); @@ -171,12 +166,12 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // } env.decScopeLayer(); for (ExceptHandler h : handlers) { - ExceptionTypeSym expSym = env.getExp(h.name()); + ExceptionTypeSym expSym = h.exceptionType(env); PsiUnit u = psi.psi.get(expSym); if (u != null) { env.checkFlow(u.c.lambda, beginContext.lambda, location, - "Try clause should maintain locks when throwing exception " + h.name()); + "Try clause should maintain locks when throwing exception " + h.type().name()); /*env.cons.add(new Constraint(new Inequality(u.c.pc, h.), env.hypothesis, location, env.curContractSym.name, "Try clause should maintain locks when throwing exception " + h.name));*/ @@ -188,20 +183,28 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic //Context cTry = env.outContext; //env.psi = oldPsi; + // A handler may be entered from any point in the body. + List> exitVersions = new ArrayList<>(); + exitVersions.add(env.saveVersions()); + java.util.Set assignedInBody = ast.Assign.assignedNames(body.statements()); for (ExceptHandler h : handlers) { + env.restoreVersions(versionsBefore); + env.havoc(assignedInBody); env.incScopeLayer(); //env.inContext = new Context(h.getHandlerPcLabelName(), beginContext.lambda); - ExceptionTypeSym expSym = env.getExp(h.name()); + ExceptionTypeSym expSym = h.exceptionType(env); PsiUnit expUnit = input.psi.get(expSym); if (expUnit != null) { env.inContext = Utils.genNewContextAndConstraints(env, false, expUnit.c, beginContext.lambda, h.nextPcSHL(), h.location); // env.inContext = new Context(expUnit.c.pc, beginContext.lambda); PathOutcome ho = h.genConsVisit(env, tail_position); psi.join(ho); + exitVersions.add(env.saveVersions()); } env.decScopeLayer(); // cTry = new Context(Utils.makeJoin(cTry.pc, env.outContext.outPcName), Utils.makeJoin(cTry.lambda, env.outContext.lockName)); } + env.mergeVersions(exitVersions); typecheck.Utils.contextFlow(env, psi.getNormalPath().c, endContext, location); @@ -226,7 +229,7 @@ public void passScopeContext(ScopeContext parent) { @Override public List children() { ArrayList rtn = new ArrayList<>(); - rtn.addAll(body); + rtn.add(body); rtn.addAll(handlers); return rtn; } @@ -238,10 +241,7 @@ public boolean exceptionHandlingFree() { @Override protected Map readMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } + Map result = new HashMap<>(body.readMap(code)); for (ExceptHandler s: handlers) { result.putAll(s.readMap(code)); } @@ -250,10 +250,7 @@ protected Map readMap(CompileEnv code) { @Override protected Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } + Map result = new HashMap<>(body.writeMap(code)); for (ExceptHandler s: handlers) { result.putAll(s.writeMap(code)); } diff --git a/src/ast/UnaryOp.java b/src/ast/UnaryOp.java index 9355b31..218b8e0 100644 --- a/src/ast/UnaryOp.java +++ b/src/ast/UnaryOp.java @@ -36,7 +36,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S } @Override - public ExpOutcome genIFConstraints(VisitEnv env, boolean tail_position) throws SemanticException { + protected ExpOutcome genFlowConstraints(VisitEnv env, boolean tail_position) throws SemanticException { return operand.genIFConstraints(env, tail_position); } @@ -62,4 +62,10 @@ public ArrayList children() { public java.util.Map readMap(CompileEnv code) { return operand.readMap(code); } + + @Override + protected TypeSym staticType(VisitEnv env) { + return env.builtinType(op == UnaryOperator.USub || op == UnaryOperator.UAdd + ? BuiltInT.UINT : BuiltInT.BOOL); + } } diff --git a/src/ast/UncheckedBlock.java b/src/ast/UncheckedBlock.java index 7fd6f83..eb46332 100644 --- a/src/ast/UncheckedBlock.java +++ b/src/ast/UncheckedBlock.java @@ -18,87 +18,47 @@ public class UncheckedBlock extends Statement { - List body; + Block body; public UncheckedBlock(List body) { - this.body = body; + this.body = new Block(body); } public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { - for (Statement s : body) { - s.genTypeConstraints(env, parent); - } - + body.genTypeConstraints(env, parent); return parent; } public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws SemanticException { - - int index = 0; - PathOutcome ifo = null; - for (Statement stmt: body) { - ++index; - String prevLambda = env.inContext.lambda; - ifo = stmt.IFCVisit(env, index == body.size() && tail_position); - PsiUnit normalUnit = ifo.getNormalPath(); - if (normalUnit == null) { - break; - } - env.inContext = Utils.genNewContextAndConstraints(env, index == body.size() && tail_position, normalUnit.c, prevLambda, stmt.nextPcSHL(), stmt.location); - } - return ifo; + return body.IFCVisit(env, tail_position); } @Override public List solidityCodeGen(CompileEnv code) { List result = new ArrayList<>(); - List block_body = new ArrayList<>(); - for (Statement stmt : body) { - block_body.addAll(stmt.solidityCodeGen(code)); - } - result.add(new UncheckedStatement(block_body)); + result.add(new UncheckedStatement(body.solidityCodeGen(code))); return result; } - @Override - public void passScopeContext(ScopeContext parent) { - scopeContext = parent; - for (Node node : children()) { - node.passScopeContext(scopeContext); - } - } - @Override public ArrayList children() { ArrayList rtn = new ArrayList<>(); - rtn.addAll(body); + rtn.add(body); return rtn; } + @Override public boolean exceptionHandlingFree() { - for (Statement s: body) { - if (!s.exceptionHandlingFree()) { - return false; - } - } - return true; + return body.exceptionHandlingFree(); } @Override protected Map readMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } - return result; + return body.readMap(code); } @Override protected Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } - return result; + return body.writeMap(code); } } diff --git a/src/ast/While.java b/src/ast/While.java index 49c262f..50846b4 100644 --- a/src/ast/While.java +++ b/src/ast/While.java @@ -17,11 +17,11 @@ public class While extends Statement { Expression test; - List body; + Block body; public While(Expression test, List body) { this.test = test; - this.body = body; + this.body = new Block(body); } public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws SemanticException { @@ -32,9 +32,7 @@ public ScopeContext genTypeConstraints(NTCEnv env, ScopeContext parent) throws S ScopeContext rtn = test.genTypeConstraints(env, now); env.addCons(rtn.genTypeConstraints(Utils.BuiltinType2ID(BuiltInT.BOOL), Relation.EQ, env, location)); - for (Statement s : body) { - ScopeContext tmp = s.genTypeConstraints(env, now); - } + body.genTypeConstraints(env, now); env.exitNewScope(); env.addCons(now.genTypeConstraints(rtn, Relation.EQ, env, location)); return now; @@ -46,6 +44,9 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic Context endContext = new Context(Utils.getLabelNamePc(toSHErrLocFmt()), Utils.getLabelNameLock(toSHErrLocFmt())); + // The loop head is reached from before the loop and from the end of the body. + java.util.Set assigned = Assign.assignedNames(body.statements()); + env.havoc(assigned); ExpOutcome to = test.genIFConstraints(env, false); String ifNameTest = to.valueLabelName; @@ -62,8 +63,15 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic env.inContext = new Context(ifNamePcAfter, beginContext.lambda); CodeLocation loc = null; - PathOutcome ifo = to.psi; - Utils.genConsStmts(body, env, ifo, false); + PathOutcome bodyOutcome = body.IFCVisit(env, false); + // The loop exits only from its head, which is also reached from the end of the body. + PathOutcome ifo = to.psi.withoutNormalPath(); + ifo.join(bodyOutcome.withoutNormalPath()); + if (bodyOutcome.getNormalPath() instanceof PsiUnit bodyEnd) { + env.checkFlow(bodyEnd.c.pc, ifNamePcAfter, location, + "Control flow at the end of the loop body contributes to the control flow of this while statement"); + } + ifo.setNormalPath(new Context(ifNamePcAfter, to.psi.getNormalPath().c.lambda)); // for (Statement stmt : body) { // ifo = stmt.genConsVisit(env, false); // @@ -75,6 +83,8 @@ public PathOutcome IFCVisit(VisitEnv env, boolean tail_position) throws Semantic // } env.decScopeLayer(); + // The loop exits from its head or from a break in the body. + env.havoc(assigned); return ifo; @@ -88,9 +98,7 @@ public List solidityCodeGen(CompileEnv code) { compile.ast.Expression cond = test.solidityCodeGen(condPrep, code); result.addAll(condPrep); code.enterNewVarScope(); - for (Statement stmt : body) { - whileBody.addAll(stmt.solidityCodeGen(code)); - } + whileBody.addAll(body.solidityCodeGen(code)); // add prep the end of whileBody excluding declarations for (compile.ast.Statement s: condPrep) { @@ -115,34 +123,23 @@ public void passScopeContext(ScopeContext parent) { public ArrayList children() { ArrayList rtn = new ArrayList<>(); rtn.add(test); - rtn.addAll(body); + rtn.add(body); return rtn; } @Override public boolean exceptionHandlingFree() { - for (Statement s: body) { - if (!s.exceptionHandlingFree()) { - return false; - } - } - return true; + return body.exceptionHandlingFree(); } @Override protected java.util.Map readMap(CompileEnv code) { java.util.Map result = test.readMap(code); - for (Statement s: body) { - result.putAll(s.readMap(code)); - } + result.putAll(body.readMap(code)); return result; } @Override protected java.util.Map writeMap(CompileEnv code) { - Map result = new HashMap<>(); - for (Statement s: body) { - result.putAll(s.writeMap(code)); - } - return result; + return body.writeMap(code); } } diff --git a/src/test.cup b/src/test.cup index e8c68f9..07b572e 100644 --- a/src/test.cup +++ b/src/test.cup @@ -238,7 +238,6 @@ nonterminal List except_types; nonterminal Throw throw_stmt; nonterminal List constructors; nonterminal FunctionDef constructor; -//nonterminal EndorseStatement endorse_stmt; nonterminal EndorseIfStatement endorseIf_stmt; nonterminal Call call_expr; nonterminal Revert revert_stmt; @@ -1086,9 +1085,6 @@ stmt ::= assign_stmt:a {: RESULT = a; :} | throw_stmt:a {: RESULT = a;:} | revert_stmt:a {: RESULT = a;:} | emit_stmt:a {: RESULT = a; :} -// | endorse_stmt:a {: -// RESULT = a; -// :} | endorseIf_stmt:a {: RESULT = a; :} @@ -1101,14 +1097,6 @@ stmt ::= assign_stmt:a {: RESULT = a; :} :} ; -//endorse_stmt ::= -// ENDORSEIF:x LPAR name_list:exprList COMMA ifLabel:from RIGHT_ARROW ifLabel:to RPAR IN suite:s {: -// EndorseStatement statement = new EndorseStatement(exprList, from, to, s); -// RESULT = statement; -// RESULT.setLoc(new CodeLocation(xleft, xright, filename)); -// :} -// ; - emit_stmt ::= EMIT:x call_expr:e SEMI {: if (!(e instanceof Call)) { diff --git a/src/typecheck/ComplexLabel.java b/src/typecheck/ComplexLabel.java index 6c8aecb..b339b2c 100644 --- a/src/typecheck/ComplexLabel.java +++ b/src/typecheck/ComplexLabel.java @@ -16,6 +16,13 @@ public List principalLeaves() { return leaves; } + @Override + public List principalSyms() { + List syms = new ArrayList<>(left.principalSyms()); + syms.addAll(right.principalSyms()); + return syms; + } + public ComplexLabel(Label left, IfOperator op, Label right, CodeLocation location) { super(location); this.left = left; diff --git a/src/typecheck/FuncSym.java b/src/typecheck/FuncSym.java index 50b46cd..d637653 100644 --- a/src/typecheck/FuncSym.java +++ b/src/typecheck/FuncSym.java @@ -19,7 +19,8 @@ public class FuncSym extends Sym { private VarSym sender; public final Label external_pc, internal_pc, gamma, rtn; - public Map exceptions; + /** The exceptions this method may throw, each with the declared label of its path. */ + public Map exceptions; public CodeLocation location; private boolean isPublic, isBuiltIn, isClosurable; private String plainSignature; @@ -35,7 +36,7 @@ public FuncSym(String funcName, List parameters, TypeSym returnType, Label returnLabel, - Map exceptions, + Map exceptions, ScopeContext defContext, VarSym sender, CodeLocation location) { super(funcName, defContext); diff --git a/src/typecheck/InterfaceSym.java b/src/typecheck/InterfaceSym.java index 1b1f5ac..a0cf1d3 100644 --- a/src/typecheck/InterfaceSym.java +++ b/src/typecheck/InterfaceSym.java @@ -367,6 +367,13 @@ public VarSym any() { return anySym; } + /** The top principal: the most trusted one. */ + public VarSym top() { + VarSym topSym = (VarSym) lookupSym(Utils.LABEL_TOP); + assert topSym != null; + return topSym; + } + public VarSym invoker() { VarSym invokerSym = (VarSym) lookupSym(Utils.LABEL_INVOKER); assert invokerSym != null; diff --git a/src/typecheck/Label.java b/src/typecheck/Label.java index dc6284f..b658e27 100644 --- a/src/typecheck/Label.java +++ b/src/typecheck/Label.java @@ -22,6 +22,9 @@ protected Label(CodeLocation location) { /** The local names of this label's principal leaves (flattening joins). */ abstract public List principalLeaves(); + /** The variables naming this label's principal leaves (flattening joins). */ + abstract public List principalSyms(); + public CodeLocation location() { return location; } diff --git a/src/typecheck/PathOutcome.java b/src/typecheck/PathOutcome.java index 299fb1d..e967b08 100644 --- a/src/typecheck/PathOutcome.java +++ b/src/typecheck/PathOutcome.java @@ -73,6 +73,13 @@ public void setReturnPath(Context inContext) { set(Utils.getReturnPathException(), inContext); } + /** The paths of this outcome other than its normal path. */ + public PathOutcome withoutNormalPath() { + PathOutcome result = new PathOutcome(new HashMap<>(psi)); + result.psi.remove(Utils.getNormalPathException()); + return result; + } + public boolean existsNormalPath() { return getNormalPath() != null; } diff --git a/src/typecheck/PrimitiveLabel.java b/src/typecheck/PrimitiveLabel.java index c895f51..e1d6c0f 100644 --- a/src/typecheck/PrimitiveLabel.java +++ b/src/typecheck/PrimitiveLabel.java @@ -5,33 +5,49 @@ public class PrimitiveLabel extends Label { VarSym sym; + /** The version of {@code sym} this label names, or null if it names {@code sym} itself. */ + private final String version; @Override public List principalLeaves() { return List.of(sym.getName()); } + @Override + public List principalSyms() { + return List.of(sym); + } + + /** A label naming the principal held by {@code sym}. */ public PrimitiveLabel(VarSym sym, CodeLocation location) { + this(sym, null, location); + } + + /** + * A label naming {@code version}, a version of the principal variable {@code sym}. + * If {@code version} is null, the label names {@code sym} itself. + */ + public PrimitiveLabel(VarSym sym, String version, CodeLocation location) { super(location); assert sym != null; this.sym = sym; + this.version = version; } @Override public String toSHErrLocFmt() { - return sym.toSHErrLocFmt(); + return version != null ? version : sym.toSHErrLocFmt(); } @Override public String toSHErrLocFmt(String origin, String substitution) { - if (sym.toSHErrLocFmt().equals(origin)) { - return substitution; - } - return sym.toSHErrLocFmt(); + String str = toSHErrLocFmt(); + return str.equals(origin) ? substitution : str; } + @Override public String toSHErrLocFmt(Map mapping) { - String str = sym.toSHErrLocFmt(); + String str = toSHErrLocFmt(); return mapping.getOrDefault(str, str); } } diff --git a/src/typecheck/StructTypeSym.java b/src/typecheck/StructTypeSym.java index de6a24c..775c151 100644 --- a/src/typecheck/StructTypeSym.java +++ b/src/typecheck/StructTypeSym.java @@ -44,6 +44,21 @@ public VarSym getMemberVarInfo(String prefix, int index) { return rtn; } + /** The member named {@code name}, or null if there is none. */ + public VarSym member(String name) { + for (VarSym mb : members) { + if (mb.getName().equals(name)) { + return mb; + } + } + return null; + } + + /** The members of this struct type, in order. */ + public List members() { + return members; + } + public int merberSize() { return members.size(); } diff --git a/src/typecheck/Utils.java b/src/typecheck/Utils.java index 92e4db4..de18807 100644 --- a/src/typecheck/Utils.java +++ b/src/typecheck/Utils.java @@ -237,10 +237,6 @@ public static String getLabelNameArgLabel(String funcName, VarSym arg) { return funcName + "." + arg.getName() + ".lbl"; } - public static String getLabelNameFuncExpLabel(String funcName, String name) { - return funcName + "." + name + ".lbl"; - } - public static String joinLabels(String lhs, String rhs) { return "(" + lhs + " ⊔ " + rhs + ")"; } @@ -271,11 +267,17 @@ public static String BuiltinType2ID(BuiltInT type) { public static SherrlocDiagnoser createDiagnoser(Set constructors, List assumptions, List constraints, boolean isIFC, InterfaceSym contractSym) { - return createDiagnoser(constructors, assumptions, constraints, isIFC, contractSym, - java.util.Set.of(), List.of()); + return createDiagnoser(constructors, java.util.Set.of(), assumptions, constraints, isIFC, + contractSym, java.util.Set.of(), List.of()); } - public static SherrlocDiagnoser createDiagnoser(Set constructors, List assumptions, + /** + * A diagnoser for {@code constraints} under {@code assumptions}. The principals named by + * {@code constructors} and {@code principalNames} are constants rather than variables to + * solve for; in IFC mode, each lies between the top and bottom principals. + */ + public static SherrlocDiagnoser createDiagnoser(Set constructors, + Set principalNames, List assumptions, List constraints, boolean isIFC, InterfaceSym contractSym, Set rigidAtoms, List atomAssumptions) { @@ -317,6 +319,15 @@ public static SherrlocDiagnoser createDiagnoser(Set constructors, diagnoser.addAssumedInequality(diagnoser.createEqualityConstraint( diagnoser.createElement(top.toSHErrLocFmt(), sherrloc.constraint.ast.Position.EmptyPosition()), diagnoser.createElement(contractSym.thisSym().toSHErrLocFmt(), sherrloc.constraint.ast.Position.EmptyPosition()))); + for (String x : principalNames) { + diagnoser.defineConstructor(x, 0); + diagnoser.addAssumedInequality(diagnoser.createGreaterThanConstraint( + diagnoser.createElement(bot.toSHErrLocFmt(), sherrloc.constraint.ast.Position.EmptyPosition()), + diagnoser.createElement(x, sherrloc.constraint.ast.Position.EmptyPosition()))); + diagnoser.addAssumedInequality(diagnoser.createLessThanConstraint( + diagnoser.createElement(top.toSHErrLocFmt(), sherrloc.constraint.ast.Position.EmptyPosition()), + diagnoser.createElement(x, sherrloc.constraint.ast.Position.EmptyPosition()))); + } } if (isIFC) { for (String atom : rigidAtoms) { @@ -836,41 +847,6 @@ public static Context genNewContextAndConstraints(VisitEnv env, boolean tail_pos return new Context(newPc, c.lambda); } - public static void genConsStmtsWithException(List body, VisitEnv env, PathOutcome so, PathOutcome psi, boolean tail_positon) throws SemanticException { - int index = 0; - for (Statement s : body) { - ++index; - String prevLambda = env.inContext.lambda; - boolean isTail = index == body.size() && tail_positon; - so = s.IFCVisit(env, isTail); - psi.joinExe(so); - // env.inContext = new Context(so.getNormalPath().c.pc, beginContext.lambda); -// boolean nextIsTail = index + 1 == body.size() && tail_positon; - env.inContext = Utils.genNewContextAndConstraints(env, isTail, so.getNormalPath().c, prevLambda, s.nextPcSHL(), s.location()); -// new Context(so.getNormalPath().c); - - } - } - public static void genConsStmts(List body, VisitEnv env, PathOutcome so, boolean tail_posn) throws SemanticException { - int index = 0; - for (Statement s : body) { - ++index; - String prevLambda = env.inContext.lambda; - boolean isTail = index == body.size() && tail_posn; - so = s.IFCVisit(env, isTail); - PsiUnit normalUnit = so.getNormalPath(); - if (normalUnit == null) { - break; - } - // env.inContext = new Context(so.getNormalPath().c.pc, beginContext.lambda); -// boolean nextIsTail = (index + 1 == body.size() && tail_positon); - env.inContext = Utils.genNewContextAndConstraints(env, isTail, so.getNormalPath().c, prevLambda, s.nextPcSHL(), s.location()); -// new Context(so.getNormalPath().c); - - } - } - - static int inferredLabelCount = 0; public static IfLabel newInferredLabel() { return new PrimitiveIfLabel(new Name("__inferredLabel" + inferredLabelCount)); diff --git a/src/typecheck/VarSym.java b/src/typecheck/VarSym.java index ac8ae79..82fd217 100644 --- a/src/typecheck/VarSym.java +++ b/src/typecheck/VarSym.java @@ -133,8 +133,22 @@ public void setLabel(Label label) { ifl = label; } + /** Whether this variable holds an address or a contract reference, and so names a principal. */ + public boolean isPrincipalTyped() { + return typeSym instanceof InterfaceSym || typeSym.getName().equals(Utils.ADDRESS_TYPE); + } + + /** Whether this variable is final and names a principal. */ public boolean isPrincipalVar() { - return isFinal && (typeSym instanceof ContractSym || typeSym instanceof InterfaceSym || typeSym.getName().equals(Utils.ADDRESS_TYPE)); + return isFinal && isPrincipalTyped(); + } + + /** + * Whether this variable names a principal that can change during execution, so that + * different occurrences of it may name different principals. + */ + public boolean isMutablePrincipal() { + return !isFinal && isPrincipalTyped(); } @Override diff --git a/src/typecheck/VisitEnv.java b/src/typecheck/VisitEnv.java index 3e35ee8..14b7590 100644 --- a/src/typecheck/VisitEnv.java +++ b/src/typecheck/VisitEnv.java @@ -35,6 +35,15 @@ public class VisitEnv { public Map sigReq = new HashMap<>(); public Map> programMap; + /** + * The current version of each mutable principal variable that has been versioned in + * the current method, keyed by the variable's SHErrLoc name. A variable absent from + * the map is at its initial version, which is named by the variable itself. + */ + private Map versions = new HashMap<>(); + private final Set versionNames = new java.util.LinkedHashSet<>(); + private int versionCount = 0; + private final Set rigidAtoms = new java.util.LinkedHashSet<>(); private final List atomAssumptions = new ArrayList<>(); @@ -116,6 +125,11 @@ public boolean containsFunc(String id) { return getFunc(id) != null; } + /** The built-in type {@code t}. */ + public TypeSym builtinType(BuiltInT t) { + return (TypeSym) curSymTab.lookup(Utils.BuiltinType2ID(t)); + } + public boolean containsContract(String funcName) { return getContract(funcName) != null; } @@ -216,6 +230,111 @@ public void addPrincipal(VarSym varSym) { principalSet.add(varSym); } + /** + * The SHErrLoc name of the principal that {@code v} holds at the current program point. + * For a variable that is not a mutable principal, this is the variable's own name. + */ + public String principalName(VarSym v) { + String base = v.toSHErrLocFmt(); + return versions.getOrDefault(base, base); + } + + /** + * Starts a new version of the mutable principal variable {@code v}, about which nothing + * is known, and returns its SHErrLoc name. + */ + public String newVersion(VarSym v) { + assert v.isMutablePrincipal() : v.getName(); + return newVersion(v.toSHErrLocFmt()); + } + + private String newVersion(String base) { + String version = base + "..v" + (++versionCount); + versions.put(base, version); + versionNames.add(version); + return version; + } + + /** + * Assumes that the principals named {@code a} and {@code b} are the same, as when one + * variable is assigned the principal held by another. + */ + public void assumeSamePrincipal(String a, String b) { + addAtomAssumption(a, b); + } + + /** + * Registers {@code name} as a principal about which nothing is known, such as a + * newly created contract, and returns it. + */ + public String freshPrincipal(String name) { + versionNames.add(name); + return name; + } + + /** The SHErrLoc names of all versions created so far. Each names a principal. */ + public Set versionNames() { + return versionNames; + } + + /** The current versions, for restoring with {@link #restoreVersions}. */ + public Map saveVersions() { + return new HashMap<>(versions); + } + + /** Makes {@code saved} the current versions. */ + public void restoreVersions(Map saved) { + versions = new HashMap<>(saved); + } + + /** + * Makes the current versions those at a point where the control-flow paths ending in + * {@code branches} merge. A variable whose version differs among the branches gets a + * new version (a phi-version), about which nothing is known. + */ + public void mergeVersions(List> branches) { + Set keys = new java.util.HashSet<>(); + for (Map b : branches) keys.addAll(b.keySet()); + Map merged = new HashMap<>(); + for (String base : keys) { + Set seen = new java.util.HashSet<>(); + for (Map b : branches) seen.add(b.getOrDefault(base, base)); + if (seen.size() == 1) { + String only = seen.iterator().next(); + if (!only.equals(base)) merged.put(base, only); + } else { + merged.put(base, base + "..v" + (++versionCount)); + versionNames.add(merged.get(base)); + } + } + versions = merged; + } + + /** + * Gives a new version to each mutable principal variable visible here whose name is in + * {@code names}, and to each mutable principal field of the current contract. + */ + public void havoc(Set names) { + for (String name : names) { + if (curSymTab.lookup(name) instanceof VarSym v && v.isMutablePrincipal()) { + newVersion(v); + } + } + havocFields(); + } + + /** + * Gives a new version to each mutable principal field of the current contract, as + * needed after any call, which may assign to fields. + */ + public void havocFields() { + for (VarSym v : curContractSym.symTab.getVars().values()) { + if (v.isMutablePrincipal()) { + newVersion(v); + } + } + } + public VarSym thisSym() { return curContractSym.thisSym(); } @@ -237,11 +356,15 @@ public void setCurFuncSym(FuncSym funcSym) { curFuncSym = funcSym; } + /** + * Resolves {@code ifl} at the current program point: a mutable principal variable in it + * names the version it holds here. + */ public Label toLabel(IfLabel ifl) { if (ifl instanceof PrimitiveIfLabel) { - VarSym label = (VarSym) getVar(((PrimitiveIfLabel) ifl).value().id); - if (label == null) return null; - return new PrimitiveLabel(label, ifl.getLocation()); + VarSym label = getVar(((PrimitiveIfLabel) ifl).value().id); + return new PrimitiveLabel(label, + label.isMutablePrincipal() ? principalName(label) : null, ifl.getLocation()); } else if (ifl instanceof ComplexIfLabel) { return new ComplexLabel(toLabel(((ComplexIfLabel) ifl).getLeft()), ((ComplexIfLabel) ifl).getOp(), @@ -262,6 +385,7 @@ public void enterNewContract() { } public void enterNewMethod(String name) { + versions = new HashMap<>(); cons = new ArrayList<>(); trustCons = new ArrayList<>(); conMap.put(name, cons); diff --git a/src/typecheck/sherrlocUtils/Inequality.java b/src/typecheck/sherrlocUtils/Inequality.java index eaad2e0..b9fa1cd 100644 --- a/src/typecheck/sherrlocUtils/Inequality.java +++ b/src/typecheck/sherrlocUtils/Inequality.java @@ -4,6 +4,8 @@ import sherrloc.diagnostic.SherrlocDiagnoser; import java.util.List; +import sherrloc.constraint.ast.Element; +import sherrloc.constraint.ast.Position; // a <= b means a flows to b // that is, a >= b in terms of integrity level @@ -57,8 +59,8 @@ public String toSherrlocFmt() { } public sherrloc.constraint.ast.Inequality toSherrlocInequality(SherrlocDiagnoser diagnoser) { - var e = diagnoser.createElement(lhs, sherrloc.constraint.ast.Position.EmptyPosition()); - var o = diagnoser.createElement(rhs, sherrloc.constraint.ast.Position.EmptyPosition()); + var e = toElement(lhs, diagnoser); + var o = toElement(rhs, diagnoser); if (relation == Relation.EQ) { return diagnoser.createEqualityConstraint(e, o); @@ -68,4 +70,41 @@ public sherrloc.constraint.ast.Inequality toSherrlocInequality(SherrlocDiagnoser return diagnoser.createGreaterThanConstraint(e, o); } } + + /** + * The element denoted by {@code label}, a name or a join or meet written as + * {@code (l ⊔ r)} or {@code (l ⊓ r)}. Each name resolves to the constructor or + * variable that {@code diagnoser} knows by that name. + */ + private static Element toElement(String label, SherrlocDiagnoser diagnoser) { + // A composite string handed to the diagnoser whole would be parsed with + // empty tables, turning every name inside it into a fresh variable. + if (label.startsWith("(") && label.endsWith(")")) { + String inner = label.substring(1, label.length() - 1); + int depth = 0; + for (int i = 0; i < inner.length(); i++) { + char c = inner.charAt(i); + if (c == '(') { + depth++; + } else if (c == ')') { + depth--; + if (depth < 0) { + break; // the outer parentheses do not enclose the whole label + } + } else if (depth == 0 && (c == '⊔' || c == '⊓') + && i > 0 && inner.charAt(i - 1) == ' ' && i + 1 < inner.length() + && inner.charAt(i + 1) == ' ') { + List operands = List.of( + toElement(inner.substring(0, i - 1), diagnoser), + toElement(inner.substring(i + 2), diagnoser)); + return c == '⊔' ? diagnoser.createJoinListElement(operands) + : diagnoser.createMeetListElement(operands); + } + } + if (depth == 0) { + return toElement(inner, diagnoser); + } + } + return diagnoser.createElement(label, Position.EmptyPosition()); + } } diff --git a/test/contracts/applications/Dexible.scif b/test/contracts/applications/Dexible.scif index 8638428..7fbaea1 100644 --- a/test/contracts/applications/Dexible.scif +++ b/test/contracts/applications/Dexible.scif @@ -7,7 +7,7 @@ contract Dexible { constructor() { super(); } public void swap{sender}(IERC20 tokenIn, IERC20 tokenOut, IExchange router, uint amount) - throws (FailedSwap) { + throws (FailedSwap{any}) { atomic { tokenIn.approveFrom(sender, address(router), amount); router.exchange(sender, tokenIn, tokenOut, amount); @@ -17,7 +17,7 @@ contract Dexible { } public void multiSwap{sender}(IERC20[] tokens, IExchange[] routers, uint[] amounts) - throws (FailedSwap) { + throws (FailedSwap{any}) { atomic { uint{sender} i = 0; while (i < tokens.length() - 1) { diff --git a/test/contracts/applications/ERC20_depmap.scif b/test/contracts/applications/ERC20_depmap.scif index f37bbaf..238b36b 100644 --- a/test/contracts/applications/ERC20_depmap.scif +++ b/test/contracts/applications/ERC20_depmap.scif @@ -8,8 +8,8 @@ contract ERC20 implements IERC20 { bytes _name; bytes _symbol; - exception ERC20InsufficientBalance(address owner, uint cur, uint needed); - exception ERC20InsufficientAllowance(address owner, uint cur, uint needed); + exception ERC20InsufficientBalance(address{any} owner, uint{owner} balance, uint{owner} needed); + exception ERC20InsufficientAllowance(address{any} owner, address{owner} spender, uint{owner} allowance, uint{owner ⨆ spender} needed); constructor(bytes name_, bytes symbol_) { _name = endorse(name_, sender -> this); @@ -61,11 +61,11 @@ contract ERC20 implements IERC20 { } } - public uint{owner} allowance(final address owner, final address spender) { + public uint{owner ⨆ sender} allowance(final address owner, final address spender) { return _allowances[owner][spender]; } - public void transferFrom{sender -> from; sender}(final address from, address to, uint val) throws (ERC20InsufficientAllowance{this}, ERC20InsufficientBalance{this}) { + public void transferFrom{sender -> from; sender}(final address from, address to, uint val) throws (ERC20InsufficientAllowance{from}, ERC20InsufficientBalance{from}) { endorse([from, to, val], sender -> from) when (_allowances[from][sender] >= val) { transfer(from, to, val); @@ -73,7 +73,7 @@ contract ERC20 implements IERC20 { _allowances[from][sender] -= val; } } else { - throw ERC20InsufficientAllowance(to, _allowances[from][sender], val); + throw ERC20InsufficientAllowance(from, sender, _allowances[from][sender], val); } } @@ -82,7 +82,7 @@ contract ERC20 implements IERC20 { currentAllowance = allowance(owner, spender); if (currentAllowance != UINT_MAX) { if (currentAllowance < val) { - throw ERC20InsufficientAllowance(spender, currentAllowance, val); + throw ERC20InsufficientAllowance(owner, spender, currentAllowance, val); } unchecked { _allowances[owner][spender] = currentAllowance - val; diff --git a/test/contracts/applications/ERC20_depmap_noe.scif b/test/contracts/applications/ERC20_depmap_noe.scif index 9b417e9..5b21a58 100644 --- a/test/contracts/applications/ERC20_depmap_noe.scif +++ b/test/contracts/applications/ERC20_depmap_noe.scif @@ -58,7 +58,7 @@ contract ERC20 implements IERC20 { } } - public uint{owner} allowance(final address owner, final address spender) { + public uint{owner ⨆ sender} allowance(final address owner, final address spender) { return _allowances[owner][spender]; } diff --git a/test/contracts/applications/ERC20_depmap_old.scif b/test/contracts/applications/ERC20_depmap_old.scif index 219833d..235eef4 100644 --- a/test/contracts/applications/ERC20_depmap_old.scif +++ b/test/contracts/applications/ERC20_depmap_old.scif @@ -8,8 +8,8 @@ contract ERC20 implements IERC20 { bytes _name; bytes _symbol; - exception ERC20InsufficientBalance(address owner, uint cur, uint needed); - exception ERC20InsufficientAllowance(address owner, uint cur, uint needed); + exception ERC20InsufficientBalance(address{any} owner, uint{owner} balance, uint{owner} needed); + exception ERC20InsufficientAllowance(address{any} owner, address{owner} spender, uint{owner} allowance, uint{owner ⨆ spender} needed); constructor(bytes name_, bytes symbol_) { _name = endorse(name_, sender -> this); @@ -60,18 +60,18 @@ contract ERC20 implements IERC20 { } } - public uint{owner} allowance(final address owner, final address spender) { + public uint{owner ⨆ sender} allowance(final address owner, final address spender) { return _allowances[owner][spender]; } - public void transferFrom{sender -> from; sender}(final address from, address to, uint val) throws (ERC20InsufficientAllowance{this}, ERC20InsufficientBalance{this}) { + public void transferFrom{sender -> from; sender}(final address from, address to, uint val) throws (ERC20InsufficientAllowance{from}, ERC20InsufficientBalance{from}) { uint{from} currentAllowance = _allowances[from][sender]; endorse([from, to, val], sender -> from) when (currentAllowance >= val) { transfer(from, to, val); _allowances[from][sender] = currentAllowance - val; } else { - throw ERC20InsufficientAllowance(to, currentAllowance, val); + throw ERC20InsufficientAllowance(from, sender, currentAllowance, val); } } @@ -80,7 +80,7 @@ contract ERC20 implements IERC20 { currentAllowance = allowance(owner, spender); if (currentAllowance != UINT_MAX) { if (currentAllowance < val) { - throw ERC20InsufficientAllowance(spender, currentAllowance, val); + throw ERC20InsufficientAllowance(owner, spender, currentAllowance, val); } _allowances[owner][spender] = currentAllowance - val; } diff --git a/test/contracts/applications/ERC20_nodepmap.scif b/test/contracts/applications/ERC20_nodepmap.scif index 3b03d61..4a39363 100644 --- a/test/contracts/applications/ERC20_nodepmap.scif +++ b/test/contracts/applications/ERC20_nodepmap.scif @@ -50,10 +50,11 @@ contract ERC20 { } public void approveFrom{from->this}(final address from, address spender, uint val) { - address _from = endorse(from, sender->this); - address _to = endorse(spender, sender->this); - uint _val = endorse(val, sender->this); - _allowances[from][spender] = val; + address _from = endorse(from, from->this); + address owner = endorse(from, from->this); + address _to = endorse(spender, from->this); + uint _val = endorse(val, from->this); + _allowances[owner][_to] = _val; } public void transfer{from->this}(final address from, address to, uint val) throws (ERC20InsufficientBalance) { diff --git a/test/contracts/applications/EthCrossChainManager.scif b/test/contracts/applications/EthCrossChainManager.scif index d4b4de7..95e6a3e 100644 --- a/test/contracts/applications/EthCrossChainManager.scif +++ b/test/contracts/applications/EthCrossChainManager.scif @@ -3,22 +3,22 @@ import "./IECCUtils.scif"; import "./IEthCrossChainData.scif"; contract EthCrossChainManager { - address EthCrossChainDataAddress; - address EthECCUtilsAddress; + final IEthCrossChainData{this} eccd; + final IECCUtils{this} cutils; constructor(address _eccd, address _cutils) { - EthCrossChainDataAddress = endorse(_eccd, sender->this); - EthECCUtilsAddress = endorse(_cutils, sender->this); + eccd = endorse(IEthCrossChainData(_eccd), (sender ⨆ _eccd) -> this); + cutils = endorse(IECCUtils(_cutils), (sender ⨆ _cutils) -> this); super(); } public void initGenesisBlock(Header@IECCUtils header, bytes pubKeyList) { - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); address[] keepers = eccd.getCurEpochConPubKeys(); assert(keepers.length() == 0); - // Parse header and convit the public keys into nextBookKeeper and compare it with header.nextBookKeeper to verify the validity of signature + // Parse header and convit the public keys into nextBookKeeper + // and compare it with header.nextBookKeeper to verify the + // validity of signature address nextBookkeeper = cutils.verifyPubkey(keepers); endorse([header], sender->this) when (header.nextBookkeeper == nextBookkeeper) { @@ -31,8 +31,6 @@ contract EthCrossChainManager { } public void changeBookKeeper(Header@IECCUtils header, address[] polyChainBKs, bytes sigList) { - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); // Make sure rawHeader.height is higher than recorded current epoch start height uint curEpochStartHeight = eccd.getCurEpochStartHeight(); @@ -42,12 +40,14 @@ contract EthCrossChainManager { (header.nextBookkeeper != address(0)) && (cutils.verifySig(header, sigList, polyChainBKs, polyChainBKs.length() - (polyChainBKs.length() - 1) / 3))) { - // Convert pubKeyList into ethereum address format and make sure the compound address from the converted ethereum addresses - // equals passed in header.nextBooker + // Convert pubKeyList into ethereum address format and make + // sure the compound address from the converted ethereum + // addresses equals passed in header.nextBooker address nextBookKeeper = cutils.verifyPubkey(polyChainBKs); assert(header.nextBookkeeper == nextBookKeeper); - // update current epoch start height of Poly chain and current epoch consensus peers book keepers addresses + // update current epoch start height of Poly chain and current + // epoch consensus peers book keepers addresses eccd.putCurEpochStartHeight(header.height); eccd.putCurEpochConPubKeys(polyChainBKs); } else { @@ -56,8 +56,6 @@ contract EthCrossChainManager { } public void crossChain(uint toChainId, address toContract, bytes txData) { - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); // To help differentiate two txs, the ethTxHashIndex is increasing automatically uint txHashIndex = eccd.getEthTxHashIndex(); @@ -70,11 +68,15 @@ contract EthCrossChainManager { eccd.putEthTxHash(cutils.keccak256(rawParam)); } - public void verifyHeaderAndExecuteTx(bytes proof, Header@IECCUtils header, bytes headerProof, Header@IECCUtils curHeader, bytes headerSig) { - // Load ehereum cross chain data contract - // Get stored consensus public key bytes of current poly chain epoch and deserialize Poly chain consensus public key bytes to address[] - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); + public void verifyHeaderAndExecuteTx{sender -> this; any}(bytes proof, + Header@IECCUtils header, + bytes headerProof, + Header@IECCUtils curHeader, + bytes headerSig) + { + // Load Ethereum cross-chain data contract. + // Get stored consensus public key bytes of current poly chain epoch + // and deserialize Poly chain consensus public key bytes to address[] assert eccd => this; assert cutils => this; @@ -94,7 +96,8 @@ contract EthCrossChainManager { assert(cutils.getHeaderHash(header) == proveValue); } - // Parse the toMerkleValue struct and make sure the tx has not been processed, then mark this tx as processed + // Parse the toMerkleValue struct and make sure the tx has not been + // processed, then mark this tx as processed. ToMerkleValue@IECCUtils toMerkleValue = cutils.merkleProve(proof, header.crossStatesRoot); assert(!eccd.checkIfFromChainTxExist(toMerkleValue.fromChainID, toMerkleValue.txHash)); eccd.markFromChainTxExist(toMerkleValue.fromChainID, toMerkleValue.txHash); @@ -102,10 +105,12 @@ contract EthCrossChainManager { // Ethereum ChainId is 2, we need to check the transaction is for Ethereum network assert(toMerkleValue.toChainId == 2); - // Obtain the targeting contract, so that Ethereum cross chain manager contract can trigger the executation of cross chain tx on Ethereum side + // Obtain the targeting contract, so that Ethereum cross chain manager + // contract can trigger the execution of cross chain tx on Ethereum + // side address toContract = toMerkleValue.toContract; - IPolyCallee callee = IPolyCallee(toContract); + IPolyCallee{toContract} callee = IPolyCallee(toContract); callee.polyCallback(sender, toMerkleValue.args, toMerkleValue.fromContract, toMerkleValue.fromChainID); } -} \ No newline at end of file +} diff --git a/test/contracts/applications/HODLWallet.scif b/test/contracts/applications/HODLWallet.scif index 2ff0ce1..e5d5e6a 100644 --- a/test/contracts/applications/HODLWallet.scif +++ b/test/contracts/applications/HODLWallet.scif @@ -2,7 +2,7 @@ contract HODLWallet { map(address, uint) balances; map(address, uint) withdrawalCount; - map(address, map(address, bool{owner}){owner}) approvals; + map(address owner, map(address, bool{owner}){owner}) approvals; uint MAX_WITHDRAWAL; @@ -11,7 +11,7 @@ contract HODLWallet { super(); } - public void doWithdraw{from -> this}(final address from, address to, uint amount) { + public void doWithdraw{from -> this; any}(final address from, address to, uint amount) { endorse([from, to, amount], from -> this) when (amount <= MAX_WITHDRAWAL && balances[from] >= amount && withdrawalCount[from] < 3) { unchecked { @@ -37,7 +37,7 @@ contract HODLWallet { } } - public void withdrawForTo{sender -> from; sender}(final address from, address to, uint amount) { + public void withdrawForTo{sender -> from; any}(final address from, address to, uint amount) { endorse([from, to, amount], sender -> from) when (approvals[from][sender]) { doWithdraw(from, to, amount); diff --git a/test/contracts/applications/IERC20.scif b/test/contracts/applications/IERC20.scif index 85db5c7..a97e171 100644 --- a/test/contracts/applications/IERC20.scif +++ b/test/contracts/applications/IERC20.scif @@ -1,9 +1,9 @@ interface IERC20 { - exception ERC20InsufficientBalance(address owner, uint cur, uint needed); - exception ERC20InsufficientAllowance(address owner, uint cur, uint needed); + exception ERC20InsufficientBalance(address{any} owner, uint{owner} balance, uint{owner} needed); + exception ERC20InsufficientAllowance(address{any} owner, address{owner} spender, uint{owner} allowance, uint{owner ⨆ spender} needed); public uint balanceOf(address account); public void approve{sender}(address allowed, uint amount); public void approveFrom{from}(final address from, address spender, uint val); public void transfer{from -> this}(final address from, address to, uint amount) throws (ERC20InsufficientBalance{this}); - public void transferFrom{sender -> from; sender}(final address from, address to, uint amount) throws (ERC20InsufficientAllowance{this}, ERC20InsufficientBalance{this}); + public void transferFrom{sender -> from; sender}(final address from, address to, uint amount) throws (ERC20InsufficientAllowance{from}, ERC20InsufficientBalance{from}); } \ No newline at end of file diff --git a/test/contracts/applications/IERC777.scif b/test/contracts/applications/IERC777.scif index 7db9bc3..7d347d9 100644 --- a/test/contracts/applications/IERC777.scif +++ b/test/contracts/applications/IERC777.scif @@ -1,9 +1,9 @@ interface IERC777 { - exception ERC20InsufficientBalance(address owner, uint cur, uint needed); - exception ERC20InsufficientAllowance(address owner, uint cur, uint needed); + exception ERC20InsufficientBalance(address{any} owner, uint{owner} balance, uint{owner} needed); + exception ERC20InsufficientAllowance(address{any} owner, address{owner} spender, uint{owner} allowance, uint{owner ⨆ spender} needed); public uint balanceOf(address account); public void approve{sender}(address allowed, uint amount); public void approveFrom{from}(final address from, address spender, uint val); public void transfer{from -> this; any}(final address from, address to, uint amount) throws (ERC20InsufficientBalance{this}); - public void transferFrom{sender -> from; any}(final address from, address to, uint amount) throws (ERC20InsufficientAllowance{this}, ERC20InsufficientBalance{this}); + public void transferFrom{sender -> from; any}(final address from, address to, uint amount) throws (ERC20InsufficientAllowance{from}, ERC20InsufficientBalance{this}); } \ No newline at end of file diff --git a/test/contracts/applications/KoET.scif b/test/contracts/applications/KoET.scif index 5e34759..4f6e770 100644 --- a/test/contracts/applications/KoET.scif +++ b/test/contracts/applications/KoET.scif @@ -63,7 +63,7 @@ contract KoET { currentClaimPrice = startingClaimPrice; currentMonarch = endorse(new Monarch( wizardAddress, - name, + endorse(name, sender->this), 0, now ), sender->this); @@ -114,7 +114,7 @@ contract KoET { pastMonarchs.push(currentMonarch); currentMonarch = endorse(new Monarch( sender, - name, + endorse(name, sender->this), valuePaid, now ), sender->this); diff --git a/test/contracts/applications/SysEscrow.scif b/test/contracts/applications/SysEscrow.scif index 46c095a..3a78260 100644 --- a/test/contracts/applications/SysEscrow.scif +++ b/test/contracts/applications/SysEscrow.scif @@ -38,9 +38,8 @@ contract SysEscrow { address _buyer, // The buying party of the trade uint _paymentWindowInSeconds // The time in seconds from Escrow creation that the buyer can return money ) { - Escrow currentEscrow = escrows[_tradeHash]; - endorse([_seller, _buyer, _paymentWindowInSeconds], any -> this) - when (value >= MinDeposit && !(currentEscrow.exists)) { + endorse([_tradeHash, _seller, _buyer, _paymentWindowInSeconds], any -> this) + when (value >= MinDeposit && !(escrows[_tradeHash].exists)) { uint _buyerCanCancelAfter = now + _paymentWindowInSeconds; escrows[_tradeHash] = new Escrow(true, _seller, _buyer, value, _buyerCanCancelAfter, false, false); } @@ -63,24 +62,27 @@ contract SysEscrow { address _buyer, // The buying party of the trade uint _value // ) { - - Escrow currentEscrow = escrows[_tradeHash]; - assert(currentEscrow.exists); - assert(currentEscrow.buyerCanCancelAfter this) + when (escrows[_tradeHash].exists && escrows[_tradeHash].buyerCanCancelAfter < now) { + Escrow currentEscrow = escrows[_tradeHash]; + + uint arbitratorValue; + uint buyerValue; + unchecked { + arbitratorValue = currentEscrow.summ*ARBITRATOR_PERCENT/100; + buyerValue = currentEscrow.summ - arbitratorValue; + } + + lock(this) { + send(currentEscrow.buyer, buyerValue); + send(arbitrator, arbitratorValue); + } + + delete escrows[_tradeHash]; + } else { + assert false; } - - lock(this) { - send(currentEscrow.buyer, buyerValue); - send(arbitrator, arbitratorValue); - } - - delete escrows[_tradeHash]; } public void approveEscrow( @@ -92,10 +94,13 @@ contract SysEscrow { address _buyer, // The buying party of the trade uint _value // Trade value ) { - Escrow currentEscrow = escrows[_tradeHash]; - assert(currentEscrow.exists); - assert(currentEscrow.buyer==sender); - currentEscrow.buyerApprovedTheTransaction = true; + // Only the buyer of an escrow may approve it. + endorse(_tradeHash, sender -> this) + when (escrows[_tradeHash].exists && escrows[_tradeHash].buyer == sender) { + escrows[_tradeHash].buyerApprovedTheTransaction = true; + } else { + assert false; + } } public void releaseEscrow( @@ -107,25 +112,29 @@ contract SysEscrow { address _buyer, // The buying party of the trade uint _value // Trade value ) { - Escrow currentEscrow = escrows[_tradeHash]; - assert(currentEscrow.exists); - assert(currentEscrow.buyerApprovedTheTransaction); - - - uint arbitratorValue; - uint buyerValue; - unchecked { - arbitratorValue = currentEscrow.summ*ARBITRATOR_PERCENT/100; - buyerValue = currentEscrow.summ - arbitratorValue; + // Anyone may release an escrow that exists and that its buyer approved. + endorse(_tradeHash, sender -> this) + when (escrows[_tradeHash].exists && escrows[_tradeHash].buyerApprovedTheTransaction) { + Escrow currentEscrow = escrows[_tradeHash]; + + + uint arbitratorValue; + uint buyerValue; + unchecked { + arbitratorValue = currentEscrow.summ*ARBITRATOR_PERCENT/100; + buyerValue = currentEscrow.summ - arbitratorValue; + } + + lock(this) { + send(currentEscrow.buyer, buyerValue); + send(arbitrator, arbitratorValue); + } + + delete escrows[_tradeHash]; + } else { + assert false; } - lock(this) { - send(currentEscrow.buyer, buyerValue); - send(arbitrator, arbitratorValue); - } - - delete escrows[_tradeHash]; - } private bool isExistsEscrow( diff --git a/test/contracts/applications/Uniswap_ERC20.scif b/test/contracts/applications/Uniswap_ERC20.scif index fddfc88..ae8ad5d 100644 --- a/test/contracts/applications/Uniswap_ERC20.scif +++ b/test/contracts/applications/Uniswap_ERC20.scif @@ -9,7 +9,7 @@ contract Uniswap extends ERC20 { constructor(address token_addr, bytes _name, bytes _symbol) { assert (address(token) == address(0) && token_addr != address(0)); - token = endorse(IERC20(token_addr), sender->this); + token = endorse(IERC20(token_addr), (sender ⨆ token_addr) -> this); super(endorse(_name, sender->this), endorse(_symbol, sender->this)); } @@ -206,9 +206,9 @@ contract Uniswap extends ERC20 { return tokenToEthOutput(endorse(eth_bought, sender->this), max_tokens, deadline, sender, endorse(recipient, sender->this)); } - uint tokenToTokenInput(uint tokens_sold, uint{any} min_tokens_bought, uint{any} min_eth_bought, uint{any} deadline, address buyer, address recipient, address exchange_addr) { + uint tokenToTokenInput(uint tokens_sold, uint{any} min_tokens_bought, uint{any} min_eth_bought, uint{any} deadline, address buyer, address recipient, Uniswap exchange) { assert ((deadline >= now && tokens_sold > 0) && (min_tokens_bought > 0 && min_eth_bought > 0)); - assert (exchange_addr != address(this) && exchange_addr != address(0)); + assert (address(exchange) != address(this) && address(exchange) != address(0)); uint token_reserve = token.balanceOf(address(this)); uint eth_bought = getInputPrice(tokens_sold, token_reserve, balance(address(this))); uint wei_bought = eth_bought; @@ -222,25 +222,23 @@ contract Uniswap extends ERC20 { assert false; } } - Uniswap exchange = Uniswap(exchange_addr); uint tokens_bought = exchange.ethToTokenTransferInput(min_tokens_bought, deadline, recipient){value = wei_bought}; return tokens_bought; } public uint tokenToTokenSwapInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToTokenTransferInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address recipient, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } - uint tokenToTokenOutput(uint tokens_bought, uint{any} max_tokens_sold, uint{any} max_eth_sold, uint{any} deadline, address buyer, address recipient, address exchange_addr) { + uint tokenToTokenOutput(uint tokens_bought, uint{any} max_tokens_sold, uint{any} max_eth_sold, uint{any} deadline, address buyer, address recipient, Uniswap exchange) { assert (deadline >= now && (tokens_bought > 0 && max_eth_sold > 0)); - assert (exchange_addr != address(this) && exchange_addr != address(0)); - Uniswap exchange = Uniswap(exchange_addr); + assert (address(exchange) != address(this) && address(exchange) != address(0)); uint eth_bought = exchange.getEthToTokenOutputPrice(tokens_bought); uint token_reserve = token.balanceOf(address(this)); uint tokens_sold = getOutputPrice(eth_bought, token_reserve, balance(address(this))); @@ -259,31 +257,31 @@ contract Uniswap extends ERC20 { } public uint tokenToTokenSwapOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToTokenTransferOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address recipient, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToExchangeSwapInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address exchange_addr) { - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(exchange_addr, sender->this)); + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeTransferInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address recipient, address exchange_addr) { assert (recipient != address(this)); - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeSwapOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address exchange_addr) { - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(exchange_addr, sender->this)); + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeTransferOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address recipient, address exchange_addr) { assert (recipient != address(this)); - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), any -> this)); } public uint getEthToTokenInputPrice(uint eth_sold) { diff --git a/test/contracts/applications/Uniswap_ERC20_noe.scif b/test/contracts/applications/Uniswap_ERC20_noe.scif index 6102a5e..9df0742 100644 --- a/test/contracts/applications/Uniswap_ERC20_noe.scif +++ b/test/contracts/applications/Uniswap_ERC20_noe.scif @@ -9,7 +9,7 @@ contract Uniswap extends ERC20 { constructor(address token_addr, bytes _name, bytes _symbol) { assert (address(token) == address(0) && token_addr != address(0)); - token = endorse(IERC20(token_addr), sender->this); + token = endorse(IERC20(token_addr), (sender ⨆ token_addr) -> this); super(endorse(_name, sender->this), endorse(_symbol, sender->this)); } @@ -170,9 +170,9 @@ contract Uniswap extends ERC20 { return tokenToEthOutput(endorse(eth_bought, sender->this), max_tokens, deadline, sender, endorse(recipient, sender->this)); } - uint tokenToTokenInput(uint tokens_sold, uint{any} min_tokens_bought, uint{any} min_eth_bought, uint{any} deadline, address buyer, address recipient, address exchange_addr) { + uint tokenToTokenInput(uint tokens_sold, uint{any} min_tokens_bought, uint{any} min_eth_bought, uint{any} deadline, address buyer, address recipient, Uniswap exchange) { assert ((deadline >= now && tokens_sold > 0) && (min_tokens_bought > 0 && min_eth_bought > 0)); - assert (exchange_addr != address(this) && exchange_addr != address(0)); + assert (address(exchange) != address(this) && address(exchange) != address(0)); uint token_reserve = token.balanceOf(address(this)); uint eth_bought = getInputPrice(tokens_sold, token_reserve, balance(address(this))); uint wei_bought = eth_bought; @@ -180,25 +180,23 @@ contract Uniswap extends ERC20 { atomic { token.transferFrom(buyer, address(this), tokens_sold); } - Uniswap exchange = Uniswap(exchange_addr); uint tokens_bought = exchange.ethToTokenTransferInput(min_tokens_bought, deadline, recipient){value = wei_bought}; return tokens_bought; } public uint tokenToTokenSwapInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToTokenTransferInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address recipient, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } - uint tokenToTokenOutput(uint tokens_bought, uint{any} max_tokens_sold, uint{any} max_eth_sold, uint{any} deadline, address buyer, address recipient, address exchange_addr) { + uint tokenToTokenOutput(uint tokens_bought, uint{any} max_tokens_sold, uint{any} max_eth_sold, uint{any} deadline, address buyer, address recipient, Uniswap exchange) { assert (deadline >= now && (tokens_bought > 0 && max_eth_sold > 0)); - assert (exchange_addr != address(this) && exchange_addr != address(0)); - Uniswap exchange = Uniswap(exchange_addr); + assert (address(exchange) != address(this) && address(exchange) != address(0)); uint eth_bought = exchange.getEthToTokenOutputPrice(tokens_bought); uint token_reserve = token.balanceOf(address(this)); uint tokens_sold = getOutputPrice(eth_bought, token_reserve, balance(address(this))); @@ -211,31 +209,31 @@ contract Uniswap extends ERC20 { } public uint tokenToTokenSwapOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToTokenTransferOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address recipient, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToExchangeSwapInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address exchange_addr) { - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(exchange_addr, sender->this)); + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeTransferInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address recipient, address exchange_addr) { assert (recipient != address(this)); - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeSwapOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address exchange_addr) { - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(exchange_addr, sender->this)); + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeTransferOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address recipient, address exchange_addr) { assert (recipient != address(this)); - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), any -> this)); } public uint getEthToTokenInputPrice(uint eth_sold) { diff --git a/test/contracts/applications/Uniswap_ERC777_noe.scif b/test/contracts/applications/Uniswap_ERC777_noe.scif index 3271ab5..ae40166 100644 --- a/test/contracts/applications/Uniswap_ERC777_noe.scif +++ b/test/contracts/applications/Uniswap_ERC777_noe.scif @@ -7,7 +7,7 @@ contract Uniswap extends ERC20 { constructor(address token_addr, bytes _name, bytes _symbol) { assert (address(token) == address(0) && token_addr != address(0)); - token = endorse(IERC777(token_addr), sender->this); + token = endorse(IERC777(token_addr), (sender ⨆ token_addr) -> this); super(endorse(_name, sender->this), endorse(_symbol, sender->this)); } @@ -176,9 +176,9 @@ contract Uniswap extends ERC20 { return tokenToEthOutput(endorse(eth_bought, sender->this), max_tokens, deadline, sender, endorse(recipient, sender->this)); } - uint tokenToTokenInput(uint tokens_sold, uint{any} min_tokens_bought, uint{any} min_eth_bought, uint{any} deadline, address buyer, address recipient, address exchange_addr) { + uint tokenToTokenInput(uint tokens_sold, uint{any} min_tokens_bought, uint{any} min_eth_bought, uint{any} deadline, address buyer, address recipient, Uniswap exchange) { assert ((deadline >= now && tokens_sold > 0) && (min_tokens_bought > 0 && min_eth_bought > 0)); - assert (exchange_addr != address(this) && exchange_addr != address(0)); + assert (address(exchange) != address(this) && address(exchange) != address(0)); uint token_reserve = token.balanceOf(address(this)); uint eth_bought = getInputPrice(tokens_sold, token_reserve, balance(address(this))); uint wei_bought = eth_bought; @@ -188,25 +188,23 @@ contract Uniswap extends ERC20 { token.transferFrom(buyer, address(this), tokens_sold); } } - Uniswap exchange = Uniswap(exchange_addr); uint tokens_bought = exchange.ethToTokenTransferInput(min_tokens_bought, deadline, recipient){value = wei_bought}; return tokens_bought; } public uint tokenToTokenSwapInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToTokenTransferInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address recipient, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } - uint tokenToTokenOutput(uint tokens_bought, uint{any} max_tokens_sold, uint{any} max_eth_sold, uint{any} deadline, address buyer, address recipient, address exchange_addr) { + uint tokenToTokenOutput(uint tokens_bought, uint{any} max_tokens_sold, uint{any} max_eth_sold, uint{any} deadline, address buyer, address recipient, Uniswap exchange) { assert (deadline >= now && (tokens_bought > 0 && max_eth_sold > 0)); - assert (exchange_addr != address(this) && exchange_addr != address(0)); - Uniswap exchange = Uniswap(exchange_addr); + assert (address(exchange) != address(this) && address(exchange) != address(0)); uint eth_bought = exchange.getEthToTokenOutputPrice(tokens_bought); uint token_reserve = token.balanceOf(address(this)); uint tokens_sold = getOutputPrice(eth_bought, token_reserve, balance(address(this))); @@ -221,31 +219,31 @@ contract Uniswap extends ERC20 { } public uint tokenToTokenSwapOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToTokenTransferOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address recipient, address token_addr) { - address exchange_addr = token_to_exchange[token_addr]; - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + address{sender} exchange_addr = token_to_exchange[token_addr]; + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), (sender ⨆ exchange_addr) -> this)); } public uint tokenToExchangeSwapInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address exchange_addr) { - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(exchange_addr, sender->this)); + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, sender, endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeTransferInput(uint tokens_sold, uint min_tokens_bought, uint min_eth_bought, uint deadline, address recipient, address exchange_addr) { assert (recipient != address(this)); - return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + return tokenToTokenInput(endorse(tokens_sold, sender->this), min_tokens_bought, min_eth_bought, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeSwapOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address exchange_addr) { - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(exchange_addr, sender->this)); + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, sender, endorse(Uniswap(exchange_addr), any -> this)); } public uint tokenToExchangeTransferOutput(uint tokens_bought, uint max_tokens_sold, uint max_eth_sold, uint deadline, address recipient, address exchange_addr) { assert (recipient != address(this)); - return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(exchange_addr, sender->this)); + return tokenToTokenOutput(endorse(tokens_bought, sender->this), max_tokens_sold, max_eth_sold, deadline, sender, endorse(recipient, sender->this), endorse(Uniswap(exchange_addr), any -> this)); } public uint getEthToTokenInputPrice(uint eth_sold) { diff --git a/test/contracts/ifcTypechecking/AssertFalseHandler.scif b/test/contracts/ifcTypechecking/AssertFalseHandler.scif new file mode 100644 index 0000000..145a52c --- /dev/null +++ b/test/contracts/ifcTypechecking/AssertFalseHandler.scif @@ -0,0 +1,20 @@ +// An assertion of false never completes normally, so a handler that consists +// of one does not make control flow after the try untrusted. +contract AssertFalseHandler { + uint{this} count; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}() { + try { + g(); + } catch (E e) { + assert false; + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/CallElementReceiver.scif b/test/contracts/ifcTypechecking/CallElementReceiver.scif new file mode 100644 index 0000000..e0cb142 --- /dev/null +++ b/test/contracts/ifcTypechecking/CallElementReceiver.scif @@ -0,0 +1,11 @@ +import "./IOracle.scif"; +// A method can be called on an element of an array of contract references. +contract CallElementReceiver { + IOracle[] oracles; + uint{this} secret; + constructor() { super(); } + + public void f{this}(uint{this} k) { + secret = oracles[k].price(); + } +} diff --git a/test/contracts/ifcTypechecking/CallElementReceiverLow_W01.scif b/test/contracts/ifcTypechecking/CallElementReceiverLow_W01.scif new file mode 100644 index 0000000..8f3643f --- /dev/null +++ b/test/contracts/ifcTypechecking/CallElementReceiverLow_W01.scif @@ -0,0 +1,12 @@ +import "./IOracle.scif"; +// A reference chosen by an untrusted index is trusted no more than the index, +// so the results of calls through it are not trusted. +contract CallElementReceiverLow_W01 { + IOracle[] oracles; + uint{this} secret; + constructor() { super(); } + + public void f{this}(uint{any} k) { + secret = oracles[k].price(); + } +} diff --git a/test/contracts/ifcTypechecking/CallLendsAuthority_W01.scif b/test/contracts/ifcTypechecking/CallLendsAuthority_W01.scif new file mode 100644 index 0000000..a9c9812 --- /dev/null +++ b/test/contracts/ifcTypechecking/CallLendsAuthority_W01.scif @@ -0,0 +1,12 @@ +import "./IVault.scif"; +// The callee demands data and control flow trusted by itself. A caller vouches +// for them with its own authority, so it must trust them itself, whoever chose +// the reference: otherwise anyone could use relay to set a price in a vault +// that trusts this contract. +contract CallLendsAuthority_W01 { + constructor() { super(); } + + public void relay{any}(IVault{any} v, uint{any} p) { + v.setPrice(p); + } +} diff --git a/test/contracts/ifcTypechecking/CallMemberReceiver.scif b/test/contracts/ifcTypechecking/CallMemberReceiver.scif new file mode 100644 index 0000000..a21c9d1 --- /dev/null +++ b/test/contracts/ifcTypechecking/CallMemberReceiver.scif @@ -0,0 +1,14 @@ +import "./IOracle.scif"; +// A method can be called on a struct member that holds a contract reference. +contract CallMemberReceiver { + struct Feed { + IOracle oracle; + } + Feed feed; + uint{this} secret; + constructor() { super(); } + + public void f{this}() { + secret = feed.oracle.price(); + } +} diff --git a/test/contracts/ifcTypechecking/CallMemberReceiverLow_W01.scif b/test/contracts/ifcTypechecking/CallMemberReceiverLow_W01.scif new file mode 100644 index 0000000..1d3ee5f --- /dev/null +++ b/test/contracts/ifcTypechecking/CallMemberReceiverLow_W01.scif @@ -0,0 +1,15 @@ +import "./IOracle.scif"; +// A reference held in an untrusted member is untrusted, so the results of calls +// through it are not trusted. +contract CallMemberReceiverLow_W01 { + struct Feed { + IOracle{any} oracle; + } + Feed feed; + uint{this} secret; + constructor() { super(); } + + public void f{this}() { + secret = feed.oracle.price(); + } +} diff --git a/test/contracts/ifcTypechecking/CallPcTrusted.scif b/test/contracts/ifcTypechecking/CallPcTrusted.scif new file mode 100644 index 0000000..2863e00 --- /dev/null +++ b/test/contracts/ifcTypechecking/CallPcTrusted.scif @@ -0,0 +1,13 @@ +import "./ISelfVouching.scif"; +// Control flow after a call through a trusted reference stays trusted. +contract CallPcTrusted { + uint{this} count; + constructor() { super(); } + + public void f{this}(ISelfVouching{this} x) { + lock(this) { + x.get(); + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/CallPcUntrusted_W01.scif b/test/contracts/ifcTypechecking/CallPcUntrusted_W01.scif new file mode 100644 index 0000000..e4ff2ce --- /dev/null +++ b/test/contracts/ifcTypechecking/CallPcUntrusted_W01.scif @@ -0,0 +1,14 @@ +import "./ISelfVouching.scif"; +// Control flow after a call is trusted no more than the reference used to make +// it, whatever the callee's labels claim. +contract CallPcUntrusted_W01 { + uint{this} count; + constructor() { super(); } + + public void f{this}(ISelfVouching{any} x) { + lock(this) { + x.get(); + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/CallResultTrusted.scif b/test/contracts/ifcTypechecking/CallResultTrusted.scif new file mode 100644 index 0000000..93e3d04 --- /dev/null +++ b/test/contracts/ifcTypechecking/CallResultTrusted.scif @@ -0,0 +1,9 @@ +import "./ISelfVouching.scif"; +// A callee's return label is a claim trusted as much as the reference used to call it. +contract CallResultTrusted { + constructor() { super(); } + + public uint{this} f{this}(ISelfVouching{this} x) { + return x.get(); + } +} diff --git a/test/contracts/ifcTypechecking/CallResultUntrusted_W01.scif b/test/contracts/ifcTypechecking/CallResultUntrusted_W01.scif new file mode 100644 index 0000000..cba55e5 --- /dev/null +++ b/test/contracts/ifcTypechecking/CallResultUntrusted_W01.scif @@ -0,0 +1,10 @@ +import "./ISelfVouching.scif"; +// A callee's return label is a claim trusted no more than the reference used to +// call it: an untrusted contract cannot vouch for its result on its caller's behalf. +contract CallResultUntrusted_W01 { + constructor() { super(); } + + public uint{this} f{this}(ISelfVouching{any} x) { + return x.get(); + } +} diff --git a/test/contracts/ifcTypechecking/CallTrustedReference.scif b/test/contracts/ifcTypechecking/CallTrustedReference.scif new file mode 100644 index 0000000..3a60510 --- /dev/null +++ b/test/contracts/ifcTypechecking/CallTrustedReference.scif @@ -0,0 +1,13 @@ +import "./IOracle.scif"; +// Endorsing a reference states that the contract it names is trusted; the +// results of calls through it are then trusted as much as the reference. +contract CallTrustedReference { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a) { + IOracle{this} o = endorse(IOracle(a), a -> this); + secret = o.price(); + } +} diff --git a/test/contracts/ifcTypechecking/CastUnknownContract_W01.scif b/test/contracts/ifcTypechecking/CastUnknownContract_W01.scif new file mode 100644 index 0000000..025c2d7 --- /dev/null +++ b/test/contracts/ifcTypechecking/CastUnknownContract_W01.scif @@ -0,0 +1,10 @@ +import "./IOracle.scif"; +// A reference is trusted no more than the contract it names: a trusted address +// may still name an untrusted contract. +contract CastUnknownContract_W01 { + constructor() { super(); } + + public void f{this}(address{this} a) { + IOracle{this} o = IOracle(a); + } +} diff --git a/test/contracts/ifcTypechecking/ClosureCrossContractBothThis.scif b/test/contracts/ifcTypechecking/ClosureCrossContractBothThis.scif index 541e3e5..6166110 100644 --- a/test/contracts/ifcTypechecking/ClosureCrossContractBothThis.scif +++ b/test/contracts/ifcTypechecking/ClosureCrossContractBothThis.scif @@ -2,26 +2,14 @@ // // Caller builds a closure value of type // (fun {this -> this; this} () -> void) {this} -// where every `this` resolves, in Caller's scope, to Caller's contract -// identity. The closure value flows out via `Sink.accept` whose -// parameter type uses the same syntactic shape, with `this` resolving -// in Sink's scope to Sink's contract identity. The two `this`'s are -// syntactically the same but logically different principals. -// -// IFC accepts the contract — and that acceptance is correct for the -// single-hop case. The reason: SCIF's normal external-call -// dependent-label substitution rewrites the parameter slot's -// `Sink.this` to the receiver's label (Caller's `sink` field, which -// defaults to {this} = Caller.this). After substitution, the closure's -// inner labels (Caller.this) and the parameter slot's substituted -// labels (Caller.this) are equal, so the variance constraints emitted -// by the closure-argument hook in Call.genIFConstraints reduce to -// trivially true. -// -// This test exists to lock in that single-hop behavior. Multi-hop -// (Caller → Intermediate → Other) is the remaining open case: the -// substitution chain breaks after the first hop. See G2 in -// IMPLEMENTATION.md. +// where every `this` is Caller. It passes the closure to Sink.accept, +// whose parameter type has the same syntax but where every `this` is +// Sink: Sink promises to invoke the closure with control flow trusted +// by Sink. At the call, Sink's `this` is the principal the receiver +// `sink` holds, so the closure fits the slot only if Sink acts for +// Caller. Handing Sink a closure that demands Caller-trusted control +// flow delegates Caller's authority, so Caller checks that it trusts +// Sink before doing so. interface IT { closurable public void op{this -> this}(); } @@ -42,7 +30,9 @@ contract Caller { public void forward{this -> this; this}() { (fun {this -> this; this} () -> void) {this} c = closure t.op(); - sink.accept(c); + if (sink => this) { + sink.accept(c); + } } } diff --git a/test/contracts/ifcTypechecking/ClosureCrossContractUntrustedSink_W01.scif b/test/contracts/ifcTypechecking/ClosureCrossContractUntrustedSink_W01.scif new file mode 100644 index 0000000..bfec02f --- /dev/null +++ b/test/contracts/ifcTypechecking/ClosureCrossContractUntrustedSink_W01.scif @@ -0,0 +1,38 @@ +// Caller hands Sink a closure that demands control flow trusted by +// Caller, but Sink promises only control flow trusted by Sink. Without +// knowing that Sink acts for Caller, this delegates Caller's authority. +interface IT { + closurable public void op{this -> this}(); +} + +interface ISink { + public void accept{this -> this; this}( + (fun {this -> this; this} () -> void) {this} c + ); +} + +contract Caller { + IT t; + ISink sink; + + constructor() { + super(); + } + + public void forward{this -> this; this}() { + (fun {this -> this; this} () -> void) {this} c = closure t.op(); + sink.accept(c); + } +} + +contract Sink { + constructor() { + super(); + } + + public void accept{this -> this; this}( + (fun {this -> this; this} () -> void) {this} c + ) { + c.invoke(); + } +} diff --git a/test/contracts/ifcTypechecking/DepMapKeyRead.scif b/test/contracts/ifcTypechecking/DepMapKeyRead.scif new file mode 100644 index 0000000..c709c6f --- /dev/null +++ b/test/contracts/ifcTypechecking/DepMapKeyRead.scif @@ -0,0 +1,10 @@ +// When an element's label names its key, the element is trusted by the +// principal the index holds, whoever chose the index. +contract DepMapKeyRead { + map(address a, uint{a}) m; + constructor() { super(); } + + public uint{k} f{this}(final address{any} k) { + return m[k]; + } +} diff --git a/test/contracts/ifcTypechecking/DepMapKeyReadLow_W01.scif b/test/contracts/ifcTypechecking/DepMapKeyReadLow_W01.scif new file mode 100644 index 0000000..06b48cd --- /dev/null +++ b/test/contracts/ifcTypechecking/DepMapKeyReadLow_W01.scif @@ -0,0 +1,10 @@ +// An element whose label names its key is trusted only as much as the +// principal the index holds. +contract DepMapKeyReadLow_W01 { + map(address a, uint{a}) m; + constructor() { super(); } + + public uint{this} f{this}(final address{any} k) { + return m[k]; + } +} diff --git a/test/contracts/ifcTypechecking/DepMapKeyWrite.scif b/test/contracts/ifcTypechecking/DepMapKeyWrite.scif new file mode 100644 index 0000000..bb15196 --- /dev/null +++ b/test/contracts/ifcTypechecking/DepMapKeyWrite.scif @@ -0,0 +1,10 @@ +// When an element's label names its key, any index may be written, since the +// element is labeled by the principal the index holds. +contract DepMapKeyWrite { + map(address a, uint{a}) m; + constructor() { super(); } + + public void f{this}(address{any} k) { + m[k] = 1; + } +} diff --git a/test/contracts/ifcTypechecking/DepMapUnnamedKeyRead.scif b/test/contracts/ifcTypechecking/DepMapUnnamedKeyRead.scif new file mode 100644 index 0000000..4f6b7e6 --- /dev/null +++ b/test/contracts/ifcTypechecking/DepMapUnnamedKeyRead.scif @@ -0,0 +1,13 @@ +// An element whose label does not name its key, read at a trusted index, is +// trusted at its label. +contract DepMapUnnamedKeyRead { + map(address a, uint{this}) m; + uint{this} secret; + constructor() { super(); } + + public void f{this}(address{this} k) { + if (k => this) { + secret = m[k]; + } + } +} diff --git a/test/contracts/ifcTypechecking/DepMapUnnamedKeyReadLow_W01.scif b/test/contracts/ifcTypechecking/DepMapUnnamedKeyReadLow_W01.scif new file mode 100644 index 0000000..4603d48 --- /dev/null +++ b/test/contracts/ifcTypechecking/DepMapUnnamedKeyReadLow_W01.scif @@ -0,0 +1,13 @@ +// When an element's label does not name its key, the index chooses which +// element is read, so the result is trusted no more than the index. +contract DepMapUnnamedKeyReadLow_W01 { + map(address a, uint{this}) m; + uint{this} secret; + constructor() { super(); } + + public void f{this}(address{any} k) { + if (k => this) { + secret = m[k]; + } + } +} diff --git a/test/contracts/ifcTypechecking/DepMapUnnamedKeyWriteLow_W01.scif b/test/contracts/ifcTypechecking/DepMapUnnamedKeyWriteLow_W01.scif new file mode 100644 index 0000000..bea9539 --- /dev/null +++ b/test/contracts/ifcTypechecking/DepMapUnnamedKeyWriteLow_W01.scif @@ -0,0 +1,10 @@ +// When an element's label does not name its key, the index of a write must be +// trusted at the element's label. +contract DepMapUnnamedKeyWriteLow_W01 { + map(address a, uint{this}) m; + constructor() { super(); } + + public void f{this}(address{any} k) { + m[k] = 1; + } +} diff --git a/test/contracts/ifcTypechecking/EmitArgLow_W01.scif b/test/contracts/ifcTypechecking/EmitArgLow_W01.scif new file mode 100644 index 0000000..0e355b3 --- /dev/null +++ b/test/contracts/ifcTypechecking/EmitArgLow_W01.scif @@ -0,0 +1,10 @@ +// An event parameter labeled {this} (the default) cannot carry untrusted data. +contract EmitArgLow_W01 { + event Paid(address to, uint amount); + + constructor() { super(); } + + public void f{this}(address{this} to, uint{any} amount) { + emit Paid(to, amount); + } +} diff --git a/test/contracts/ifcTypechecking/EmitDependentLow_W01.scif b/test/contracts/ifcTypechecking/EmitDependentLow_W01.scif new file mode 100644 index 0000000..d233e72 --- /dev/null +++ b/test/contracts/ifcTypechecking/EmitDependentLow_W01.scif @@ -0,0 +1,10 @@ +// amount is labeled by who, so data trusted only by sender cannot be reported as trusted by this. +contract EmitDependentLow_W01 { + event Claimed(address who, uint{who} amount); + + constructor() { super(); } + + public void g{sender -> this; any}(uint{sender} amount) { + emit Claimed(address(this), amount); + } +} diff --git a/test/contracts/ifcTypechecking/EmitIFC.scif b/test/contracts/ifcTypechecking/EmitIFC.scif new file mode 100644 index 0000000..2cf45f1 --- /dev/null +++ b/test/contracts/ifcTypechecking/EmitIFC.scif @@ -0,0 +1,16 @@ +// Emitting trusted data under trusted control flow; a parameter label may name +// another parameter, which stands for the principal passed for it. +contract EmitIFC { + event Paid(address to, uint amount); + event Claimed(address who, uint{who} amount); + + constructor() { super(); } + + public void f{this}(address{this} to, uint{this} amount) { + emit Paid(to, amount); + } + + public void g{sender -> this; any}(uint{sender} amount) { + emit Claimed(sender, amount); + } +} diff --git a/test/contracts/ifcTypechecking/EmitPcLow_W01.scif b/test/contracts/ifcTypechecking/EmitPcLow_W01.scif new file mode 100644 index 0000000..3007fa2 --- /dev/null +++ b/test/contracts/ifcTypechecking/EmitPcLow_W01.scif @@ -0,0 +1,12 @@ +// Emitting under untrusted control flow tells observers something untrusted. +contract EmitPcLow_W01 { + event Paid(address to, uint amount); + + constructor() { super(); } + + public void f{this}(address{this} to, uint{this} amount, bool{any} c) { + if (c) { + emit Paid(to, amount); + } + } +} diff --git a/test/contracts/ifcTypechecking/EndorseFromLabel_W01.scif b/test/contracts/ifcTypechecking/EndorseFromLabel_W01.scif new file mode 100644 index 0000000..f2fb309 --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorseFromLabel_W01.scif @@ -0,0 +1,10 @@ +// The endorsed value must flow to the from-label: v is not trusted by sender. +contract EndorseFromLabel_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(uint{any} v) { + secret = endorse(v, sender -> this); + } +} diff --git a/test/contracts/ifcTypechecking/EndorseIfBodyPcLow_W01.scif b/test/contracts/ifcTypechecking/EndorseIfBodyPcLow_W01.scif new file mode 100644 index 0000000..7d8e6d2 --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorseIfBodyPcLow_W01.scif @@ -0,0 +1,16 @@ +import "./ISelfVouching.scif"; +// Control flow after an endorse-if is trusted no more than control flow at the +// end of its branches. +contract EndorseIfBodyPcLow_W01 { + uint{this} count; + constructor() { super(); } + + public void f{this}(ISelfVouching{any} x, uint{this} n) { + lock(this) { + endorse(n, this -> this) when (n > 0) { + x.get(); + } + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/EndorseIfBodyPcTrusted.scif b/test/contracts/ifcTypechecking/EndorseIfBodyPcTrusted.scif new file mode 100644 index 0000000..ef8dcb2 --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorseIfBodyPcTrusted.scif @@ -0,0 +1,16 @@ +import "./ISelfVouching.scif"; +// Control flow after an endorse-if whose branch calls a trusted contract stays +// trusted. +contract EndorseIfBodyPcTrusted { + uint{this} count; + constructor() { super(); } + + public void f{this}(ISelfVouching{this} x, uint{this} n) { + lock(this) { + endorse(n, this -> this) when (n > 0) { + x.get(); + } + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/EndorseIfThrowLow_W01.scif b/test/contracts/ifcTypechecking/EndorseIfThrowLow_W01.scif new file mode 100644 index 0000000..286f4eb --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorseIfThrowLow_W01.scif @@ -0,0 +1,13 @@ +// An exception thrown in a branch of an endorse-if must be trusted at its +// declared label. +contract EndorseIfThrowLow_W01 { + exception E(); + constructor() { super(); } + + public void f{any}(uint{any} n) throws (E{this}) { + endorse(n, any -> this) when (n > 0) { + } else { + throw E(); + } + } +} diff --git a/test/contracts/ifcTypechecking/EndorseIfThrowUntrusted.scif b/test/contracts/ifcTypechecking/EndorseIfThrowUntrusted.scif new file mode 100644 index 0000000..5be5fd0 --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorseIfThrowUntrusted.scif @@ -0,0 +1,12 @@ +// An exception thrown in a branch of an endorse-if may be declared untrusted. +contract EndorseIfThrowUntrusted { + exception E(); + constructor() { super(); } + + public void f{any}(uint{any} n) throws (E{any}) { + endorse(n, any -> this) when (n > 0) { + } else { + throw E(); + } + } +} diff --git a/test/contracts/ifcTypechecking/EndorseLabels.scif b/test/contracts/ifcTypechecking/EndorseLabels.scif new file mode 100644 index 0000000..f87af71 --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorseLabels.scif @@ -0,0 +1,10 @@ +// An endorsement raises a value from its from-label to its to-label. +contract EndorseLabels { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(uint{any} v) { + secret = endorse(v, any -> this); + } +} diff --git a/test/contracts/ifcTypechecking/EndorsePcLow_W01.scif b/test/contracts/ifcTypechecking/EndorsePcLow_W01.scif new file mode 100644 index 0000000..90298bf --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorsePcLow_W01.scif @@ -0,0 +1,11 @@ +// Code under untrusted control flow cannot endorse data up to this, +// even if the result is used only where untrusted data may go. +contract EndorsePcLow_W01 { + constructor() { super(); } + + public void f{this}(uint{any} v, bool{any} c) { + if (c) { + uint{any} w = endorse(v, any -> this); + } + } +} diff --git a/test/contracts/ifcTypechecking/EndorseToLabel_W01.scif b/test/contracts/ifcTypechecking/EndorseToLabel_W01.scif new file mode 100644 index 0000000..2c30972 --- /dev/null +++ b/test/contracts/ifcTypechecking/EndorseToLabel_W01.scif @@ -0,0 +1,10 @@ +// The result of an endorsement is trusted only at its to-label. +contract EndorseToLabel_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(uint{any} v) { + secret = endorse(v, any -> sender); + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionData.scif b/test/contracts/ifcTypechecking/ExceptionData.scif new file mode 100644 index 0000000..3474e17 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionData.scif @@ -0,0 +1,18 @@ +// Trusted data thrown in a trusted exception can be used as trusted in its handler. +contract ExceptionData { + uint{this} secret; + exception E(uint v); + constructor() { super(); } + + void g{this}(uint{this} x) throws (E{this}) { + throw E(x); + } + + public void f{this}(uint{this} x) { + try { + g(x); + } catch (E e) { + secret = e.v; + } + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionDataLow_W01.scif b/test/contracts/ifcTypechecking/ExceptionDataLow_W01.scif new file mode 100644 index 0000000..d19fb52 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionDataLow_W01.scif @@ -0,0 +1,10 @@ +// An exception's label covers its data, so untrusted data cannot be thrown in +// an exception declared trusted. +contract ExceptionDataLow_W01 { + exception E(uint v); + constructor() { super(); } + + public void g{this}(uint{any} x) throws (E{this}) { + throw E(x); + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionDependentLabel.scif b/test/contracts/ifcTypechecking/ExceptionDependentLabel.scif new file mode 100644 index 0000000..1aba35c --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionDependentLabel.scif @@ -0,0 +1,22 @@ +// An exception's data may be labeled by a principal it carries. A handler can +// use the data as trusted once it knows that principal is trusted. +contract ExceptionDependentLabel { + uint{this} secret; + exception E(address owner, uint{owner} v); + constructor() { super(); } + + void g{this}(final address a, uint{a} x) throws (E{this}) { + throw E(a, x); + } + + public void f{this}(final address a, uint{a} x) { + try { + g(a, x); + } catch (E e) { + final address owner = e.owner; + if (owner => this) { + secret = e.v; + } + } + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionDependentLabelLow_W01.scif b/test/contracts/ifcTypechecking/ExceptionDependentLabelLow_W01.scif new file mode 100644 index 0000000..c37ea69 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionDependentLabelLow_W01.scif @@ -0,0 +1,19 @@ +// Data labeled by a principal an exception carries is trusted only as much as +// that principal. +contract ExceptionDependentLabelLow_W01 { + uint{this} secret; + exception E(address owner, uint{owner} v); + constructor() { super(); } + + void g{this}(final address a, uint{a} x) throws (E{this}) { + throw E(a, x); + } + + public void f{this}(final address a, uint{a} x) { + try { + g(a, x); + } catch (E e) { + secret = e.v; + } + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionDependentThrowLow_W01.scif b/test/contracts/ifcTypechecking/ExceptionDependentThrowLow_W01.scif new file mode 100644 index 0000000..7926000 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionDependentThrowLow_W01.scif @@ -0,0 +1,10 @@ +// Data thrown for a parameter labeled by a principal parameter must be trusted +// by the principal passed for it. +contract ExceptionDependentThrowLow_W01 { + exception E(address owner, uint{owner} v); + constructor() { super(); } + + public void g{this}(final address a, final address b, uint{a} x) throws (E{this}) { + throw E(b, x); + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionExternalTrusted.scif b/test/contracts/ifcTypechecking/ExceptionExternalTrusted.scif new file mode 100644 index 0000000..1dea844 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionExternalTrusted.scif @@ -0,0 +1,17 @@ +import "./ICrossThrower.scif"; +// An exception from a trusted contract is trusted at its declared label. +contract ExceptionExternalTrusted { + uint{this} secret; + exception Oops(); + constructor() { super(); } + + public void f{this}(ICrossThrower{this} x) { + lock(this) { + try { + x.boom(); + } catch (Oops e) { + secret = 1; + } + } + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionExternalUntrusted_W01.scif b/test/contracts/ifcTypechecking/ExceptionExternalUntrusted_W01.scif new file mode 100644 index 0000000..e0fe5c3 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionExternalUntrusted_W01.scif @@ -0,0 +1,18 @@ +import "./ICrossThrower.scif"; +// An exception from another contract is trusted no more than the reference used +// to call it, whatever label the callee declares. +contract ExceptionExternalUntrusted_W01 { + uint{this} secret; + exception Oops(); + constructor() { super(); } + + public void f{this}(ICrossThrower{any} x) { + lock(this) { + try { + x.boom(); + } catch (Oops e) { + secret = 1; + } + } + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionHandlerLow_W01.scif b/test/contracts/ifcTypechecking/ExceptionHandlerLow_W01.scif new file mode 100644 index 0000000..ec5bd92 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionHandlerLow_W01.scif @@ -0,0 +1,19 @@ +// A handler runs on the exception's path, so it is trusted no more than the +// exception's declared label. +contract ExceptionHandlerLow_W01 { + uint{this} secret; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}() { + try { + g(); + } catch (E e) { + secret = 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionHandlerTrusted.scif b/test/contracts/ifcTypechecking/ExceptionHandlerTrusted.scif new file mode 100644 index 0000000..524c342 --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionHandlerTrusted.scif @@ -0,0 +1,18 @@ +// A handler for an exception declared trusted runs with trusted control flow. +contract ExceptionHandlerTrusted { + uint{this} secret; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{this}) { + throw E(); + } + + public void f{this}() { + try { + g(); + } catch (E e) { + secret = 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionThrowLow_W01.scif b/test/contracts/ifcTypechecking/ExceptionThrowLow_W01.scif new file mode 100644 index 0000000..4f18b2e --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionThrowLow_W01.scif @@ -0,0 +1,9 @@ +// An exception must be thrown from control flow trusted at its declared label. +contract ExceptionThrowLow_W01 { + exception E(); + constructor() { super(); } + + public void g{any}() throws (E{this}) { + throw E(); + } +} diff --git a/test/contracts/ifcTypechecking/ExceptionThrowUntrusted.scif b/test/contracts/ifcTypechecking/ExceptionThrowUntrusted.scif new file mode 100644 index 0000000..9faa4ff --- /dev/null +++ b/test/contracts/ifcTypechecking/ExceptionThrowUntrusted.scif @@ -0,0 +1,9 @@ +// An untrusted control flow can throw an exception declared untrusted. +contract ExceptionThrowUntrusted { + exception E(); + constructor() { super(); } + + public void g{any}() throws (E{any}) { + throw E(); + } +} diff --git a/test/contracts/ifcTypechecking/FinalFieldInit.scif b/test/contracts/ifcTypechecking/FinalFieldInit.scif new file mode 100644 index 0000000..fade468 --- /dev/null +++ b/test/contracts/ifcTypechecking/FinalFieldInit.scif @@ -0,0 +1,13 @@ +// A final field initialized in the constructor names a fixed principal in every method. +contract FinalFieldInit { + final address{this} owner; + uint{this} secret; + + constructor() { super(); owner = sender; } + + public void f{this}(uint{owner} v) { + if (owner => this) { + secret = v; + } + } +} diff --git a/test/contracts/ifcTypechecking/HandlerThrowLow_W01.scif b/test/contracts/ifcTypechecking/HandlerThrowLow_W01.scif new file mode 100644 index 0000000..b5fcca5 --- /dev/null +++ b/test/contracts/ifcTypechecking/HandlerThrowLow_W01.scif @@ -0,0 +1,19 @@ +// An exception thrown in a handler leaves the method on its path, which is +// trusted no more than the exception the handler caught. +contract HandlerThrowLow_W01 { + exception E(); + exception F(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}() throws (F{this}) { + try { + g(); + } catch (E e) { + throw F(); + } + } +} diff --git a/test/contracts/ifcTypechecking/HandlerThrowUntrusted.scif b/test/contracts/ifcTypechecking/HandlerThrowUntrusted.scif new file mode 100644 index 0000000..35c6c97 --- /dev/null +++ b/test/contracts/ifcTypechecking/HandlerThrowUntrusted.scif @@ -0,0 +1,19 @@ +// An exception thrown in a handler may leave the method when declared as +// untrusted as the exception the handler caught. +contract HandlerThrowUntrusted { + exception E(); + exception F(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}() throws (F{any}) { + try { + g(); + } catch (E e) { + throw F(); + } + } +} diff --git a/test/contracts/ifcTypechecking/ICrossThrower.scif b/test/contracts/ifcTypechecking/ICrossThrower.scif new file mode 100644 index 0000000..9a10141 --- /dev/null +++ b/test/contracts/ifcTypechecking/ICrossThrower.scif @@ -0,0 +1,4 @@ +interface ICrossThrower { + exception Oops(); + public void boom{any}() throws (Oops{sender}); +} diff --git a/test/contracts/ifcTypechecking/IOracle.scif b/test/contracts/ifcTypechecking/IOracle.scif new file mode 100644 index 0000000..3d45433 --- /dev/null +++ b/test/contracts/ifcTypechecking/IOracle.scif @@ -0,0 +1,3 @@ +interface IOracle { + public uint price{this}(); +} diff --git a/test/contracts/ifcTypechecking/IPointSource.scif b/test/contracts/ifcTypechecking/IPointSource.scif new file mode 100644 index 0000000..a24e3c8 --- /dev/null +++ b/test/contracts/ifcTypechecking/IPointSource.scif @@ -0,0 +1,7 @@ +interface IPointSource { + struct P { + uint v; + uint{any} u; + } + public P{sender} get{any}(); +} diff --git a/test/contracts/ifcTypechecking/ISelfVouching.scif b/test/contracts/ifcTypechecking/ISelfVouching.scif new file mode 100644 index 0000000..6015a79 --- /dev/null +++ b/test/contracts/ifcTypechecking/ISelfVouching.scif @@ -0,0 +1,3 @@ +interface ISelfVouching { + public uint{sender} get{any}(); +} diff --git a/test/contracts/ifcTypechecking/IVault.scif b/test/contracts/ifcTypechecking/IVault.scif new file mode 100644 index 0000000..a6be1d5 --- /dev/null +++ b/test/contracts/ifcTypechecking/IVault.scif @@ -0,0 +1,3 @@ +interface IVault { + public void setPrice{this}(uint{this} p); +} diff --git a/test/contracts/ifcTypechecking/IfEarlierThrowLow_W01.scif b/test/contracts/ifcTypechecking/IfEarlierThrowLow_W01.scif new file mode 100644 index 0000000..8dddbe2 --- /dev/null +++ b/test/contracts/ifcTypechecking/IfEarlierThrowLow_W01.scif @@ -0,0 +1,18 @@ +// An exception from any statement of a branch, not only the last, leaves the +// method on its path. +contract IfEarlierThrowLow_W01 { + uint{this} count; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}(uint{this} n) throws (E{this}) { + if (n > 0) { + g(); + count = 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/IfEarlierThrowUntrusted.scif b/test/contracts/ifcTypechecking/IfEarlierThrowUntrusted.scif new file mode 100644 index 0000000..5b58563 --- /dev/null +++ b/test/contracts/ifcTypechecking/IfEarlierThrowUntrusted.scif @@ -0,0 +1,18 @@ +// An exception from an earlier statement of a branch may leave the method when +// it is declared untrusted. +contract IfEarlierThrowUntrusted { + uint{this} count; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}(uint{this} n) throws (E{any}) { + if (n > 0) { + g(); + count = 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/InterfaceLocalPrincipal.scif b/test/contracts/ifcTypechecking/InterfaceLocalPrincipal.scif new file mode 100644 index 0000000..d6b9da3 --- /dev/null +++ b/test/contracts/ifcTypechecking/InterfaceLocalPrincipal.scif @@ -0,0 +1,15 @@ +import "./IOracle.scif"; +// An interface-typed local names the principal it is initialized to. +contract InterfaceLocalPrincipal { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a) { + if (a => this) { + final IOracle o = IOracle(a); + uint{o} w = 1; + secret = w; + } + } +} diff --git a/test/contracts/ifcTypechecking/InterfaceLocalPrincipal_W01.scif b/test/contracts/ifcTypechecking/InterfaceLocalPrincipal_W01.scif new file mode 100644 index 0000000..e008396 --- /dev/null +++ b/test/contracts/ifcTypechecking/InterfaceLocalPrincipal_W01.scif @@ -0,0 +1,13 @@ +import "./IOracle.scif"; +// An interface-typed local names a principal, so data it trusts is not trusted by this. +contract InterfaceLocalPrincipal_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a) { + final IOracle o = IOracle(a); + uint{o} w = 1; + secret = w; + } +} diff --git a/test/contracts/ifcTypechecking/JoinLabelPrincipal.scif b/test/contracts/ifcTypechecking/JoinLabelPrincipal.scif new file mode 100644 index 0000000..cf8db4f --- /dev/null +++ b/test/contracts/ifcTypechecking/JoinLabelPrincipal.scif @@ -0,0 +1,12 @@ +// Once a is known to be trusted, data labeled with a join of a and this is trusted. +contract JoinLabelPrincipal { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a, uint{a ⨆ this} x) { + if (a => this) { + secret = x; + } + } +} diff --git a/test/contracts/ifcTypechecking/JoinLabelPrincipal_W01.scif b/test/contracts/ifcTypechecking/JoinLabelPrincipal_W01.scif new file mode 100644 index 0000000..b0ac42b --- /dev/null +++ b/test/contracts/ifcTypechecking/JoinLabelPrincipal_W01.scif @@ -0,0 +1,10 @@ +// A principal inside a join is the principal itself, not something the solver may choose. +contract JoinLabelPrincipal_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a, uint{a ⨆ this} x) { + secret = x; + } +} diff --git a/test/contracts/ifcTypechecking/MapIndexRead.scif b/test/contracts/ifcTypechecking/MapIndexRead.scif new file mode 100644 index 0000000..6e856ea --- /dev/null +++ b/test/contracts/ifcTypechecking/MapIndexRead.scif @@ -0,0 +1,10 @@ +// An element of a trusted map read at a trusted index is trusted. +contract MapIndexRead { + map(address, uint) m; + uint{this} secret; + constructor() { super(); } + + public void f{this}(address{this} k) { + secret = m[k]; + } +} diff --git a/test/contracts/ifcTypechecking/MapIndexReadLow_W01.scif b/test/contracts/ifcTypechecking/MapIndexReadLow_W01.scif new file mode 100644 index 0000000..46c5b75 --- /dev/null +++ b/test/contracts/ifcTypechecking/MapIndexReadLow_W01.scif @@ -0,0 +1,11 @@ +// The index of a map read chooses which element is read, so the result is +// trusted no more than the index. +contract MapIndexReadLow_W01 { + map(address, uint) m; + uint{this} secret; + constructor() { super(); } + + public void f{this}(address{any} k) { + secret = m[k]; + } +} diff --git a/test/contracts/ifcTypechecking/MapIndexWrite.scif b/test/contracts/ifcTypechecking/MapIndexWrite.scif new file mode 100644 index 0000000..c757b61 --- /dev/null +++ b/test/contracts/ifcTypechecking/MapIndexWrite.scif @@ -0,0 +1,9 @@ +// A trusted map can be written at a trusted index. +contract MapIndexWrite { + map(address, uint) m; + constructor() { super(); } + + public void f{this}(address{this} k) { + m[k] = 1; + } +} diff --git a/test/contracts/ifcTypechecking/MapIndexWriteLow_W01.scif b/test/contracts/ifcTypechecking/MapIndexWriteLow_W01.scif new file mode 100644 index 0000000..7e18556 --- /dev/null +++ b/test/contracts/ifcTypechecking/MapIndexWriteLow_W01.scif @@ -0,0 +1,10 @@ +// The index of a map write chooses which element is overwritten, so it must be +// trusted at the map's label. +contract MapIndexWriteLow_W01 { + map(address, uint) m; + constructor() { super(); } + + public void f{this}(address{any} k) { + m[k] = 1; + } +} diff --git a/test/contracts/ifcTypechecking/MemberArrayPush.scif b/test/contracts/ifcTypechecking/MemberArrayPush.scif new file mode 100644 index 0000000..0279f5f --- /dev/null +++ b/test/contracts/ifcTypechecking/MemberArrayPush.scif @@ -0,0 +1,13 @@ +// Pushing onto an array held in a struct member requires trust at the +// member's location. +contract MemberArrayPush { + struct Log { + uint[] entries; + } + Log log; + constructor() { super(); } + + public void f{this}(uint{this} x) { + log.entries.push(x); + } +} diff --git a/test/contracts/ifcTypechecking/MemberArrayPushLow_W01.scif b/test/contracts/ifcTypechecking/MemberArrayPushLow_W01.scif new file mode 100644 index 0000000..a52f60d --- /dev/null +++ b/test/contracts/ifcTypechecking/MemberArrayPushLow_W01.scif @@ -0,0 +1,13 @@ +// Untrusted data cannot be pushed onto a trusted array held in a struct +// member. +contract MemberArrayPushLow_W01 { + struct Log { + uint[] entries; + } + Log log; + constructor() { super(); } + + public void f{this}(uint{any} x) { + log.entries.push(x); + } +} diff --git a/test/contracts/ifcTypechecking/NestedMapWrite.scif b/test/contracts/ifcTypechecking/NestedMapWrite.scif new file mode 100644 index 0000000..2f0d1b0 --- /dev/null +++ b/test/contracts/ifcTypechecking/NestedMapWrite.scif @@ -0,0 +1,9 @@ +// Trusted data can be written through nested subscripts of a trusted map. +contract NestedMapWrite { + map(address, map(address, uint)) m; + constructor() { super(); } + + public void f{this}(address{this} a, address{this} b, uint{this} x) { + m[a][b] = x; + } +} diff --git a/test/contracts/ifcTypechecking/NestedMapWriteLow_W01.scif b/test/contracts/ifcTypechecking/NestedMapWriteLow_W01.scif new file mode 100644 index 0000000..6ff5b81 --- /dev/null +++ b/test/contracts/ifcTypechecking/NestedMapWriteLow_W01.scif @@ -0,0 +1,9 @@ +// Writes through nested subscripts are checked against the map's label. +contract NestedMapWriteLow_W01 { + map(address, map(address, uint)) m; + constructor() { super(); } + + public void f{this}(address{this} a, address{this} b, uint{any} x) { + m[a][b] = x; + } +} diff --git a/test/contracts/ifcTypechecking/NewEndorseTail.scif b/test/contracts/ifcTypechecking/NewEndorseTail.scif new file mode 100644 index 0000000..88e3f33 --- /dev/null +++ b/test/contracts/ifcTypechecking/NewEndorseTail.scif @@ -0,0 +1,13 @@ +// A trusted control flow can endorse a reference to a contract it has just +// created. +contract Child { + constructor() { super(); } +} + +contract NewEndorseTail { + constructor() { super(); } + + public Child{this} f{this}() { + return endorse(new Child(), any -> this); + } +} diff --git a/test/contracts/ifcTypechecking/NewLocked.scif b/test/contracts/ifcTypechecking/NewLocked.scif new file mode 100644 index 0000000..305bea1 --- /dev/null +++ b/test/contracts/ifcTypechecking/NewLocked.scif @@ -0,0 +1,16 @@ +// Creating a contract under a dynamic lock need not be the last operation. +contract Child { + constructor() { super(); } +} + +contract NewLocked { + uint{this} count; + constructor() { super(); } + + public void f{this}() { + lock(this) { + Child{any} c = new Child(); + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/NewNonTail_W01.scif b/test/contracts/ifcTypechecking/NewNonTail_W01.scif new file mode 100644 index 0000000..914b3be --- /dev/null +++ b/test/contracts/ifcTypechecking/NewNonTail_W01.scif @@ -0,0 +1,15 @@ +// Creating a contract runs code with the new contract's authority, which +// nothing trusts. Without a dynamic lock, it must be the last operation. +contract Child { + constructor() { super(); } +} + +contract NewNonTail_W01 { + uint{this} count; + constructor() { super(); } + + public void f{this}() { + Child{any} c = new Child(); + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/NewTail.scif b/test/contracts/ifcTypechecking/NewTail.scif new file mode 100644 index 0000000..0eab08e --- /dev/null +++ b/test/contracts/ifcTypechecking/NewTail.scif @@ -0,0 +1,13 @@ +// A reference to a newly created contract is trusted by nobody, and can be +// returned as such. +contract Child { + constructor() { super(); } +} + +contract NewTail { + constructor() { super(); } + + public Child{any} f{this}() { + return new Child(); + } +} diff --git a/test/contracts/ifcTypechecking/NewUntrusted_W01.scif b/test/contracts/ifcTypechecking/NewUntrusted_W01.scif new file mode 100644 index 0000000..2edb77f --- /dev/null +++ b/test/contracts/ifcTypechecking/NewUntrusted_W01.scif @@ -0,0 +1,13 @@ +// A newly created contract is a principal that nothing trusts, so a reference +// to it cannot be returned as trusted. +contract Child { + constructor() { super(); } +} + +contract NewUntrusted_W01 { + constructor() { super(); } + + public Child{this} f{this}() { + return new Child(); + } +} diff --git a/test/contracts/ifcTypechecking/NonFinalPrincipalLabel_W01.scif b/test/contracts/ifcTypechecking/NonFinalPrincipalLabel_W01.scif new file mode 100644 index 0000000..c8d8728 --- /dev/null +++ b/test/contracts/ifcTypechecking/NonFinalPrincipalLabel_W01.scif @@ -0,0 +1,10 @@ +// x is not final, but it still names a principal: data trusted by it is not trusted by this. +contract NonFinalPrincipalLabel_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} x, uint{x} v) { + secret = v; + } +} diff --git a/test/contracts/ifcTypechecking/RescueAllLow_W01.scif b/test/contracts/ifcTypechecking/RescueAllLow_W01.scif new file mode 100644 index 0000000..4909f8a --- /dev/null +++ b/test/contracts/ifcTypechecking/RescueAllLow_W01.scif @@ -0,0 +1,14 @@ +// Nothing is known about where a failure came from, so a handler for failures +// runs with untrusted control flow. +contract RescueAllLow_W01 { + uint{this} count; + constructor() { super(); } + + public void f{this; any}(address{this} to) { + atomic { + send(to, 1); + } rescue * { + count = 0; + } + } +} diff --git a/test/contracts/ifcTypechecking/RescueAllUntrusted.scif b/test/contracts/ifcTypechecking/RescueAllUntrusted.scif new file mode 100644 index 0000000..28f4f2f --- /dev/null +++ b/test/contracts/ifcTypechecking/RescueAllUntrusted.scif @@ -0,0 +1,13 @@ +// A handler for failures can update untrusted state. +contract RescueAllUntrusted { + uint{any} count; + constructor() { super(); } + + public void f{this; any}(address{this} to) { + atomic { + send(to, 1); + } rescue * { + count = 0; + } + } +} diff --git a/test/contracts/ifcTypechecking/StructElementRead.scif b/test/contracts/ifcTypechecking/StructElementRead.scif new file mode 100644 index 0000000..0d4d043 --- /dev/null +++ b/test/contracts/ifcTypechecking/StructElementRead.scif @@ -0,0 +1,14 @@ +// A trusted member of a trusted array element can be used as trusted. +contract StructElementRead { + struct S { + uint{any} v; + uint w; + } + S[] ss; + uint{this} secret; + constructor() { super(); } + + public void f{this}(uint{this} k) { + secret = ss[k].w; + } +} diff --git a/test/contracts/ifcTypechecking/StructElementReadLow_W01.scif b/test/contracts/ifcTypechecking/StructElementReadLow_W01.scif new file mode 100644 index 0000000..7120f95 --- /dev/null +++ b/test/contracts/ifcTypechecking/StructElementReadLow_W01.scif @@ -0,0 +1,14 @@ +// A member of an array element is read at its own label. +contract StructElementReadLow_W01 { + struct S { + uint{any} v; + uint w; + } + S[] ss; + uint{this} secret; + constructor() { super(); } + + public void f{this}(uint{this} k) { + secret = ss[k].v; + } +} diff --git a/test/contracts/ifcTypechecking/StructElementWrite.scif b/test/contracts/ifcTypechecking/StructElementWrite.scif new file mode 100644 index 0000000..91af4aa --- /dev/null +++ b/test/contracts/ifcTypechecking/StructElementWrite.scif @@ -0,0 +1,13 @@ +// Untrusted data can be written to an untrusted member of an array element. +contract StructElementWrite { + struct S { + uint{any} v; + uint w; + } + S[] ss; + constructor() { super(); } + + public void f{this}(uint{this} k, uint{any} x) { + ss[k].v = x; + } +} diff --git a/test/contracts/ifcTypechecking/StructElementWriteLow_W01.scif b/test/contracts/ifcTypechecking/StructElementWriteLow_W01.scif new file mode 100644 index 0000000..9092566 --- /dev/null +++ b/test/contracts/ifcTypechecking/StructElementWriteLow_W01.scif @@ -0,0 +1,13 @@ +// Untrusted data cannot be written to a trusted member of an array element. +contract StructElementWriteLow_W01 { + struct S { + uint{any} v; + uint w; + } + S[] ss; + constructor() { super(); } + + public void f{this}(uint{this} k, uint{any} x) { + ss[k].w = x; + } +} diff --git a/test/contracts/ifcTypechecking/StructForeignMember.scif b/test/contracts/ifcTypechecking/StructForeignMember.scif new file mode 100644 index 0000000..f96482e --- /dev/null +++ b/test/contracts/ifcTypechecking/StructForeignMember.scif @@ -0,0 +1,14 @@ +import "./IPointSource.scif"; +// A struct declared by another contract labels its members in that contract's +// terms. A member it trusts is trusted here as much as the struct value. +contract StructForeignMember { + uint{this} secret; + constructor() { super(); } + + public void f{this}(IPointSource{this} x) { + lock(this) { + P@IPointSource p = x.get(); + secret = p.v; + } + } +} diff --git a/test/contracts/ifcTypechecking/StructForeignMemberLow_W01.scif b/test/contracts/ifcTypechecking/StructForeignMemberLow_W01.scif new file mode 100644 index 0000000..20452f6 --- /dev/null +++ b/test/contracts/ifcTypechecking/StructForeignMemberLow_W01.scif @@ -0,0 +1,13 @@ +import "./IPointSource.scif"; +// A member that the declaring contract labels untrusted is untrusted here too. +contract StructForeignMemberLow_W01 { + uint{this} secret; + constructor() { super(); } + + public void f{this}(IPointSource{this} x) { + lock(this) { + P@IPointSource p = x.get(); + secret = p.u; + } + } +} diff --git a/test/contracts/ifcTypechecking/StructMemberInput.scif b/test/contracts/ifcTypechecking/StructMemberInput.scif new file mode 100644 index 0000000..615b34d --- /dev/null +++ b/test/contracts/ifcTypechecking/StructMemberInput.scif @@ -0,0 +1,12 @@ +// Untrusted data can be stored in a member declared untrusted. +contract StructMemberInput { + struct S { + uint{any} v; + } + S s; + constructor() { super(); } + + public void f{this}(uint{any} x) { + s = new S(x); + } +} diff --git a/test/contracts/ifcTypechecking/StructMemberInputLow_W01.scif b/test/contracts/ifcTypechecking/StructMemberInputLow_W01.scif new file mode 100644 index 0000000..41c12ce --- /dev/null +++ b/test/contracts/ifcTypechecking/StructMemberInputLow_W01.scif @@ -0,0 +1,12 @@ +// Each member of a new struct is checked against its declared label. +contract StructMemberInputLow_W01 { + struct S { + uint{this} v; + } + S s; + constructor() { super(); } + + public void f{this}(uint{any} x) { + s = new S(x); + } +} diff --git a/test/contracts/ifcTypechecking/StructMemberRead.scif b/test/contracts/ifcTypechecking/StructMemberRead.scif new file mode 100644 index 0000000..d89aa84 --- /dev/null +++ b/test/contracts/ifcTypechecking/StructMemberRead.scif @@ -0,0 +1,14 @@ +// A trusted member of a trusted struct can be used as trusted. +contract StructMemberRead { + struct S { + uint{any} v; + uint w; + } + S s; + uint{this} secret; + constructor() { super(); } + + public void f{this}() { + secret = s.w; + } +} diff --git a/test/contracts/ifcTypechecking/StructMemberReadLow_W01.scif b/test/contracts/ifcTypechecking/StructMemberReadLow_W01.scif new file mode 100644 index 0000000..d6611a2 --- /dev/null +++ b/test/contracts/ifcTypechecking/StructMemberReadLow_W01.scif @@ -0,0 +1,14 @@ +// A member is read at its declared label, even in a trusted struct. +contract StructMemberReadLow_W01 { + struct S { + uint{any} v; + uint w; + } + S s; + uint{this} secret; + constructor() { super(); } + + public void f{this}() { + secret = s.v; + } +} diff --git a/test/contracts/ifcTypechecking/StructMemberWrite.scif b/test/contracts/ifcTypechecking/StructMemberWrite.scif new file mode 100644 index 0000000..be081ea --- /dev/null +++ b/test/contracts/ifcTypechecking/StructMemberWrite.scif @@ -0,0 +1,13 @@ +// Untrusted data can be written to an untrusted member of a trusted struct. +contract StructMemberWrite { + struct S { + uint{any} v; + uint w; + } + S s; + constructor() { super(); } + + public void f{this}(uint{any} x) { + s.v = x; + } +} diff --git a/test/contracts/ifcTypechecking/StructMemberWriteLow_W01.scif b/test/contracts/ifcTypechecking/StructMemberWriteLow_W01.scif new file mode 100644 index 0000000..0a7014f --- /dev/null +++ b/test/contracts/ifcTypechecking/StructMemberWriteLow_W01.scif @@ -0,0 +1,13 @@ +// Untrusted data cannot be written to a trusted member. +contract StructMemberWriteLow_W01 { + struct S { + uint{any} v; + uint w; + } + S s; + constructor() { super(); } + + public void f{this}(uint{any} x) { + s.w = x; + } +} diff --git a/test/contracts/ifcTypechecking/StructNestedRead.scif b/test/contracts/ifcTypechecking/StructNestedRead.scif new file mode 100644 index 0000000..84dd39d --- /dev/null +++ b/test/contracts/ifcTypechecking/StructNestedRead.scif @@ -0,0 +1,17 @@ +// A trusted member of a nested struct can be used as trusted. +contract StructNestedRead { + struct In { + uint{any} v; + uint w; + } + struct Out { + In i; + } + Out o; + uint{this} secret; + constructor() { super(); } + + public void f{this}() { + secret = o.i.w; + } +} diff --git a/test/contracts/ifcTypechecking/StructNestedReadLow_W01.scif b/test/contracts/ifcTypechecking/StructNestedReadLow_W01.scif new file mode 100644 index 0000000..2da971a --- /dev/null +++ b/test/contracts/ifcTypechecking/StructNestedReadLow_W01.scif @@ -0,0 +1,17 @@ +// A member of a nested struct is read at its own label. +contract StructNestedReadLow_W01 { + struct In { + uint{any} v; + uint w; + } + struct Out { + In i; + } + Out o; + uint{this} secret; + constructor() { super(); } + + public void f{this}() { + secret = o.i.v; + } +} diff --git a/test/contracts/ifcTypechecking/StructNestedWrite.scif b/test/contracts/ifcTypechecking/StructNestedWrite.scif new file mode 100644 index 0000000..2a65581 --- /dev/null +++ b/test/contracts/ifcTypechecking/StructNestedWrite.scif @@ -0,0 +1,16 @@ +// Untrusted data can be written to an untrusted member of a nested struct. +contract StructNestedWrite { + struct In { + uint{any} v; + uint w; + } + struct Out { + In i; + } + Out o; + constructor() { super(); } + + public void f{this}(uint{any} x) { + o.i.v = x; + } +} diff --git a/test/contracts/ifcTypechecking/StructNestedWriteLow_W01.scif b/test/contracts/ifcTypechecking/StructNestedWriteLow_W01.scif new file mode 100644 index 0000000..80fe83a --- /dev/null +++ b/test/contracts/ifcTypechecking/StructNestedWriteLow_W01.scif @@ -0,0 +1,16 @@ +// Untrusted data cannot be written to a trusted member of a nested struct. +contract StructNestedWriteLow_W01 { + struct In { + uint{any} v; + uint w; + } + struct Out { + In i; + } + Out o; + constructor() { super(); } + + public void f{this}(uint{any} x) { + o.i.w = x; + } +} diff --git a/test/contracts/ifcTypechecking/TrustFactBranch_W01.scif b/test/contracts/ifcTypechecking/TrustFactBranch_W01.scif new file mode 100644 index 0000000..add68c0 --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustFactBranch_W01.scif @@ -0,0 +1,16 @@ +// a may be reassigned on one branch, so after the merge {a} names an unknown principal. +contract TrustFactBranch_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a, address{this} b, bool{this} c) { + if (a => this) { + if (c) { + a = b; + } + uint{a} w = 1; + secret = w; + } + } +} diff --git a/test/contracts/ifcTypechecking/TrustFactFieldCall_W01.scif b/test/contracts/ifcTypechecking/TrustFactFieldCall_W01.scif new file mode 100644 index 0000000..301fa75 --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustFactFieldCall_W01.scif @@ -0,0 +1,17 @@ +// A call may reassign the field owner, so after it {owner} names an unknown principal. +contract TrustFactFieldCall_W01 { + uint{this} secret; + address{this} owner; + + constructor() { super(); owner = sender; } + + public void g{this}() { } + + public void f{this}() { + if (owner => this) { + g(); + uint{owner} w = 1; + secret = w; + } + } +} diff --git a/test/contracts/ifcTypechecking/TrustFactKept.scif b/test/contracts/ifcTypechecking/TrustFactKept.scif new file mode 100644 index 0000000..1e4eb45 --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustFactKept.scif @@ -0,0 +1,21 @@ +// A trust test on a variable holds for its uses until the variable is reassigned. +contract TrustFactKept { + uint{this} secret; + address{this} owner; + + constructor() { super(); owner = sender; } + + public void local{this}(address{this} a) { + if (a => this) { + uint{a} w = 1; + secret = w; + } + } + + public void field{this}() { + if (owner => this) { + uint{owner} w = 1; + secret = w; + } + } +} diff --git a/test/contracts/ifcTypechecking/TrustFactLoop_W01.scif b/test/contracts/ifcTypechecking/TrustFactLoop_W01.scif new file mode 100644 index 0000000..c9bdabc --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustFactLoop_W01.scif @@ -0,0 +1,17 @@ +// a may be reassigned in the loop, so after the loop {a} names an unknown principal. +contract TrustFactLoop_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a, address{this} b, uint{this} n) { + if (a => this) { + while (n > 0) { + a = b; + n = n - 1; + } + uint{a} w = 1; + secret = w; + } + } +} diff --git a/test/contracts/ifcTypechecking/TrustFactReassign_W01.scif b/test/contracts/ifcTypechecking/TrustFactReassign_W01.scif new file mode 100644 index 0000000..7b8d0b7 --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustFactReassign_W01.scif @@ -0,0 +1,14 @@ +// After a is reassigned, {a} names the new principal, which the trust test did not cover. +contract TrustFactReassign_W01 { + uint{this} secret; + + constructor() { super(); } + + public void f{this}(address{this} a, address{this} b) { + if (a => this) { + a = b; + uint{a} w = 1; + secret = w; + } + } +} diff --git a/test/contracts/ifcTypechecking/TrustTestAfterBranch_W01.scif b/test/contracts/ifcTypechecking/TrustTestAfterBranch_W01.scif new file mode 100644 index 0000000..351656a --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustTestAfterBranch_W01.scif @@ -0,0 +1,14 @@ +// The trust test justifies the flow inside its branch only; the same flow +// after the branch must still be rejected. +contract TrustTestAfterBranch_W01 { + uint{this} secret; + uint{this} other; + constructor() { super(); } + + public void f{this}(address{this} a, uint{a} x) { + if (a => this) { + other = x; + } + secret = x; + } +} diff --git a/test/contracts/ifcTypechecking/TrustTestBeforeBranch_W01.scif b/test/contracts/ifcTypechecking/TrustTestBeforeBranch_W01.scif new file mode 100644 index 0000000..95e9446 --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustTestBeforeBranch_W01.scif @@ -0,0 +1,13 @@ +// The same flow before the branch is not justified by the trust test. +contract TrustTestBeforeBranch_W01 { + uint{this} secret; + uint{this} other; + constructor() { super(); } + + public void f{this}(address{this} a, uint{a} x) { + secret = x; + if (a => this) { + other = x; + } + } +} diff --git a/test/contracts/ifcTypechecking/TrustTestTwoBranches.scif b/test/contracts/ifcTypechecking/TrustTestTwoBranches.scif new file mode 100644 index 0000000..2848cf4 --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustTestTwoBranches.scif @@ -0,0 +1,14 @@ +// Both flows are justified by the trust tests that guard them. +contract TrustTestTwoBranches { + uint{this} secret; + constructor() { super(); } + + public void f{this}(address{this} a, uint{a} x) { + if (a => this) { + secret = x; + } + if (a => this) { + secret = x + 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/TrustTestTwoBranches_W01.scif b/test/contracts/ifcTypechecking/TrustTestTwoBranches_W01.scif new file mode 100644 index 0000000..bc24035 --- /dev/null +++ b/test/contracts/ifcTypechecking/TrustTestTwoBranches_W01.scif @@ -0,0 +1,15 @@ +// Each branch's flow is checked under its own trust test: the test on b +// does not justify the flow of data trusted by a. +contract TrustTestTwoBranches_W01 { + uint{this} secret; + constructor() { super(); } + + public void f{this}(address{this} a, address{this} b, uint{a} x) { + if (a => this) { + secret = x; + } + if (b => this) { + secret = x; + } + } +} diff --git a/test/contracts/ifcTypechecking/UncheckedEarlierThrowLow_W01.scif b/test/contracts/ifcTypechecking/UncheckedEarlierThrowLow_W01.scif new file mode 100644 index 0000000..d65ee37 --- /dev/null +++ b/test/contracts/ifcTypechecking/UncheckedEarlierThrowLow_W01.scif @@ -0,0 +1,18 @@ +// An exception from any statement of an unchecked block, not only the last, +// leaves the method on its path. +contract UncheckedEarlierThrowLow_W01 { + uint{this} count; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}() throws (E{this}) { + unchecked { + g(); + count = count + 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/UncheckedEarlierThrowUntrusted.scif b/test/contracts/ifcTypechecking/UncheckedEarlierThrowUntrusted.scif new file mode 100644 index 0000000..eb012d3 --- /dev/null +++ b/test/contracts/ifcTypechecking/UncheckedEarlierThrowUntrusted.scif @@ -0,0 +1,18 @@ +// An exception from an earlier statement of an unchecked block may leave the +// method when it is declared untrusted. +contract UncheckedEarlierThrowUntrusted { + uint{this} count; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}() throws (E{any}) { + unchecked { + g(); + count = count + 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/UncheckedEmpty.scif b/test/contracts/ifcTypechecking/UncheckedEmpty.scif new file mode 100644 index 0000000..2829eea --- /dev/null +++ b/test/contracts/ifcTypechecking/UncheckedEmpty.scif @@ -0,0 +1,11 @@ +// An empty unchecked block completes normally. +contract UncheckedEmpty { + uint{this} count; + constructor() { super(); } + + public void f{this}() { + unchecked { + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/ValueNowTrusted.scif b/test/contracts/ifcTypechecking/ValueNowTrusted.scif new file mode 100644 index 0000000..bf1e140 --- /dev/null +++ b/test/contracts/ifcTypechecking/ValueNowTrusted.scif @@ -0,0 +1,12 @@ +// The ether sent with a call and the block timestamp are trusted data. +contract ValueNowTrusted { + uint{this} paid; + uint{this} paidAt; + + constructor() { super(); } + + public void pay{sender -> this; any}() { + paid = value; + paidAt = now; + } +} diff --git a/test/contracts/ifcTypechecking/Wallet_lock_exception.scif b/test/contracts/ifcTypechecking/Wallet_lock_exception.scif index 0ab7f08..d6df0b1 100644 --- a/test/contracts/ifcTypechecking/Wallet_lock_exception.scif +++ b/test/contracts/ifcTypechecking/Wallet_lock_exception.scif @@ -11,7 +11,7 @@ contract Wallet { // // ... // } constructor() { super(); } - public void withdraw(uint _amount) throws (balanceNotEnough, transferFailure) { + public void withdraw(uint _amount) throws (balanceNotEnough, transferFailure{any}) { uint amount = endorse(_amount, any -> this); if (balances[sender] >= amount) { lock(this) { diff --git a/test/contracts/ifcTypechecking/WhileBodyPcLow_W01.scif b/test/contracts/ifcTypechecking/WhileBodyPcLow_W01.scif new file mode 100644 index 0000000..88367ce --- /dev/null +++ b/test/contracts/ifcTypechecking/WhileBodyPcLow_W01.scif @@ -0,0 +1,16 @@ +import "./ISelfVouching.scif"; +// Control flow after a loop is trusted no more than control flow at the end of +// its body. +contract WhileBodyPcLow_W01 { + uint{this} count; + constructor() { super(); } + + public void f{this}(ISelfVouching{any} x, uint{this} n) { + lock(this) { + while (n > 0) { + x.get(); + } + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/WhileBodyPcTrusted.scif b/test/contracts/ifcTypechecking/WhileBodyPcTrusted.scif new file mode 100644 index 0000000..7ae2879 --- /dev/null +++ b/test/contracts/ifcTypechecking/WhileBodyPcTrusted.scif @@ -0,0 +1,15 @@ +import "./ISelfVouching.scif"; +// Control flow after a loop whose body calls a trusted contract stays trusted. +contract WhileBodyPcTrusted { + uint{this} count; + constructor() { super(); } + + public void f{this}(ISelfVouching{this} x, uint{this} n) { + lock(this) { + while (n > 0) { + x.get(); + } + } + count = count + 1; + } +} diff --git a/test/contracts/ifcTypechecking/WhileThrowLow_W01.scif b/test/contracts/ifcTypechecking/WhileThrowLow_W01.scif new file mode 100644 index 0000000..871623f --- /dev/null +++ b/test/contracts/ifcTypechecking/WhileThrowLow_W01.scif @@ -0,0 +1,17 @@ +// An exception thrown in a loop body leaves the method on its path. +contract WhileThrowLow_W01 { + uint{this} count; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}(uint{this} n) throws (E{this}) { + while (n > 0) { + g(); + count = 1; + } + } +} diff --git a/test/contracts/ifcTypechecking/WhileThrowUntrusted.scif b/test/contracts/ifcTypechecking/WhileThrowUntrusted.scif new file mode 100644 index 0000000..b7ac80d --- /dev/null +++ b/test/contracts/ifcTypechecking/WhileThrowUntrusted.scif @@ -0,0 +1,17 @@ +// An exception thrown in a loop body may leave the method when declared untrusted. +contract WhileThrowUntrusted { + uint{this} count; + exception E(); + constructor() { super(); } + + void g{this}() throws (E{any}) { + throw E(); + } + + public void f{this}(uint{this} n) throws (E{any}) { + while (n > 0) { + g(); + count = 1; + } + } +} diff --git a/test/contracts/multiContract/Dexible.scif b/test/contracts/multiContract/Dexible.scif index 18b1111..a4d2e43 100644 --- a/test/contracts/multiContract/Dexible.scif +++ b/test/contracts/multiContract/Dexible.scif @@ -23,7 +23,7 @@ contract Dexible { constructor() { super(); } public void swap{sender}(IERC20 tokenIn, IERC20 tokenOut, IExchange router, uint amount) - throws (FailedSwap) { + throws (FailedSwap{any}) { emit Swap(tokenIn, tokenOut, router, amount); atomic { tokenIn.approveFrom(sender, address(router), amount); @@ -34,7 +34,7 @@ contract Dexible { } public void multiSwap{sender}(IERC20[] tokens, IExchange[] routers, uint[] amounts) - throws (FailedSwap) { + throws (FailedSwap{any}) { atomic { uint{sender} i = 0; while (i < tokens.length() - 1) { diff --git a/test/contracts/multiContract/DexibleWithEvents.scif b/test/contracts/multiContract/DexibleWithEvents.scif index e6d6bed..88e5010 100644 --- a/test/contracts/multiContract/DexibleWithEvents.scif +++ b/test/contracts/multiContract/DexibleWithEvents.scif @@ -18,12 +18,12 @@ interface IExchange { contract Dexible { exception FailedSwap(); - event Swap(IERC20 tokenIn, IERC20 tokenOut, IExchange router, uint amount); + event Swap(IERC20{any} tokenIn, IERC20{any} tokenOut, IExchange{any} router, uint{any} amount); constructor() { super(); } public void swap{sender}(IERC20 tokenIn, IERC20 tokenOut, IExchange router, uint amount) - throws (FailedSwap) { + throws (FailedSwap{any}) { emit Swap(tokenIn, tokenOut, router, amount); atomic { tokenIn.approveFrom(sender, address(router), amount); @@ -34,7 +34,7 @@ contract Dexible { } public void multiSwap{sender}(IERC20[] tokens, IExchange[] routers, uint[] amounts) - throws (FailedSwap) { + throws (FailedSwap{any}) { atomic { uint{sender} i = 0; while (i < tokens.length() - 1) { diff --git a/test/contracts/multiContract/EthCrossChainManager.scif b/test/contracts/multiContract/EthCrossChainManager.scif deleted file mode 100644 index 7c6b3e8..0000000 --- a/test/contracts/multiContract/EthCrossChainManager.scif +++ /dev/null @@ -1,155 +0,0 @@ -interface IPolyCallee { - // called by PolyNetManager -// public void polyCallBack{any}(bytes args, bytes _fromContractAddr, uint _fromChainId); -// public void polyCallBack{origin}(final address origin, bytes args, bytes _fromContractAddr, uint _fromChainId); - public void polyCallback{this}(final address origin, bytes{origin} args, bytes _fromContractAddr, uint _fromChainId); -} -interface IECCUtils { - struct Header { - uint version; - uint chainId; - uint timestamp; - uint height; - uint consensusData; - uint prevBlockHash; - uint transactionsRoot; - uint crossStatesRoot; - uint blockRoot; - bytes consensusPayload; - address nextBookkeeper; - } - struct ToMerkleValue { - uint txHash; // cross chain txhash - uint fromChainID; - uint crossChainId; - bytes fromContract; - uint toChainId; - address toContract; - bytes args; - } - public bool verifySig{this}(Header{any} header, bytes{any} sigList, address[]{any} keepers, uint{any} m); - public address verifyPubkey{this}(address[]{any} keepers); - public bytes encodeParam{this}(uint{any} paramTxHash, address{any} _this, address{any} _sender, uint{any} toChainId, address{any} toContract, bytes{any} txData); - public uint keccak256{this}(bytes{any} data); - public uint merkleProveValue{this}(bytes{any} proof, uint{any} root); - public ToMerkleValue merkleProve{this}(bytes{any} proof, uint{any} root); - public uint getHeaderHash{this}(Header{any} header); -} -interface IEthCrossChainData { - public address[] getCurEpochConPubKeys(); - public void putCurEpochConPubKeys{this}(address[] keepers); - public uint getCurEpochStartHeight(); - public uint getEthTxHashIndex(); - public void putCurEpochStartHeight{this}(uint height); - public void putEthTxHash{this}(uint hash); - public bool checkIfFromChainTxExist(uint fromChainID, uint txHash); - public void markFromChainTxExist{this}(uint fromChainID, uint txHash); -} -contract EthCrossChainManager { - - address EthCrossChainDataAddress; - address EthECCUtilsAddress; - - constructor(address _eccd, address _cutils) { - EthCrossChainDataAddress = endorse(_eccd, sender->this); - EthECCUtilsAddress = endorse(_cutils, sender->this); - super(); - } - - public void initGenesisBlock(Header@IECCUtils header, bytes pubKeyList) { - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); - address[] keepers = eccd.getCurEpochConPubKeys(); - assert(keepers.length() == 0); - - // Parse header and convit the public keys into nextBookKeeper and compare it with header.nextBookKeeper to verify the validity of signature - address nextBookkeeper = cutils.verifyPubkey(keepers); - endorse([header], sender->this) - when (header.nextBookkeeper == nextBookkeeper) { - // Record current epoch start height and public keys (by storing them in address format) - eccd.putCurEpochStartHeight(header.height); - eccd.putCurEpochConPubKeys(keepers); - } else { - assert(false); - } - } - - public void changeBookKeeper(Header@IECCUtils header, address[] polyChainBKs, bytes sigList) { - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); - // Make sure rawHeader.height is higher than recorded current epoch start height - uint curEpochStartHeight = eccd.getCurEpochStartHeight(); - - // Verify signature of rawHeader comes from pubKeyList - endorse([header, polyChainBKs], sender -> this) - when ((header.height > curEpochStartHeight) && - (header.nextBookkeeper != address(0)) && - (cutils.verifySig(header, sigList, polyChainBKs, polyChainBKs.length() - (polyChainBKs.length() - 1) / 3))) { - - // Convert pubKeyList into ethereum address format and make sure the compound address from the converted ethereum addresses - // equals passed in header.nextBooker - address nextBookKeeper = cutils.verifyPubkey(polyChainBKs); - assert(header.nextBookkeeper == nextBookKeeper); - - // update current epoch start height of Poly chain and current epoch consensus peers book keepers addresses - eccd.putCurEpochStartHeight(header.height); - eccd.putCurEpochConPubKeys(polyChainBKs); - } else { - assert(false); - } - } - - public void crossChain(uint toChainId, address toContract, bytes txData) { - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); - // To help differentiate two txs, the ethTxHashIndex is increasing automatically - uint txHashIndex = eccd.getEthTxHashIndex(); - - uint paramTxHash = txHashIndex; - - // Construct the makeTxParam, and put the hash info storage, to help provide proof of tx existence - bytes rawParam = cutils.encodeParam(paramTxHash, address(this), sender, toChainId, toContract, txData); - - // Must save it in the storage to be included in the proof to be verified. - eccd.putEthTxHash(cutils.keccak256(rawParam)); - } - - public void verifyHeaderAndExecuteTx(bytes proof, Header@IECCUtils header, bytes headerProof, Header@IECCUtils curHeader, bytes headerSig) { - // Load ehereum cross chain data contract - // Get stored consensus public key bytes of current poly chain epoch and deserialize Poly chain consensus public key bytes to address[] - final IEthCrossChainData eccd = IEthCrossChainData(EthCrossChainDataAddress); - final IECCUtils cutils = IECCUtils(EthECCUtilsAddress); - - assert eccd => this; - assert cutils => this; - address[] keepers = eccd.getCurEpochConPubKeys(); - - uint curEpochStartHeight = eccd.getCurEpochStartHeight(); - - uint n = keepers.length(); - if (endorse(header.height, sender->this) >= curEpochStartHeight) { -// It's enough to verify rawHeader signature - assert(cutils.verifySig(header, headerSig, keepers, n - ( n - 1) / 3)); - } else { -// We need to verify the signature of curHeader - assert(cutils.verifySig(curHeader, headerSig, keepers, n - ( n - 1) / 3)); - - uint proveValue = cutils.merkleProveValue(headerProof, curHeader.blockRoot); - assert(cutils.getHeaderHash(header) == proveValue); - } - - // Parse the toMerkleValue struct and make sure the tx has not been processed, then mark this tx as processed - ToMerkleValue@IECCUtils toMerkleValue = cutils.merkleProve(proof, header.crossStatesRoot); - assert(!eccd.checkIfFromChainTxExist(toMerkleValue.fromChainID, toMerkleValue.txHash)); - eccd.markFromChainTxExist(toMerkleValue.fromChainID, toMerkleValue.txHash); - - // Ethereum ChainId is 2, we need to check the transaction is for Ethereum network - assert(toMerkleValue.toChainId == 2); - - // Obtain the targeting contract, so that Ethereum cross chain manager contract can trigger the executation of cross chain tx on Ethereum side - address toContract = toMerkleValue.toContract; - - IPolyCallee callee = IPolyCallee(toContract); - callee.polyCallback(sender, toMerkleValue.args, toMerkleValue.fromContract, toMerkleValue.fromChainID); - } -} \ No newline at end of file diff --git a/test/contracts/regularTypechecking/CallReceiverNotVariable_W01.scif b/test/contracts/regularTypechecking/CallReceiverNotVariable_W01.scif index 1e7cf62..6cf5dfe 100644 --- a/test/contracts/regularTypechecking/CallReceiverNotVariable_W01.scif +++ b/test/contracts/regularTypechecking/CallReceiverNotVariable_W01.scif @@ -1,8 +1,8 @@ -// The receiver of a method call is not a variable. +// The receiver of a method call must be a variable, a member, or an element, +// not the result of another call, even in parentheses. contract CallReceiverNotVariable_W01 { - struct S { CallReceiverNotVariable_W01 c; } - S s; - uint x; - constructor() { super(); x = s.c.g(); } - public uint g() { return 1; } + CallReceiverNotVariable_W01 c; + constructor() { super(); } + public CallReceiverNotVariable_W01 self() { return c; } + public uint g() { return (self()).g(); } } diff --git a/test/contracts/regularTypechecking/DepMapMutableIndex.scif b/test/contracts/regularTypechecking/DepMapMutableIndex.scif new file mode 100644 index 0000000..d228363 --- /dev/null +++ b/test/contracts/regularTypechecking/DepMapMutableIndex.scif @@ -0,0 +1,10 @@ +// A dependent map may be indexed by any address variable, final or not. The +// element's label names the principal the index holds when the map is read. +contract DepMapMutableIndex { + map(address x, uint{x}) m; + uint{this} y; + constructor() { super(); } + public void f{this}(address a) { + y = endorse(m[a], any -> this); + } +} diff --git a/test/contracts/regularTypechecking/DepMapNonFinalIndex_W01.scif b/test/contracts/regularTypechecking/DepMapNonFinalIndex_W01.scif deleted file mode 100644 index 59067f1..0000000 --- a/test/contracts/regularTypechecking/DepMapNonFinalIndex_W01.scif +++ /dev/null @@ -1,9 +0,0 @@ -// A dependent map is indexed by a non-final address. -contract DepMapNonFinalIndex_W01 { - map(address x, uint{x}) m; - uint{this} y; - constructor() { super(); } - public void f{this}(address a) { - y = endorse(m[a], any -> this); - } -} diff --git a/test/contracts/regularTypechecking/ExceptionParamLabelNotPrincipal_W01.scif b/test/contracts/regularTypechecking/ExceptionParamLabelNotPrincipal_W01.scif new file mode 100644 index 0000000..29235f9 --- /dev/null +++ b/test/contracts/regularTypechecking/ExceptionParamLabelNotPrincipal_W01.scif @@ -0,0 +1,6 @@ +// The label of an exception parameter may name only principal-typed parameters +// of the exception. +contract ExceptionParamLabelNotPrincipal_W01 { + exception E(uint n, uint{n} v); + constructor() { super(); } +} diff --git a/test/contracts/regularTypechecking/ExceptionParamLabels.scif b/test/contracts/regularTypechecking/ExceptionParamLabels.scif new file mode 100644 index 0000000..142c47a --- /dev/null +++ b/test/contracts/regularTypechecking/ExceptionParamLabels.scif @@ -0,0 +1,6 @@ +// Exception parameters may be unlabeled, labeled with contract-level principals, +// or labeled with principal-typed parameters of the exception. +contract ExceptionParamLabels { + exception E(address owner, uint{owner} balance, uint{any} needed, uint plain); + constructor() { super(); } +} diff --git a/test/contracts/regularTypechecking/FieldLabelNonFinal_W01.scif b/test/contracts/regularTypechecking/FieldLabelNonFinal_W01.scif new file mode 100644 index 0000000..92660ec --- /dev/null +++ b/test/contracts/regularTypechecking/FieldLabelNonFinal_W01.scif @@ -0,0 +1,6 @@ +// A field's label may name only final variables. +contract FieldLabelNonFinal_W01 { + address owner; + uint{owner} amount; + constructor() { super(); } +} diff --git a/test/contracts/regularTypechecking/FinalFieldAssignInMethod_W01.scif b/test/contracts/regularTypechecking/FinalFieldAssignInMethod_W01.scif new file mode 100644 index 0000000..19b4088 --- /dev/null +++ b/test/contracts/regularTypechecking/FinalFieldAssignInMethod_W01.scif @@ -0,0 +1,6 @@ +// Only the constructor initializes a final field. +contract FinalFieldAssignInMethod_W01 { + final address owner; + constructor() { super(); owner = sender; } + public void f() { owner = sender; } +} diff --git a/test/contracts/regularTypechecking/FinalFieldInitNested_W01.scif b/test/contracts/regularTypechecking/FinalFieldInitNested_W01.scif new file mode 100644 index 0000000..252452b --- /dev/null +++ b/test/contracts/regularTypechecking/FinalFieldInitNested_W01.scif @@ -0,0 +1,11 @@ +// A final field is initialized by a top-level statement of the constructor. +contract FinalFieldInitNested_W01 { + final address owner; + bool flag; + constructor() { + super(); + if (flag) { + owner = sender; + } + } +} diff --git a/test/contracts/regularTypechecking/FinalFieldInitOK.scif b/test/contracts/regularTypechecking/FinalFieldInitOK.scif new file mode 100644 index 0000000..5683c41 --- /dev/null +++ b/test/contracts/regularTypechecking/FinalFieldInitOK.scif @@ -0,0 +1,6 @@ +// A final field may be initialized by the constructor and named in another field's label. +contract FinalFieldInitOK { + final address owner; + uint{owner} amount; + constructor() { super(); owner = sender; } +} diff --git a/test/contracts/regularTypechecking/FinalFieldInitTwice_W01.scif b/test/contracts/regularTypechecking/FinalFieldInitTwice_W01.scif new file mode 100644 index 0000000..0528dfd --- /dev/null +++ b/test/contracts/regularTypechecking/FinalFieldInitTwice_W01.scif @@ -0,0 +1,5 @@ +// A final field is initialized exactly once. +contract FinalFieldInitTwice_W01 { + final address owner; + constructor() { super(); owner = sender; owner = this; } +} diff --git a/test/contracts/regularTypechecking/FinalFieldUninitialized_W01.scif b/test/contracts/regularTypechecking/FinalFieldUninitialized_W01.scif new file mode 100644 index 0000000..34d6066 --- /dev/null +++ b/test/contracts/regularTypechecking/FinalFieldUninitialized_W01.scif @@ -0,0 +1,5 @@ +// A final field must be initialized by the constructor. +contract FinalFieldUninitialized_W01 { + final address owner; + constructor() { super(); } +} diff --git a/test/contracts/regularTypechecking/FinalLocalReassign_W01.scif b/test/contracts/regularTypechecking/FinalLocalReassign_W01.scif new file mode 100644 index 0000000..9c8f9e4 --- /dev/null +++ b/test/contracts/regularTypechecking/FinalLocalReassign_W01.scif @@ -0,0 +1,8 @@ +// A final local cannot be reassigned. +contract FinalLocalReassign_W01 { + constructor() { super(); } + public void f() { + final uint a = 1; + a = 2; + } +} diff --git a/test/contracts/regularTypechecking/FinalParamReassign_W01.scif b/test/contracts/regularTypechecking/FinalParamReassign_W01.scif new file mode 100644 index 0000000..b712aab --- /dev/null +++ b/test/contracts/regularTypechecking/FinalParamReassign_W01.scif @@ -0,0 +1,7 @@ +// A final parameter cannot be reassigned. +contract FinalParamReassign_W01 { + constructor() { super(); } + public void f(final address a) { + a = sender; + } +} diff --git a/test/contracts/regularTypechecking/StructMemberLabelNonFinal_W01.scif b/test/contracts/regularTypechecking/StructMemberLabelNonFinal_W01.scif new file mode 100644 index 0000000..a13e3cd --- /dev/null +++ b/test/contracts/regularTypechecking/StructMemberLabelNonFinal_W01.scif @@ -0,0 +1,9 @@ +// A struct member's label must mean the same thing wherever the struct is +// used, so it may not name a principal that can change. +contract StructMemberLabelNonFinal_W01 { + struct S { + uint{owner} v; + } + address owner; + constructor() { super(); } +} diff --git a/test/contracts/regularTypechecking/StructMemberLabelNotPrincipal_W01.scif b/test/contracts/regularTypechecking/StructMemberLabelNotPrincipal_W01.scif new file mode 100644 index 0000000..3d26fa4 --- /dev/null +++ b/test/contracts/regularTypechecking/StructMemberLabelNotPrincipal_W01.scif @@ -0,0 +1,8 @@ +// A struct member's label may name only principals. +contract StructMemberLabelNotPrincipal_W01 { + struct S { + uint{n} v; + } + uint n; + constructor() { super(); } +} diff --git a/test/contracts/regularTypechecking/StructMemberLabels.scif b/test/contracts/regularTypechecking/StructMemberLabels.scif new file mode 100644 index 0000000..68d307b --- /dev/null +++ b/test/contracts/regularTypechecking/StructMemberLabels.scif @@ -0,0 +1,13 @@ +// Struct members may be unlabeled, or labeled with principals that cannot change. +contract StructMemberLabels { + struct S { + uint{owner} v; + uint{any} u; + uint w; + } + final address{any} owner; + constructor(address o) { + owner = o; + super(); + } +} diff --git a/test/contracts/regularTypechecking/TrustQueryNonFinal_W01.scif b/test/contracts/regularTypechecking/TrustQueryNonFinal.scif similarity index 80% rename from test/contracts/regularTypechecking/TrustQueryNonFinal_W01.scif rename to test/contracts/regularTypechecking/TrustQueryNonFinal.scif index ebd628f..55c0e7e 100644 --- a/test/contracts/regularTypechecking/TrustQueryNonFinal_W01.scif +++ b/test/contracts/regularTypechecking/TrustQueryNonFinal.scif @@ -1,5 +1,5 @@ // A dynamic trust query between non-final addresses. -contract TrustQueryNonFinal_W01 { +contract TrustQueryNonFinal { address a; address b; bool t; diff --git a/test/src/TestIfcTypechecking.java b/test/src/TestIfcTypechecking.java index 7875465..bd8d6cc 100644 --- a/test/src/TestIfcTypechecking.java +++ b/test/src/TestIfcTypechecking.java @@ -53,6 +53,53 @@ public class TestIfcTypechecking { "ifcTypechecking/ClosurePcExStronger", "ifcTypechecking/ClosureLockWeaken", "ifcTypechecking/SenderTrustedAddress", + "ifcTypechecking/ValueNowTrusted", + "ifcTypechecking/TrustFactKept", + "ifcTypechecking/FinalFieldInit", + "ifcTypechecking/InterfaceLocalPrincipal", + "ifcTypechecking/EndorseLabels", + "ifcTypechecking/EmitIFC", + "ifcTypechecking/JoinLabelPrincipal", + "ifcTypechecking/CallTrustedReference", + "ifcTypechecking/TrustTestTwoBranches", + "ifcTypechecking/NewTail", + "ifcTypechecking/NewEndorseTail", + "ifcTypechecking/NewLocked", + "ifcTypechecking/CallResultTrusted", + "ifcTypechecking/CallPcTrusted", + "ifcTypechecking/ExceptionHandlerTrusted", + "ifcTypechecking/ExceptionThrowUntrusted", + "ifcTypechecking/ExceptionData", + "ifcTypechecking/ExceptionExternalTrusted", + "ifcTypechecking/ExceptionDependentLabel", + "ifcTypechecking/WhileBodyPcTrusted", + "ifcTypechecking/EndorseIfBodyPcTrusted", + "ifcTypechecking/EndorseIfThrowUntrusted", + "ifcTypechecking/IfEarlierThrowUntrusted", + "ifcTypechecking/WhileThrowUntrusted", + "ifcTypechecking/HandlerThrowUntrusted", + "ifcTypechecking/RescueAllUntrusted", + "ifcTypechecking/AssertFalseHandler", + "ifcTypechecking/StructMemberInput", + "ifcTypechecking/StructMemberRead", + "ifcTypechecking/StructMemberWrite", + "ifcTypechecking/StructForeignMember", + "ifcTypechecking/StructNestedRead", + "ifcTypechecking/StructNestedWrite", + "ifcTypechecking/StructElementRead", + "ifcTypechecking/StructElementWrite", + "ifcTypechecking/NestedMapWrite", + "basic/StructEx04", + "ifcTypechecking/MapIndexRead", + "ifcTypechecking/MapIndexWrite", + "ifcTypechecking/DepMapKeyRead", + "ifcTypechecking/DepMapUnnamedKeyRead", + "ifcTypechecking/DepMapKeyWrite", + "ifcTypechecking/CallElementReceiver", + "ifcTypechecking/CallMemberReceiver", + "ifcTypechecking/MemberArrayPush", + "ifcTypechecking/UncheckedEarlierThrowUntrusted", + "ifcTypechecking/UncheckedEmpty", }) void testPositive(String contractName) throws Exception { File logDir = new File("./.scif"); @@ -101,6 +148,60 @@ void testPositive(String contractName) throws Exception { "ifcTypechecking/ClosureBindArgLow_W01", "ifcTypechecking/ClosureBindBoundDependent_W01", "ifcTypechecking/ClosureLockOverclaim_W01", + "ifcTypechecking/TrustFactReassign_W01", + "ifcTypechecking/TrustFactBranch_W01", + "ifcTypechecking/TrustFactLoop_W01", + "ifcTypechecking/TrustFactFieldCall_W01", + "ifcTypechecking/NonFinalPrincipalLabel_W01", + "ifcTypechecking/InterfaceLocalPrincipal_W01", + "ifcTypechecking/EndorseFromLabel_W01", + "ifcTypechecking/EndorseToLabel_W01", + "ifcTypechecking/EndorsePcLow_W01", + "ifcTypechecking/EmitArgLow_W01", + "ifcTypechecking/EmitPcLow_W01", + "ifcTypechecking/EmitDependentLow_W01", + "ifcTypechecking/JoinLabelPrincipal_W01", + "ifcTypechecking/CastUnknownContract_W01", + "ifcTypechecking/CallLendsAuthority_W01", + "ifcTypechecking/ClosureCrossContractUntrustedSink_W01", + "ifcTypechecking/TrustTestAfterBranch_W01", + "ifcTypechecking/TrustTestBeforeBranch_W01", + "ifcTypechecking/TrustTestTwoBranches_W01", + "ifcTypechecking/NewUntrusted_W01", + "ifcTypechecking/NewNonTail_W01", + "ifcTypechecking/CallResultUntrusted_W01", + "ifcTypechecking/CallPcUntrusted_W01", + "ifcTypechecking/ExceptionHandlerLow_W01", + "ifcTypechecking/ExceptionThrowLow_W01", + "ifcTypechecking/ExceptionDataLow_W01", + "ifcTypechecking/ExceptionExternalUntrusted_W01", + "ifcTypechecking/ExceptionDependentLabelLow_W01", + "ifcTypechecking/ExceptionDependentThrowLow_W01", + "ifcTypechecking/WhileBodyPcLow_W01", + "ifcTypechecking/EndorseIfBodyPcLow_W01", + "ifcTypechecking/EndorseIfThrowLow_W01", + "ifcTypechecking/IfEarlierThrowLow_W01", + "ifcTypechecking/WhileThrowLow_W01", + "ifcTypechecking/HandlerThrowLow_W01", + "ifcTypechecking/RescueAllLow_W01", + "ifcTypechecking/StructMemberInputLow_W01", + "ifcTypechecking/StructMemberReadLow_W01", + "ifcTypechecking/StructMemberWriteLow_W01", + "ifcTypechecking/StructForeignMemberLow_W01", + "ifcTypechecking/StructNestedReadLow_W01", + "ifcTypechecking/StructNestedWriteLow_W01", + "ifcTypechecking/StructElementReadLow_W01", + "ifcTypechecking/StructElementWriteLow_W01", + "ifcTypechecking/NestedMapWriteLow_W01", + "ifcTypechecking/MapIndexReadLow_W01", + "ifcTypechecking/MapIndexWriteLow_W01", + "ifcTypechecking/DepMapKeyReadLow_W01", + "ifcTypechecking/DepMapUnnamedKeyReadLow_W01", + "ifcTypechecking/DepMapUnnamedKeyWriteLow_W01", + "ifcTypechecking/CallElementReceiverLow_W01", + "ifcTypechecking/CallMemberReceiverLow_W01", + "ifcTypechecking/MemberArrayPushLow_W01", + "ifcTypechecking/UncheckedEarlierThrowLow_W01", // "examples/ERC20", }) void testNegative(String contractName) throws Exception { diff --git a/test/src/TestRegularTypechecking.java b/test/src/TestRegularTypechecking.java index 7055283..bfa0750 100644 --- a/test/src/TestRegularTypechecking.java +++ b/test/src/TestRegularTypechecking.java @@ -49,6 +49,11 @@ public class TestRegularTypechecking { "regularTypechecking/ClosureNoOuterLabelOK", "regularTypechecking/ClosureJoinPcEx", "regularTypechecking/ClosureBinderRegularOK", + "regularTypechecking/FinalFieldInitOK", + "regularTypechecking/ExceptionParamLabels", + "regularTypechecking/StructMemberLabels", + "regularTypechecking/DepMapMutableIndex", + "regularTypechecking/TrustQueryNonFinal", }) void testPositive(String contractName) throws Exception { File logDir = new File("./.scif"); @@ -118,7 +123,6 @@ void testNegative(String contractName) { "regularTypechecking/AttributeNoMember_W01", "regularTypechecking/EmitArity_W01", "regularTypechecking/ThrowArity_W01", - "regularTypechecking/TrustQueryNonFinal_W01", "regularTypechecking/MethodAsValue_W01", "regularTypechecking/CatchUnknownException_W01", "regularTypechecking/CallReceiverUndefined_W01", @@ -130,10 +134,19 @@ void testNegative(String contractName) { "regularTypechecking/CallOptionUnsupported_W01", "regularTypechecking/ThrowsUnknownException_W01", "regularTypechecking/EmptyFile_W01", - "regularTypechecking/DepMapNonFinalIndex_W01", "regularTypechecking/ThrowUndeclared_W01", "regularTypechecking/SubscriptNonCollection_W01", "regularTypechecking/LabelNonPrincipalVar_W01", + "regularTypechecking/FinalFieldUninitialized_W01", + "regularTypechecking/FinalFieldInitTwice_W01", + "regularTypechecking/FinalFieldInitNested_W01", + "regularTypechecking/FinalFieldAssignInMethod_W01", + "regularTypechecking/FinalLocalReassign_W01", + "regularTypechecking/FinalParamReassign_W01", + "regularTypechecking/FieldLabelNonFinal_W01", + "regularTypechecking/ExceptionParamLabelNotPrincipal_W01", + "regularTypechecking/StructMemberLabelNonFinal_W01", + "regularTypechecking/StructMemberLabelNotPrincipal_W01", }) void testReportedError(String contractName) throws Exception { URL input = ClassLoader.getSystemResource(contractName + ".scif");