Skip to content

Commit 5df4e48

Browse files
authored
feat: importModules without loading environment extensions (leanprover#6325)
This PR ensures that environments can be loaded, repeatedly, without executing arbitrary code
1 parent 1ee7e1a commit 5df4e48

File tree

10 files changed

+95
-83
lines changed

10 files changed

+95
-83
lines changed

releases_drafts/environment.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
**Breaking Changes**
2+
3+
* The functions `Lean.Environment.importModules` and `Lean.Environment.finalizeImport` have been extended with a new parameter `loadExts : Bool := false` that enables environment extension state loading.
4+
Their previous behavior corresponds to setting the flag to `true` but is only safe to do in combination with `enableInitializersExecution`; see also the `importModules` docstring.
5+
The new default value `false` ensures the functions can be used correctly multiple times within the same process when environment extension access is not needed.
6+
The wrapper function `Lean.Environment.withImportModules` now always calls `importModules` with `loadExts := false` as it is incompatible with extension loading.

src/Lean/Elab/Import.lean

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def processHeader (header : Syntax) (opts : Options) (messages : MessageLog)
2222
(plugins : Array System.FilePath := #[]) (leakEnv := false)
2323
: IO (Environment × MessageLog) := do
2424
try
25-
let env ← importModules (leakEnv := leakEnv) (headerToImports header) opts trustLevel plugins
25+
let env ←
26+
importModules (leakEnv := leakEnv) (loadExts := true) (headerToImports header) opts trustLevel plugins
2627
pure (env, messages)
2728
catch e =>
2829
let env ← mkEmptyEnvironment

src/Lean/Environment.lean

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,16 +1801,12 @@ private def equivInfo (cinfo₁ cinfo₂ : ConstantInfo) : Bool := Id.run do
18011801
&& tval₁.all == tval₂.all
18021802

18031803
/--
1804-
Construct environment from `importModulesCore` results.
1805-
1806-
If `leakEnv` is true, we mark the environment as persistent, which means it
1807-
will not be freed. We set this when the object would survive until the end of
1808-
the process anyway. In exchange, RC updates are avoided, which is especially
1809-
important when they would be atomic because the environment is shared across
1810-
threads (potentially, storing it in an `IO.Ref` is sufficient for marking it
1811-
as such). -/
1804+
Constructs environment from `importModulesCore` results.
1805+
1806+
See also `importModules` for parameter documentation.
1807+
-/
18121808
def finalizeImport (s : ImportState) (imports : Array Import) (opts : Options) (trustLevel : UInt32 := 0)
1813-
(leakEnv := false) : IO Environment := do
1809+
(leakEnv loadExts : Bool) : IO Environment := do
18141810
let numConsts := s.moduleData.foldl (init := 0) fun numConsts mod =>
18151811
numConsts + mod.constants.size + mod.extraConstNames.size
18161812
let mut const2ModIdx : Std.HashMap Name ModuleIdx := Std.HashMap.emptyWithCapacity (capacity := numConsts)
@@ -1860,38 +1856,56 @@ def finalizeImport (s : ImportState) (imports : Array Import) (opts : Options) (
18601856
18611857
Safety: There are no concurrent accesses to `env` at this point. -/
18621858
env ← unsafe Runtime.markPersistent env
1863-
env ← finalizePersistentExtensions env s.moduleData opts
1864-
if leakEnv then
1865-
/- Ensure the final environment including environment extension states is
1866-
marked persistent as documented.
1867-
1868-
Safety: There are no concurrent accesses to `env` at this point, assuming
1869-
extensions' `addImportFn`s did not spawn any unbound tasks. -/
1870-
env ← unsafe Runtime.markPersistent env
1859+
if loadExts then
1860+
env ← finalizePersistentExtensions env s.moduleData opts
1861+
if leakEnv then
1862+
/- Ensure the final environment including environment extension states is
1863+
marked persistent as documented.
1864+
1865+
Safety: There are no concurrent accesses to `env` at this point, assuming
1866+
extensions' `addImportFn`s did not spawn any unbound tasks. -/
1867+
env ← unsafe Runtime.markPersistent env
18711868
return { env with realizedImportedConsts? := some {
18721869
-- safety: `RealizationContext` is private
18731870
env := unsafe unsafeCast env
18741871
opts
18751872
constsRef := (← IO.mkRef {})
18761873
} }
18771874

1878-
@[export lean_import_modules]
1875+
/--
1876+
Creates environment object from given imports.
1877+
1878+
If `leakEnv` is true, we mark the environment as persistent, which means it will not be freed. We
1879+
set this when the object would survive until the end of the process anyway. In exchange, RC updates
1880+
are avoided, which is especially important when they would be atomic because the environment is
1881+
shared across threads (potentially, storing it in an `IO.Ref` is sufficient for marking it as such).
1882+
1883+
If `loadExts` is true, we initialize the environment extensions using the imported data. Doing so
1884+
may use the interpreter and thus is only safe to do after calling `enableInitializersExecution`; see
1885+
also caveats there. If not set, every extension will have its initial value as its state. While the
1886+
environment's constant map can be accessed without `loadExts`, many functions that take
1887+
`Environment` or are in a monad carrying it such as `CoreM` may not function properly without it.
1888+
-/
18791889
def importModules (imports : Array Import) (opts : Options) (trustLevel : UInt32 := 0)
1880-
(plugins : Array System.FilePath := #[]) (leakEnv := false)
1890+
(plugins : Array System.FilePath := #[]) (leakEnv := false) (loadExts := false)
18811891
: IO Environment := profileitIO "import" opts do
18821892
for imp in imports do
18831893
if imp.module matches .anonymous then
18841894
throw <| IO.userError "import failed, trying to import module with anonymous name"
18851895
withImporting do
18861896
plugins.forM Lean.loadPlugin
18871897
let (_, s) ← importModulesCore imports |>.run
1888-
finalizeImport (leakEnv := leakEnv) s imports opts trustLevel
1898+
finalizeImport (leakEnv := leakEnv) (loadExts := loadExts) s imports opts trustLevel
18891899

18901900
/--
1891-
Create environment object from imports and free compacted regions after calling `act`. No live references to the
1892-
environment object or imported objects may exist after `act` finishes. -/
1893-
unsafe def withImportModules {α : Type} (imports : Array Import) (opts : Options) (trustLevel : UInt32 := 0) (act : Environment → IO α) : IO α := do
1894-
let env ← importModules imports opts trustLevel
1901+
Creates environment object from imports and frees compacted regions after calling `act`. No live
1902+
references to the environment object or imported objects may exist after `act` finishes. As this
1903+
cannot be ruled out after loading environment extensions, `importModules`'s `loadExts` is always
1904+
unset using this function.
1905+
-/
1906+
unsafe def withImportModules {α : Type} (imports : Array Import) (opts : Options)
1907+
(act : Environment → IO α) (trustLevel : UInt32 := 0) : IO α := do
1908+
let env ← importModules (loadExts := false) imports opts trustLevel
18951909
try act env finally env.freeRegions
18961910

18971911
@[inherit_doc Kernel.Environment.enableDiag]

src/lake/Lake/Load/Lean/Elab.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ initialize importEnvCache : IO.Ref (Std.HashMap (Array Import) Environment) ←
3030
def importModulesUsingCache (imports : Array Import) (opts : Options) (trustLevel : UInt32) : IO Environment := do
3131
if let some env := (← importEnvCache.get)[imports]? then
3232
return env
33-
let env ← importModules imports opts trustLevel
33+
let env ← importModules (loadExts := true) imports opts trustLevel
3434
importEnvCache.modify (·.insert imports env)
3535
return env
3636

tests/lean/ctor_layout.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ open Lean
44
open Lean.IR
55

66
unsafe def main : IO Unit :=
7-
withImportModules #[{module := `Lean.Compiler.IR.Basic}] {} 0 fun env => do
7+
withImportModules #[{module := `Lean.Compiler.IR.Basic}] {} fun env => do
88
let ctorLayout ← IO.ofExcept $ getCtorLayout env `Lean.IR.Expr.reuse;
99
ctorLayout.fieldInfo.forM $ fun finfo => IO.println (format finfo);
1010
IO.println "---";

tests/lean/run/instances.lean

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ open Lean.Meta
66
instance : ToFormat InstanceEntry where
77
format e := format e.val
88

9-
unsafe def tst1 : IO Unit :=
10-
withImportModules #[{module := `Lean}] {} 0 fun env => do
11-
let aux : MetaM Unit := do
12-
let insts ← getGlobalInstancesIndex
13-
IO.println (format insts)
14-
discard <| aux.run |>.toIO { fileName := "", fileMap := default } { env := env }
9+
unsafe def tst1 : IO Unit := do
10+
let env ← importModules (loadExts := true) #[{module := `Lean}] {}
11+
let aux : MetaM Unit := do
12+
let insts ← getGlobalInstancesIndex
13+
assert! insts.size > 0
14+
IO.println (format insts)
15+
discard <| aux.run |>.toIO { fileName := "", fileMap := default } { env := env }
1516

1617
#eval tst1

tests/lean/run/instuniv.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Lean
33
open Lean
44

55
unsafe def tst : IO Unit :=
6-
withImportModules #[{module := `Init.Data.Array}] {} 0 fun env =>
6+
withImportModules #[{module := `Init.Data.Array}] {} fun env =>
77
match env.find? `Array.foldl with
88
| some info => do
99
IO.println (info.instantiateTypeLevelParams [levelZero, levelZero])

tests/lean/run/meta1.lean

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ import Lean.Meta
33
open Lean
44
open Lean.Meta
55

6-
unsafe def tstInferType (mods : Array Name) (e : Expr) : IO Unit :=
7-
withImportModules (mods.map $ fun m => {module := m}) {} 0 fun env => do
8-
let (type, _, _) ← (inferType e : MetaM _).toIO { fileName := "", fileMap := default } { env := env } {} {};
9-
IO.println (toString e ++ " : " ++ toString type)
10-
11-
unsafe def tstWHNF (mods : Array Name) (e : Expr) (t := TransparencyMode.default) : IO Unit :=
12-
withImportModules (mods.map $ fun m => {module := m}) {} 0 fun env => do
13-
let (s, _, _) ← (whnf e : MetaM _).toIO { fileName := "", fileMap := default } { env := env };
14-
IO.println (toString e ++ " ==> " ++ toString s)
15-
16-
unsafe def tstIsProp (mods : Array Name) (e : Expr) : IO Unit :=
17-
withImportModules (mods.map $ fun m => {module := m}) {} 0 fun env => do
6+
def tstInferType (e : Expr) : CoreM Unit := do
7+
let env ← getEnv
8+
let (type, _, _) ← (inferType e : MetaM _).toIO { fileName := "", fileMap := default } { env := env } {} {};
9+
IO.println (toString e ++ " : " ++ toString type)
10+
11+
def tstWHNF (e : Expr) : CoreM Unit := do
12+
let env ← getEnv
13+
let (s, _, _) ← (whnf e : MetaM _).toIO { fileName := "", fileMap := default } { env := env };
14+
IO.println (toString e ++ " ==> " ++ toString s)
15+
16+
unsafe def tstIsProp (e : Expr) : CoreM Unit := do
17+
let env ← getEnv
1818
let (b, _, _) ← (isProp e : MetaM _).toIO { fileName := "", fileMap := default } { env := env };
1919
IO.println (toString e ++ ", isProp: " ++ toString b)
2020

@@ -26,15 +26,15 @@ mkAppN map #[nat, bool]
2626

2727
/-- info: List.map.{1, 1} Nat Bool : (Nat -> Bool) -> (List.{1} Nat) -> (List.{1} Bool) -/
2828
#guard_msgs in
29-
#eval tstInferType #[`Init.Data.List] t1
29+
#eval tstInferType t1
3030

3131
def t2 : Expr :=
3232
let prop := mkSort levelZero;
3333
mkForall `x BinderInfo.default prop prop
3434

3535
/-- info: Prop -> Prop : Type -/
3636
#guard_msgs in
37-
#eval tstInferType #[`Init.Core] t2
37+
#eval tstInferType t2
3838

3939
def t3 : Expr :=
4040
let nat := mkConst `Nat [];
@@ -45,7 +45,7 @@ mkForall `x BinderInfo.default nat p
4545

4646
/-- info: forall (x : Nat), Nat.le x 0 : Prop -/
4747
#guard_msgs in
48-
#eval tstInferType #[`Init.Data.Nat] t3
48+
#eval tstInferType t3
4949

5050
def t4 : Expr :=
5151
let nat := mkConst `Nat [];
@@ -54,19 +54,15 @@ mkLambda `x BinderInfo.default nat p
5454

5555
/-- info: fun (x : Nat) => Nat.succ x : Nat -> Nat -/
5656
#guard_msgs in
57-
#eval tstInferType #[`Init.Core] t4
57+
#eval tstInferType t4
5858

5959
def t5 : Expr :=
6060
let add := mkConst `Nat.add [];
6161
mkAppN add #[mkLit (Literal.natVal 3), mkLit (Literal.natVal 5)]
6262

6363
/-- info: Nat.add 3 5 ==> 8 -/
6464
#guard_msgs in
65-
#eval tstWHNF #[`Init.Data.Nat] t5
66-
67-
/-- info: Nat.add 3 5 ==> 8 -/
68-
#guard_msgs in
69-
#eval tstWHNF #[`Init.Data.Nat] t5 TransparencyMode.reducible
65+
#eval tstWHNF t5
7066

7167
set_option pp.all true
7268
/-- info: @List.cons.{0} Nat : Nat → List.{0} Nat → List.{0} Nat -/
@@ -89,23 +85,23 @@ mkAppN map #[nat, nat, f, xs]
8985
info: List.map.{1, 1} Nat Nat (fun (x : Nat) => Nat.add x 1) (List.cons.{0} Nat 1 (List.cons.{0} Nat 4 (List.nil.{0} Nat))) : List.{1} Nat
9086
-/
9187
#guard_msgs in
92-
#eval tstInferType #[`Init.Data.List] t6
88+
#eval tstInferType t6
9389

9490
/--
9591
info: List.map.{1, 1} Nat Nat (fun (x : Nat) => Nat.add x 1) (List.cons.{0} Nat 1 (List.cons.{0} Nat 4 (List.nil.{0} Nat))) ==> List.cons.{1} Nat ((fun (x : Nat) => Nat.add x 1) 1) (List.map.{1, 1} Nat Nat (fun (x : Nat) => Nat.add x 1) (List.cons.{0} Nat 4 (List.nil.{0} Nat)))
9692
-/
9793
#guard_msgs in
98-
#eval tstWHNF #[`Init.Data.List] t6
94+
#eval tstWHNF t6
9995

10096
/-- info: Prop : Type -/
10197
#guard_msgs in
102-
#eval tstInferType #[] $ mkSort levelZero
98+
#eval tstInferType $ mkSort levelZero
10399

104100
/--
105101
info: fun {a : Type} (x : a) (xs : List.{0} a) => xs : forall {a : Type}, a -> (List.{0} a) -> (List.{0} a)
106102
-/
107103
#guard_msgs in
108-
#eval tstInferType #[`Init.Data.List] $ mkLambda `a BinderInfo.implicit (mkSort levelOne) (mkLambda `x BinderInfo.default (mkBVar 0) (mkLambda `xs BinderInfo.default (mkApp (mkConst `List [levelZero]) (mkBVar 1)) (mkBVar 0)))
104+
#eval tstInferType $ mkLambda `a BinderInfo.implicit (mkSort levelOne) (mkLambda `x BinderInfo.default (mkBVar 0) (mkLambda `xs BinderInfo.default (mkApp (mkConst `List [levelZero]) (mkBVar 1)) (mkBVar 0)))
109105

110106
def t7 : Expr :=
111107
let nat := mkConst `Nat [];
@@ -114,11 +110,11 @@ mkLet `x nat one one
114110

115111
/-- info: let x : Nat := 1; 1 : Nat -/
116112
#guard_msgs in
117-
#eval tstInferType #[`Init.Core] $ t7
113+
#eval tstInferType $ t7
118114

119115
/-- info: let x : Nat := 1; 1 ==> 1 -/
120116
#guard_msgs in
121-
#eval tstWHNF #[`Init.Core] $ t7
117+
#eval tstWHNF $ t7
122118

123119
def t8 : Expr :=
124120
let nat := mkConst `Nat [];
@@ -128,35 +124,35 @@ mkLet `x nat one (mkAppN add #[one, mkBVar 0])
128124

129125
/-- info: let x : Nat := 1; Nat.add 1 x : Nat -/
130126
#guard_msgs in
131-
#eval tstInferType #[`Init.Core] $ t8
127+
#eval tstInferType $ t8
132128

133129
/-- info: let x : Nat := 1; Nat.add 1 x ==> 2 -/
134130
#guard_msgs in
135-
#eval tstWHNF #[`Init.Core] $ t8
131+
#eval tstWHNF $ t8
136132

137133
def t9 : Expr :=
138134
let nat := mkConst `Nat [];
139135
mkLet `a (mkSort levelOne) nat (mkForall `x BinderInfo.default (mkBVar 0) (mkBVar 1))
140136

141137
/-- info: let a : Type := Nat; a -> a : Type -/
142138
#guard_msgs in
143-
#eval tstInferType #[`Init.Core] $ t9
139+
#eval tstInferType $ t9
144140

145141
/-- info: let a : Type := Nat; a -> a ==> Nat -> Nat -/
146142
#guard_msgs in
147-
#eval tstWHNF #[`Init.Core] $ t9
143+
#eval tstWHNF $ t9
148144

149145
/-- info: 10 : Nat -/
150146
#guard_msgs in
151-
#eval tstInferType #[`Init.Core] $ mkLit (Literal.natVal 10)
147+
#eval tstInferType $ mkLit (Literal.natVal 10)
152148

153149
/-- info: "hello" : String -/
154150
#guard_msgs in
155-
#eval tstInferType #[`Init.Core] $ mkLit (Literal.strVal "hello")
151+
#eval tstInferType $ mkLit (Literal.strVal "hello")
156152

157153
/-- info: [mdata 10] : Nat -/
158154
#guard_msgs in
159-
#eval tstInferType #[`Init.Core] $ mkMData {} $ mkLit (Literal.natVal 10)
155+
#eval tstInferType $ mkMData {} $ mkLit (Literal.natVal 10)
160156

161157
def t10 : Expr :=
162158
let nat := mkConst `Nat [];
@@ -165,39 +161,39 @@ mkLambda `a BinderInfo.default nat (mkApp refl (mkBVar 0))
165161

166162
/-- info: fun (a : Nat) => Eq.refl.{1} Nat a : forall (a : Nat), Eq.{1} Nat a a -/
167163
#guard_msgs in
168-
#eval tstInferType #[`Init.Core] t10
164+
#eval tstInferType t10
169165

170166
/-- info: fun (a : Nat) => Eq.refl.{1} Nat a, isProp: false -/
171167
#guard_msgs in
172-
#eval tstIsProp #[`Init.Core] t10
168+
#eval tstIsProp t10
173169

174170
/-- info: And True True, isProp: true -/
175171
#guard_msgs in
176-
#eval tstIsProp #[`Init.Core] (mkAppN (mkConst `And []) #[mkConst `True [], mkConst `True []])
172+
#eval tstIsProp (mkAppN (mkConst `And []) #[mkConst `True [], mkConst `True []])
177173

178174
/-- info: And, isProp: false -/
179175
#guard_msgs in
180-
#eval tstIsProp #[`Init.Core] (mkConst `And [])
176+
#eval tstIsProp (mkConst `And [])
181177

182178
-- Example where isPropQuick fails
183179
/-- info: id.{0} Prop (And True True), isProp: true -/
184180
#guard_msgs in
185-
#eval tstIsProp #[`Init.Core] (mkAppN (mkConst `id [levelZero]) #[mkSort levelZero, mkAppN (mkConst `And []) #[mkConst `True [], mkConst
181+
#eval tstIsProp (mkAppN (mkConst `id [levelZero]) #[mkSort levelZero, mkAppN (mkConst `And []) #[mkConst `True [], mkConst
186182
`True []]])
187183

188184
/-- info: Eq.{1} Nat 0 1, isProp: true -/
189185
#guard_msgs in
190-
#eval tstIsProp #[`Init.Core] (mkAppN (mkConst `Eq [levelOne]) #[mkConst `Nat [], mkLit (Literal.natVal 0), mkLit (Literal.natVal 1)])
186+
#eval tstIsProp (mkAppN (mkConst `Eq [levelOne]) #[mkConst `Nat [], mkLit (Literal.natVal 0), mkLit (Literal.natVal 1)])
191187

192188
/-- info: forall (x : Nat), Eq.{1} Nat x 1, isProp: true -/
193189
#guard_msgs in
194-
#eval tstIsProp #[`Init.Core] $
190+
#eval tstIsProp $
195191
mkForall `x BinderInfo.default (mkConst `Nat [])
196192
(mkAppN (mkConst `Eq [levelOne]) #[mkConst `Nat [], mkBVar 0, mkLit (Literal.natVal 1)])
197193

198194
/-- info: (fun (x : Nat) => Eq.{1} Nat x 1) 0, isProp: true -/
199195
#guard_msgs in
200-
#eval tstIsProp #[`Init.Core] $
196+
#eval tstIsProp $
201197
mkApp
202198
(mkLambda `x BinderInfo.default (mkConst `Nat [])
203199
(mkAppN (mkConst `Eq [levelOne]) #[mkConst `Nat [], mkBVar 0, mkLit (Literal.natVal 1)]))

tests/lean/run/meta3.lean

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ do let v? ← getExprMVarAssignment? m.mvarId!;
2121
| some v => pure v
2222
| none => throwError "metavariable is not assigned")
2323

24-
unsafe def run (mods : Array Name) (x : MetaM Unit) (opts : Options := dbgOpt) : IO Unit :=
25-
withImportModules (mods.map $ fun m => {module := m}) {} 0 fun env => do
26-
let x : MetaM Unit := do { x; printTraces };
27-
discard $ x.toIO { options := opts, fileName := "", fileMap := default } { env := env };
28-
pure ()
29-
3024
def nat := mkConst `Nat
3125
def succ := mkConst `Nat.succ
3226
def add := mkAppN (mkConst `Add.add [levelZero]) #[nat, mkConst `Nat.add]

tests/pkg/user_attr_app/Main.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ def tst : MetaM Unit := do
1313

1414
unsafe def main : IO Unit := do
1515
initSearchPath (← Lean.findSysroot)
16-
withImportModules #[{ module := `UserAttr.Tst : Import }] {} 0 fun env => pure ()
16+
withImportModules #[{ module := `UserAttr.Tst : Import }] {} fun env => pure ()

0 commit comments

Comments
 (0)