Skip to content

Commit 3120d45

Browse files
committed
Type checked struct operations must not deadlock the compiler
1 parent facde14 commit 3120d45

2 files changed

Lines changed: 32 additions & 13 deletions

File tree

lib/elixir/src/elixir_map.erl

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ validate_struct(Atom, _) when is_atom(Atom) -> true;
126126
validate_struct(_, _) -> false.
127127

128128
assert_struct_info_if_not_function(Meta, Name, Assocs, #{function := nil} = E) ->
129-
case maybe_load_struct_info(Meta, Name, E) of
129+
case maybe_load_struct_info(Meta, Name, hard, E) of
130130
{ok, Info} ->
131131
[lists:any(fun(Field) -> ?key(Field, field) =:= Key end, Info) orelse
132132
function_error(Meta, E, ?MODULE, {unknown_key_for_struct, Name, Key})
@@ -140,12 +140,24 @@ assert_struct_info_if_not_function(_Meta, _Name, _Assocs, _E) ->
140140
ok.
141141

142142
maybe_load_struct_info(Meta, Name, E) ->
143+
maybe_load_struct_info(Meta, Name, soft, E).
144+
145+
maybe_load_struct_info(Meta, Name, Mode, E) ->
143146
try
144-
case is_open(Name, Meta, E) andalso lookup_struct_info_from_data_tables(Name) of
147+
InContext = in_context(Name, E),
148+
149+
case (InContext orelse is_compiling_struct(Meta, Name, Mode, E))
150+
andalso lookup_struct_info_from_data_tables(Name) of
145151
%% If I am accessing myself and there is no attribute,
146152
%% don't invoke the fallback to avoid calling loaded code.
147153
false when ?key(E, module) =:= Name -> nil;
148-
false -> Name:'__info__'(struct);
154+
false ->
155+
%% We already attempted to wait for the struct (unless InContext),
156+
%% so we only invoke '__info__' if already loaded or InContext
157+
case InContext orelse erlang:module_loaded(Name) of
158+
true -> Name:'__info__'(struct);
159+
false -> nil
160+
end;
149161
InfoList -> InfoList
150162
end
151163
of
@@ -165,7 +177,8 @@ lookup_struct_info_from_data_tables(Module) ->
165177

166178
load_struct(Meta, Name, Assocs, E) ->
167179
try
168-
case is_open(Name, Meta, E) andalso elixir_def:external_for(Meta, Name, '__struct__', 1, [def]) of
180+
case (in_context(Name, E) orelse is_compiling_struct(Meta, Name, hard, E)) andalso
181+
elixir_def:external_for(Meta, Name, '__struct__', 1, [def]) of
169182
%% If I am accessing myself and there is no __struct__ function,
170183
%% don't invoke the fallback to avoid calling loaded code.
171184
false when ?key(E, module) =:= Name ->
@@ -221,17 +234,17 @@ assert_and_trace_struct_assocs(Meta, Name, Assocs, E) ->
221234
elixir_env:trace({struct_expansion, Meta, Name, Keys}, E),
222235
Keys.
223236

224-
is_open(Name, Meta, E) ->
225-
in_context(Name, E) orelse ((code:ensure_loaded(Name) /= {module, Name}) andalso wait_for_struct(Name, Meta, E)).
237+
is_compiling_struct(Meta, Name, Mode, E) ->
238+
(code:ensure_loaded(Name) /= {module, Name}) andalso wait_for_struct(Meta, Name, Mode, E).
226239

227240
in_context(Name, E) ->
228241
%% We also include the current module because it won't be present
229242
%% in context module in case the module name is defined dynamically.
230243
lists:member(Name, [?key(E, module) | ?key(E, context_modules)]).
231244

232-
wait_for_struct(Module, Meta, E) ->
245+
wait_for_struct(Meta, Module, Mode, E) ->
233246
(erlang:get(elixir_compiler_info) /= undefined) andalso
234-
('Elixir.Kernel.ErrorHandler':ensure_compiled(Module, struct, hard, elixir_utils:get_line(Meta, E)) =:= found).
247+
('Elixir.Kernel.ErrorHandler':ensure_compiled(Module, struct, Mode, elixir_utils:get_line(Meta, E)) =:= found).
235248

236249
struct_undef(Name, E) ->
237250
case in_context(Name, E) andalso (?key(E, function) == nil) of

lib/elixir/src/elixir_module.erl

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,19 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
159159
{DataSet, DataBag} = Tables,
160160

161161
try
162+
CompilerInfo = erlang:get(elixir_compiler_info),
162163
put_compiler_modules([Module | CompilerModules]),
163164
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
164165
CheckerInfo = checker_info(),
165166
{BeamLocation, Forceload} = beam_location(ModuleAsCharlist),
166167

167168
{Binary, PersistedAttributes, Autoload} =
168169
elixir_erl_compiler:spawn(fun() ->
170+
case CompilerInfo of
171+
undefined -> undefined;
172+
_ -> erlang:put(elixir_compiler_info, CompilerInfo)
173+
end,
174+
169175
PersistedAttributes = ets:lookup_element(DataBag, persisted_attributes, 2),
170176
Attributes = attributes(DataSet, DataBag, PersistedAttributes),
171177
AllDefinitions = elixir_def:fetch_definitions(Module, E),
@@ -574,7 +580,7 @@ bag_lookup_element(Table, Name, Pos) ->
574580
end.
575581

576582
beam_location(ModuleAsCharlist) ->
577-
case get(elixir_compiler_dest) of
583+
case erlang:get(elixir_compiler_dest) of
578584
{Dest, Forceload} when is_binary(Dest) ->
579585
{filename:join(elixir_utils:characters_to_list(Dest), ModuleAsCharlist ++ ".beam"),
580586
Forceload};
@@ -585,7 +591,7 @@ beam_location(ModuleAsCharlist) ->
585591
%% Integration with elixir_compiler that makes the module available
586592

587593
checker_info() ->
588-
case get(elixir_checker_info) of
594+
case erlang:get(elixir_checker_info) of
589595
undefined -> {self(), nil};
590596
_ -> 'Elixir.Module.ParallelChecker':get()
591597
end.
@@ -601,19 +607,19 @@ spawn_parallel_checker(CheckerInfo, Module, ModuleMap, Signatures, BeamLocation)
601607
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, Signatures, BeamLocation, Log).
602608

603609
make_module_available(Module, Binary, Loaded) ->
604-
case get(elixir_module_binaries) of
610+
case erlang:get(elixir_module_binaries) of
605611
Current when is_list(Current) ->
606612
put(elixir_module_binaries, [{Module, Binary} | Current]);
607613
_ ->
608614
ok
609615
end,
610616

611-
case get(elixir_compiler_info) of
617+
case erlang:get(elixir_compiler_info) of
612618
undefined ->
613619
ok;
614620
{PID, _} ->
615621
Ref = make_ref(),
616-
PID ! {module_available, self(), Ref, get(elixir_compiler_file), Module, Binary, Loaded},
622+
PID ! {module_available, self(), Ref, erlang:get(elixir_compiler_file), Module, Binary, Loaded},
617623
receive {Ref, ack} -> ok end
618624
end.
619625

0 commit comments

Comments
 (0)