Skip to content

Commit 974467a

Browse files
committed
Handle more non-determinism in update, filter and vdus
Expand non-determinism handling to update handlers, filters, and vdus.
1 parent c9731e1 commit 974467a

File tree

2 files changed

+90
-12
lines changed

2 files changed

+90
-12
lines changed

src/couch_quickjs/src/couch_quickjs_scanner_plugin.erl

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,16 @@ process_ddoc(#st{} = St, DbName, #doc{} = DDoc0) ->
210210
Views = maps:get(?VIEWS, DDoc, undefined),
211211
Clouseau = maps:get(?CLOUSEAU, DDoc, undefined),
212212
Nouveau = maps:get(?NOUVEAU, DDoc, undefined),
213+
Filters = maps:get(?FILTERS, DDoc, undefined),
214+
Updates = maps:get(?UPDATES, DDoc, undefined),
215+
Vdu = maps:get(?VDU, DDoc, undefined),
213216
lib_load(St1, Views),
214-
views_load(St1, valid_views(Views)),
217+
views_load(St1, views(Views)),
215218
clouseau_load(St1, indexes(Clouseau)),
216219
nouveau_load(St1, indexes(Nouveau)),
217-
filters_load(St1, maps:get(?FILTERS, DDoc, undefined)),
218-
updates_load(St1, maps:get(?UPDATES, DDoc, undefined)),
219-
vdu_load(St1, maps:get(?VDU, DDoc, undefined)),
220+
filters_load(St1, filters(Filters)),
221+
updates_load(St1, updates(Updates)),
222+
vdu_load(St1, vdu(Vdu)),
220223
St2 = start_or_reset_procs(St1),
221224
teach_ddoc_validate(St2, DDocId, DDoc),
222225
St2#st{ddocs = DDocs#{DDocId => DDoc}}
@@ -239,11 +242,11 @@ process_ddoc_functions(#st{} = St, Db, DocId, JsonDoc) ->
239242
DDocFun = fun(DDocId, #{} = DDoc) ->
240243
try
241244
Filters = maps:get(?FILTERS, DDoc, undefined),
242-
filter_doc_validate(St, DDocId, Filters, JsonDoc),
243245
VDU = maps:get(?VDU, DDoc, undefined),
244-
vdu_doc_validate(St, DDocId, VDU, JsonDoc),
245246
Updates = maps:get(?UPDATES, DDoc, undefined),
246-
update_doc_validate(St, DDocId, Updates, JsonDoc)
247+
filter_doc_validate(St, DDocId, filters(Filters), JsonDoc),
248+
vdu_doc_validate(St, DDocId, vdu(VDU), JsonDoc),
249+
update_doc_validate(St, DDocId, updates(Updates), JsonDoc)
247250
catch
248251
throw:{validate, Error} ->
249252
Meta = #{sid => SId, db => Db, ddoc => DDocId, doc => DocId},
@@ -286,7 +289,7 @@ views_validate(DDocId, #{?VIEWS := Views}, {Db, #st{} = St0}) when
286289
#st{sid = SId, docs = Docs} = St,
287290
try
288291
lib_load(St, Views),
289-
ViewList = lists:sort(maps:to_list(valid_views(Views))),
292+
ViewList = lists:sort(maps:to_list(views(Views))),
290293
case ViewList of
291294
[_ | _] ->
292295
Fun = fun({Name, #{?MAP := Src}}) -> add_fun_load(St, Name, Src) end,
@@ -475,7 +478,7 @@ start_or_reset_sm_proc(#st{sm_proc = #proc{} = Proc} = St) ->
475478
start_or_reset_sm_proc(St#st{sm_proc = undefined})
476479
end.
477480

478-
valid_views(#{} = Views) ->
481+
views(#{} = Views) ->
479482
Fun = fun
480483
(?LIB, _) ->
481484
false;
@@ -487,7 +490,7 @@ valid_views(#{} = Views) ->
487490
false
488491
end,
489492
maps:filter(Fun, Views);
490-
valid_views(_) ->
493+
views(_) ->
491494
#{}.
492495

493496
indexes(#{} = Indexes) ->
@@ -504,6 +507,32 @@ indexes(#{} = Indexes) ->
504507
indexes(_) ->
505508
#{}.
506509

510+
updates(#{} = Updates) ->
511+
Fun = fun
512+
(<<_/binary>>, <<FunSrc/binary>>) -> no_indeterminism(FunSrc);
513+
(_, _) -> false
514+
end,
515+
maps:filter(Fun, Updates);
516+
updates(_) ->
517+
#{}.
518+
519+
filters(#{} = Filters) ->
520+
Fun = fun
521+
(<<_/binary>>, <<FunSrc/binary>>) -> no_indeterminism(FunSrc);
522+
(_, _) -> false
523+
end,
524+
maps:filter(Fun, Filters);
525+
filters(_) ->
526+
#{}.
527+
528+
vdu(<<FunSrc/binary>>) ->
529+
case no_indeterminism(FunSrc) of
530+
true -> FunSrc;
531+
false -> undefined
532+
end;
533+
vdu(_) ->
534+
undefined.
535+
507536
% Math.random(), Date.now() or new Date() will always show as false postives
508537
%
509538
no_indeterminism(<<FunSrc/binary>>) ->

src/couch_quickjs/test/couch_quickjs_scanner_plugin_tests.erl

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ couch_quickjs_scanner_plugin_test_() ->
2525
?TDEF_FE(t_filter_map, 10),
2626
?TDEF_FE(t_map_only, 10),
2727
?TDEF_FE(t_vdu_only, 10),
28+
?TDEF_FE(t_non_deterministic_vdu, 10),
2829
?TDEF_FE(t_filter_only, 10),
2930
?TDEF_FE(t_filter_with_expected_error, 10),
3031
?TDEF_FE(t_empty_ddoc, 10),
@@ -205,6 +206,39 @@ t_vdu_only({_, DbName}) ->
205206
ok
206207
end.
207208

209+
t_non_deterministic_vdu({_, DbName}) ->
210+
ok = add_doc(DbName, ?DDOC1, ddoc_non_deterministic_vdu(#{})),
211+
meck:reset(couch_scanner_server),
212+
meck:reset(?PLUGIN),
213+
config:set("couch_scanner_plugins", atom_to_list(?PLUGIN), "true", false),
214+
wait_exit(10000),
215+
?assertEqual(1, num_calls(start, 2)),
216+
case couch_server:with_spidermonkey() of
217+
true ->
218+
?assertEqual(1, num_calls(complete, 1)),
219+
?assertEqual(2, num_calls(checkpoint, 1)),
220+
?assertEqual(1, num_calls(db, ['_', DbName])),
221+
?assertEqual(1, num_calls(ddoc, ['_', DbName, '_'])),
222+
?assert(num_calls(shards, 2) >= 1),
223+
DbOpenedCount = num_calls(db_opened, 2),
224+
?assert(DbOpenedCount >= 2),
225+
?assertEqual(1, num_calls(doc_id, ['_', ?DOC1, '_'])),
226+
?assertEqual(1, num_calls(doc_id, ['_', ?DOC2, '_'])),
227+
?assertEqual(1, num_calls(doc_id, ['_', ?DOC3, '_'])),
228+
?assertEqual(1, num_calls(doc_id, ['_', ?DOC4, '_'])),
229+
?assertEqual(1, num_calls(doc_id, ['_', ?DOC5, '_'])),
230+
?assert(num_calls(doc, 3) >= 5),
231+
DbClosingCount = num_calls(db_closing, 2),
232+
?assertEqual(DbOpenedCount, DbClosingCount),
233+
?assertEqual(0, couch_stats:sample([couchdb, query_server, process_error_exits])),
234+
?assertEqual(0, couch_stats:sample([couchdb, query_server, process_errors])),
235+
?assertEqual(0, couch_stats:sample([couchdb, query_server, process_exits])),
236+
% start, complete and no vdu warning because it has a Math.random(), so 2 total
237+
?assertEqual(2, log_calls(warning));
238+
false ->
239+
ok
240+
end.
241+
208242
t_filter_only({_, DbName}) ->
209243
ok = add_doc(DbName, ?DDOC1, ddoc_filter(#{})),
210244
meck:reset(couch_scanner_server),
@@ -463,6 +497,19 @@ ddoc_vdu(Doc) ->
463497
>>
464498
}.
465499

500+
ddoc_non_deterministic_vdu(Doc) ->
501+
Doc#{
502+
validate_doc_update => <<
503+
"function(newdoc, olddoc, userctx, sec){\n"
504+
" if (Math.random() > 0.5) {\n"
505+
" throw('forbidden');\n"
506+
" } else {\n"
507+
" return true\n"
508+
" }\n"
509+
"}"
510+
>>
511+
}.
512+
466513
ddoc_filter(Doc) ->
467514
Doc#{
468515
filters => #{
@@ -481,7 +528,8 @@ ddoc_filter(Doc) ->
481528
" if (doc.a == req.query.foo) {return true;} \n"
482529
" return false; \n"
483530
"}\n"
484-
>>
531+
>>,
532+
f_non_deterministic => <<"function(doc, req) {return new Date();}}">>
485533
}
486534
}.
487535

@@ -661,7 +709,8 @@ ddoc_update(Doc) ->
661709
" }\n"
662710
"}"
663711
>>,
664-
u3 => <<"function(doc, req) {throw('Potato')}">>
712+
u3 => <<"function(doc, req) {throw('Potato')}">>,
713+
u4_non_deterministic => <<"function(doc, req) {throw(Date.now())}">>
665714
}
666715
}.
667716

0 commit comments

Comments
 (0)