1use std::sync::Arc;
4
5use sim_kernel::Symbol;
6use sim_lib_core::{DynamicSourcePolicy, RequestOrigin};
7use sim_lib_namespace::{IdentitySpecifierPolicy, SourceModulePolicy};
8
9pub fn javascript_module_policy() -> SourceModulePolicy {
14 javascript_module_policy_with_codec(Symbol::qualified("codec", "javascript"))
15}
16
17pub fn javascript_module_policy_with_codec(codec: Symbol) -> SourceModulePolicy {
19 SourceModulePolicy::new(codec, Arc::new(IdentitySpecifierPolicy))
20}
21
22pub fn dynamic_javascript_policy() -> DynamicSourcePolicy {
24 dynamic_javascript_policy_with_codec(Symbol::qualified("codec", "javascript"))
25}
26
27pub fn dynamic_javascript_policy_with_codec(codec: Symbol) -> DynamicSourcePolicy {
29 DynamicSourcePolicy::new(
30 codec,
31 RequestOrigin::new(Symbol::qualified("javascript", "dynamic-source")),
32 )
33}
34
35#[cfg(test)]
36mod tests {
37 use std::{collections::BTreeMap, sync::RwLock};
38
39 use sim_codec_lisp::LispCodecLib;
40 use sim_kernel::{
41 CapabilitySet, ClassId, ClassRef, CodecId, Cx, DefaultFactory, Dir, EagerPolicy, Error,
42 Expr, Object, ObjectCompat, ReadPolicy, Result, Table, TrustLevel, Value,
43 read_eval_capability,
44 };
45 use sim_lib_core::SourceAuthority;
46 use sim_lib_namespace::{ModuleResolutionOutcome, module_load_capability};
47 use sim_shape::{AnyShape, ExactExprShape};
48
49 use super::*;
50
51 fn context() -> (Cx, sim_kernel::GrantSeat) {
52 let (mut cx, seat) = Cx::new_seated(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
53 cx.load_lib(&LispCodecLib::new(CodecId(71)).unwrap())
54 .unwrap();
55 (cx, seat)
56 }
57
58 fn trusted() -> ReadPolicy {
59 ReadPolicy {
60 trust: TrustLevel::TrustedSource,
61 capabilities: CapabilitySet::new().grant(read_eval_capability()),
62 }
63 }
64
65 #[derive(Default)]
66 struct MemoryDir(RwLock<BTreeMap<Symbol, Value>>);
67
68 impl MemoryDir {
69 fn source(&self, cx: &mut Cx, name: &str, source: &str) {
70 self.0.write().unwrap().insert(
71 Symbol::new(name),
72 cx.factory().string(source.to_owned()).unwrap(),
73 );
74 }
75 }
76
77 impl Object for MemoryDir {
78 fn display(&self, _cx: &mut Cx) -> Result<String> {
79 Ok("javascript-memory-root".to_owned())
80 }
81 fn as_any(&self) -> &dyn std::any::Any {
82 self
83 }
84 }
85 impl ObjectCompat for MemoryDir {
86 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
87 cx.factory()
88 .class_stub(ClassId(0), Symbol::qualified("test", "JavascriptRoot"))
89 }
90 fn as_table_impl(&self) -> Option<&dyn Table> {
91 Some(self)
92 }
93 fn as_dir(&self) -> Option<&dyn Dir> {
94 Some(self)
95 }
96 }
97 impl Table for MemoryDir {
98 fn backend_symbol(&self) -> Symbol {
99 Symbol::qualified("test", "javascript-root")
100 }
101 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
102 self.0
103 .read()
104 .unwrap()
105 .get(&key)
106 .cloned()
107 .map_or_else(|| cx.factory().nil(), Ok)
108 }
109 fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
110 self.0.write().unwrap().insert(key, value);
111 Ok(())
112 }
113 fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
114 Ok(self.0.read().unwrap().contains_key(&key))
115 }
116 fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
117 self.0
118 .write()
119 .unwrap()
120 .remove(&key)
121 .map_or_else(|| cx.factory().nil(), Ok)
122 }
123 fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
124 Ok(self.0.read().unwrap().keys().cloned().collect())
125 }
126 fn entries(&self, _cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
127 Ok(self
128 .0
129 .read()
130 .unwrap()
131 .iter()
132 .map(|(k, v)| (k.clone(), v.clone()))
133 .collect())
134 }
135 fn len(&self, _cx: &mut Cx) -> Result<usize> {
136 Ok(self.0.read().unwrap().len())
137 }
138 fn clear(&self, _cx: &mut Cx) -> Result<()> {
139 self.0.write().unwrap().clear();
140 Ok(())
141 }
142 }
143 impl Dir for MemoryDir {
144 fn mkdir(&self, _cx: &mut Cx, _name: Symbol) -> Result<Value> {
145 Err(Error::Eval("nested dirs unsupported".into()))
146 }
147 fn opendir(&self, _cx: &mut Cx, _name: Symbol) -> Result<Option<Value>> {
148 Ok(None)
149 }
150 fn rmdir(&self, cx: &mut Cx, _name: Symbol) -> Result<Value> {
151 cx.factory().nil()
152 }
153 fn is_dir(&self, _cx: &mut Cx, _name: Symbol) -> Result<bool> {
154 Ok(false)
155 }
156 }
157
158 fn authority(requires: Vec<sim_kernel::CapabilityName>) -> SourceAuthority {
159 SourceAuthority::new(trusted(), requires, CapabilitySet::new()).unwrap()
160 }
161
162 #[test]
163 fn esm_uses_shared_live_lifecycle_supplied_roots_and_cached_failures() {
164 let (mut cx, seat) = context();
165 seat.grant(&mut cx, read_eval_capability()).unwrap();
166 seat.grant(&mut cx, module_load_capability()).unwrap();
167 let root = Arc::new(MemoryDir::default());
168 root.source(&mut cx, "answer.mjs", "42");
169 root.source(&mut cx, "broken.mjs", "(");
170 let modules = javascript_module_policy_with_codec(Symbol::qualified("codec", "lisp"));
171 let first = modules
172 .load(
173 &mut cx,
174 Symbol::new("modules"),
175 root.clone(),
176 "answer.mjs",
177 authority(vec![module_load_capability()]),
178 )
179 .unwrap();
180 let imported = modules
181 .dynamic_import(
182 &mut cx,
183 Symbol::new("modules"),
184 root.clone(),
185 Some(first.identity().clone()),
186 "./answer.mjs",
187 authority(vec![module_load_capability()]),
188 )
189 .unwrap();
190 assert_eq!(first.identity(), imported.identity());
191 assert_eq!(
192 first
193 .default_export()
194 .get()
195 .unwrap()
196 .object()
197 .display(&mut cx)
198 .unwrap(),
199 "42"
200 );
201 assert!(
202 modules
203 .load(
204 &mut cx,
205 Symbol::new("modules"),
206 root.clone(),
207 "broken.mjs",
208 authority(Vec::new())
209 )
210 .is_err()
211 );
212 root.source(&mut cx, "broken.mjs", "41");
213 assert!(
214 modules
215 .load(
216 &mut cx,
217 Symbol::new("modules"),
218 root,
219 "broken.mjs",
220 authority(Vec::new())
221 )
222 .is_err()
223 );
224 assert_eq!(
225 modules
226 .receipts()
227 .unwrap()
228 .iter()
229 .map(|r| r.outcome)
230 .collect::<Vec<_>>(),
231 vec![
232 ModuleResolutionOutcome::Linked,
233 ModuleResolutionOutcome::CacheHit,
234 ModuleResolutionOutcome::DecodeFailed,
235 ModuleResolutionOutcome::DecodeFailed,
236 ]
237 );
238 }
239
240 #[test]
241 fn dynamic_source_rejects_ambient_authority() {
242 let (mut cx, seat) = context();
243 let dynamic = dynamic_javascript_policy_with_codec(Symbol::qualified("codec", "lisp"));
244 let denied = SourceAuthority::new(
245 ReadPolicy {
246 trust: TrustLevel::Untrusted,
247 capabilities: CapabilitySet::new(),
248 },
249 Vec::new(),
250 CapabilitySet::new(),
251 )
252 .unwrap_err();
253 assert!(matches!(
254 denied,
255 Error::TrustDenied { .. } | Error::CapabilityDenied { .. }
256 ));
257
258 seat.grant(&mut cx, read_eval_capability()).unwrap();
259 let denied = dynamic
260 .evaluate_text(
261 &mut cx,
262 "42",
263 authority(Vec::new()),
264 Arc::new(ExactExprShape::new(Expr::String("not-42".into()))),
265 )
266 .unwrap_err();
267 assert!(matches!(denied, Error::WrongShape { .. }));
268 let value = dynamic
269 .evaluate_text(&mut cx, "42", authority(Vec::new()), Arc::new(AnyShape))
270 .unwrap();
271 assert_eq!(value.object().display(&mut cx).unwrap(), "42");
272 }
273}