Skip to content

Commit 354a350

Browse files
committed
feat: safer #eval, and #eval!
previously, `#eval` would happily evaluate expressions that contain `sorry`, either explicitly or because of failing tactics. In conjunction with operations like array access this can lead to the lean process crashing, which isn't particularly great. So how `#eval` will refuse to run code that (transitively) depends on the `sorry` axiom (using the same code as `#print axioms`). If the user really wants to run it, they can use `#eval!`.
1 parent 5d632a9 commit 354a350

File tree

6 files changed

+116
-37
lines changed

6 files changed

+116
-37
lines changed

src/Lean/Elab/BuiltinCommand.lean

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
55
-/
66
prelude
77
import Lean.Util.CollectLevelParams
8+
import Lean.Util.CollectAxioms
89
import Lean.Meta.Reduce
910
import Lean.Elab.DeclarationRange
1011
import Lean.Elab.Eval
@@ -340,8 +341,7 @@ private def mkRunEval (e : Expr) : MetaM Expr := do
340341
let instVal ← mkEvalInstCore ``Lean.Eval e
341342
instantiateMVars (mkAppN (mkConst ``Lean.runEval [u]) #[α, instVal, mkSimpleThunk e])
342343

343-
unsafe def elabEvalUnsafe : CommandElab
344-
| `(#eval%$tk $term) => do
344+
unsafe def elabEvalCoreUnsafe (bang : Bool) (tk term : Syntax): CommandElabM Unit := do
345345
let declName := `_eval
346346
let addAndCompile (value : Expr) : TermElabM Unit := do
347347
let value ← Term.levelMVarToParam (← instantiateMVars value)
@@ -358,6 +358,13 @@ unsafe def elabEvalUnsafe : CommandElab
358358
}
359359
Term.ensureNoUnassignedMVars decl
360360
addAndCompile decl
361+
-- Check for sorry axioms
362+
let checkSorry (declName : Name) : MetaM Unit := do
363+
unless bang do
364+
let axioms ← collectAxioms declName
365+
if axioms.contains ``sorryAx then
366+
throwError ("Cannot evaluate expression that depends on the `sorry` axiom.\nUse `#eval!` to " ++
367+
"evaluate nevertheless (which may cause lean to crash).")
361368
-- Elaborate `term`
362369
let elabEvalTerm : TermElabM Expr := do
363370
let e ← Term.elabTerm term none
@@ -386,6 +393,7 @@ unsafe def elabEvalUnsafe : CommandElab
386393
else
387394
let e ← mkRunMetaEval e
388395
addAndCompile e
396+
checkSorry declName
389397
let act ← evalConst (Environment → Options → IO (String × Except IO.Error Environment)) declName
390398
pure <| Sum.inr act
391399
match act with
@@ -402,6 +410,7 @@ unsafe def elabEvalUnsafe : CommandElab
402410
-- modify e to `runEval e`
403411
let e ← mkRunEval (← elabEvalTerm)
404412
addAndCompile e
413+
checkSorry declName
405414
let act ← evalConst (IO (String × Except IO.Error Unit)) declName
406415
let (out, res) ← liftM (m := IO) act
407416
logInfoAt tk out
@@ -412,10 +421,19 @@ unsafe def elabEvalUnsafe : CommandElab
412421
elabMetaEval
413422
else
414423
elabEval
424+
425+
@[implemented_by elabEvalCoreUnsafe]
426+
opaque elabEvalCore (bang : Bool) (tk term : Syntax): CommandElabM Unit
427+
428+
@[builtin_command_elab «eval»]
429+
def elabEval : CommandElab
430+
| `(#eval%$tk $term) => elabEvalCore false tk term
415431
| _ => throwUnsupportedSyntax
416432

417-
@[builtin_command_elab «eval», implemented_by elabEvalUnsafe]
418-
opaque elabEval : CommandElab
433+
@[builtin_command_elab evalBang]
434+
def elabEvalBang : CommandElab
435+
| `(#eval!%$tk $term) => elabEvalCore true tk term
436+
| _ => throwUnsupportedSyntax
419437

420438
private def checkImportsForRunCmds : CommandElabM Unit := do
421439
unless (← getEnv).contains ``CommandElabM do

src/Lean/Elab/Print.lean

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Leonardo de Moura
55
-/
66
prelude
7-
import Lean.Util.FoldConsts
87
import Lean.Meta.Eqns
8+
import Lean.Util.CollectAxioms
99
import Lean.Elab.Command
1010

1111
namespace Lean.Elab.Command
@@ -120,40 +120,12 @@ private def printId (id : Syntax) : CommandElabM Unit := do
120120
| `(#print%$tk $s:str) => logInfoAt tk s.getString
121121
| _ => throwError "invalid #print command"
122122

123-
namespace CollectAxioms
124-
125-
structure State where
126-
visited : NameSet := {}
127-
axioms : Array Name := #[]
128-
129-
abbrev M := ReaderT Environment $ StateM State
130-
131-
partial def collect (c : Name) : M Unit := do
132-
let collectExpr (e : Expr) : M Unit := e.getUsedConstants.forM collect
133-
let s ← get
134-
unless s.visited.contains c do
135-
modify fun s => { s with visited := s.visited.insert c }
136-
let env ← read
137-
match env.find? c with
138-
| some (ConstantInfo.axiomInfo _) => modify fun s => { s with axioms := s.axioms.push c }
139-
| some (ConstantInfo.defnInfo v) => collectExpr v.type *> collectExpr v.value
140-
| some (ConstantInfo.thmInfo v) => collectExpr v.type *> collectExpr v.value
141-
| some (ConstantInfo.opaqueInfo v) => collectExpr v.type *> collectExpr v.value
142-
| some (ConstantInfo.quotInfo _) => pure ()
143-
| some (ConstantInfo.ctorInfo v) => collectExpr v.type
144-
| some (ConstantInfo.recInfo v) => collectExpr v.type
145-
| some (ConstantInfo.inductInfo v) => collectExpr v.type *> v.ctors.forM collect
146-
| none => pure ()
147-
148-
end CollectAxioms
149-
150123
private def printAxiomsOf (constName : Name) : CommandElabM Unit := do
151-
let env ← getEnv
152-
let (_, s) := ((CollectAxioms.collect constName).run env).run {}
153-
if s.axioms.isEmpty then
124+
let axioms ← collectAxioms constName
125+
if axioms.isEmpty then
154126
logInfo m!"'{constName}' does not depend on any axioms"
155127
else
156-
logInfo m!"'{constName}' depends on axioms: {s.axioms.qsort Name.lt |>.toList}"
128+
logInfo m!"'{constName}' depends on axioms: {axioms.qsort Name.lt |>.toList}"
157129

158130
@[builtin_command_elab «printAxioms»] def elabPrintAxioms : CommandElab
159131
| `(#print%$tk axioms $id) => withRef tk do

src/Lean/Parser/Command.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ structure Pair (α : Type u) (β : Type v) : Type (max u v) where
437437
"#check_failure " >> termParser -- Like `#check`, but succeeds only if term does not type check
438438
@[builtin_command_parser] def eval := leading_parser
439439
"#eval " >> termParser
440+
@[builtin_command_parser] def evalBang := leading_parser
441+
"#eval! " >> termParser
440442
@[builtin_command_parser] def synth := leading_parser
441443
"#synth " >> termParser
442444
@[builtin_command_parser] def exit := leading_parser

src/Lean/Util/CollectAxioms.lean

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/-
2+
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Leonardo de Moura
5+
-/
6+
prelude
7+
import Lean.MonadEnv
8+
import Lean.Util.FoldConsts
9+
10+
namespace Lean
11+
12+
namespace CollectAxioms
13+
14+
15+
structure State where
16+
visited : NameSet := {}
17+
axioms : Array Name := #[]
18+
19+
abbrev M := ReaderT Environment $ StateM State
20+
21+
partial def collect (c : Name) : M Unit := do
22+
let collectExpr (e : Expr) : M Unit := e.getUsedConstants.forM collect
23+
let s ← get
24+
unless s.visited.contains c do
25+
modify fun s => { s with visited := s.visited.insert c }
26+
let env ← read
27+
match env.find? c with
28+
| some (ConstantInfo.axiomInfo _) => modify fun s => { s with axioms := s.axioms.push c }
29+
| some (ConstantInfo.defnInfo v) => collectExpr v.type *> collectExpr v.value
30+
| some (ConstantInfo.thmInfo v) => collectExpr v.type *> collectExpr v.value
31+
| some (ConstantInfo.opaqueInfo v) => collectExpr v.type *> collectExpr v.value
32+
| some (ConstantInfo.quotInfo _) => pure ()
33+
| some (ConstantInfo.ctorInfo v) => collectExpr v.type
34+
| some (ConstantInfo.recInfo v) => collectExpr v.type
35+
| some (ConstantInfo.inductInfo v) => collectExpr v.type *> v.ctors.forM collect
36+
| none => pure ()
37+
38+
end CollectAxioms
39+
40+
def collectAxioms [Monad m] [MonadEnv m] (constName : Name) : m (Array Name) := do
41+
let env ← getEnv
42+
let (_, s) := ((CollectAxioms.collect constName).run env).run {}
43+
pure s.axioms
44+
45+
end Lean

stage0/src/stdlib_flags.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ options get_default_options() {
88
// switch to `true` for ABI-breaking changes affecting meta code
99
opts = opts.update({"interpreter", "prefer_native"}, false);
1010
// switch to `true` for changing built-in parsers used in quotations
11-
opts = opts.update({"internal", "parseQuotWithCurrentStage"}, false);
11+
opts = opts.update({"internal", "parseQuotWithCurrentStage"}, true);
1212
// toggling `parseQuotWithCurrentStage` may also require toggling the following option if macros/syntax
1313
// with custom precheck hooks were affected
1414
opts = opts.update({"quotPrecheck"}, true);

tests/lean/run/1697.lean

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
/--
3+
error: tactic 'decide' proved that the proposition
4+
False
5+
is false
6+
---
7+
error: Cannot evaluate expression that depends on the `sorry` axiom.
8+
Use `#eval!` to evaluate nevertheless (which may cause lean to crash).
9+
-/
10+
#guard_msgs in
11+
#eval show Nat from False.rec (by decide)
12+
13+
/--
14+
warning: declaration uses 'sorry'
15+
---
16+
error: Cannot evaluate expression that depends on the `sorry` axiom.
17+
Use `#eval!` to evaluate nevertheless (which may cause lean to crash).
18+
-/
19+
#guard_msgs in
20+
#eval #[1,2,3][2]'sorry
21+
22+
/-
23+
24+
Uncomment after stage0 update
25+
26+
/--
27+
warning: declaration uses 'sorry'
28+
---
29+
info: 3
30+
-/
31+
#guard_msgs in
32+
#eval! #[1,2,3][2]'sorry
33+
34+
/--
35+
warning: declaration uses 'sorry'
36+
---
37+
info: 3
38+
-/
39+
#guard_msgs in
40+
#eval! (#[1,2,3].pop)[2]'sorry
41+
42+
-/

0 commit comments

Comments
 (0)