mt-lang 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/AUTHORS +3 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/README.md +1208 -0
- data/Rakefile +332 -0
- data/bin/mtc +8 -0
- data/bin/profile-mtc-checks +133 -0
- data/bin/tracy-profiler +0 -0
- data/docs/build-guide.md +536 -0
- data/docs/index.html +2778 -0
- data/docs/language-design.md +1519 -0
- data/docs/language-manual.md +1534 -0
- data/lib/milk_tea/base.rb +49 -0
- data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
- data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
- data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
- data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
- data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
- data/lib/milk_tea/bindings/bindgen.rb +207 -0
- data/lib/milk_tea/bindings/cli.rb +121 -0
- data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
- data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
- data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
- data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
- data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
- data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
- data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
- data/lib/milk_tea/bindings/steamworks.rb +629 -0
- data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
- data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
- data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
- data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
- data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
- data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
- data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
- data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
- data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
- data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
- data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
- data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
- data/lib/milk_tea/bindings.rb +23 -0
- data/lib/milk_tea/core/ast.rb +311 -0
- data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
- data/lib/milk_tea/core/binding_types.rb +18 -0
- data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
- data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
- data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
- data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
- data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
- data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
- data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
- data/lib/milk_tea/core/c_backend/statements.rb +563 -0
- data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
- data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
- data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
- data/lib/milk_tea/core/c_backend.rb +287 -0
- data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
- data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
- data/lib/milk_tea/core/compile_time.rb +329 -0
- data/lib/milk_tea/core/control_flow/builder.rb +582 -0
- data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
- data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
- data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
- data/lib/milk_tea/core/control_flow/graph.rb +90 -0
- data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
- data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
- data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
- data/lib/milk_tea/core/control_flow/termination.rb +31 -0
- data/lib/milk_tea/core/control_flow.rb +34 -0
- data/lib/milk_tea/core/cst.rb +48 -0
- data/lib/milk_tea/core/cst_builder.rb +19 -0
- data/lib/milk_tea/core/ir.rb +85 -0
- data/lib/milk_tea/core/keywords.rb +97 -0
- data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
- data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
- data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
- data/lib/milk_tea/core/lexer/indentation.rb +62 -0
- data/lib/milk_tea/core/lexer/numbers.rb +96 -0
- data/lib/milk_tea/core/lexer/recovery.rb +32 -0
- data/lib/milk_tea/core/lexer/strings.rb +167 -0
- data/lib/milk_tea/core/lexer/symbols.rb +71 -0
- data/lib/milk_tea/core/lexer/trivia.rb +53 -0
- data/lib/milk_tea/core/lexer.rb +430 -0
- data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
- data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
- data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
- data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
- data/lib/milk_tea/core/lowering/async.rb +714 -0
- data/lib/milk_tea/core/lowering/block.rb +1052 -0
- data/lib/milk_tea/core/lowering/calls.rb +1565 -0
- data/lib/milk_tea/core/lowering/declarations.rb +214 -0
- data/lib/milk_tea/core/lowering/dyn.rb +206 -0
- data/lib/milk_tea/core/lowering/events.rb +1054 -0
- data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
- data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
- data/lib/milk_tea/core/lowering/functions.rb +242 -0
- data/lib/milk_tea/core/lowering/loops.rb +1087 -0
- data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
- data/lib/milk_tea/core/lowering/proc.rb +419 -0
- data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
- data/lib/milk_tea/core/lowering/scans.rb +220 -0
- data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
- data/lib/milk_tea/core/lowering/utils.rb +1453 -0
- data/lib/milk_tea/core/lowering.rb +378 -0
- data/lib/milk_tea/core/module_binder.rb +181 -0
- data/lib/milk_tea/core/module_loader/errors.rb +21 -0
- data/lib/milk_tea/core/module_loader.rb +479 -0
- data/lib/milk_tea/core/module_path_resolver.rb +153 -0
- data/lib/milk_tea/core/module_roots.rb +88 -0
- data/lib/milk_tea/core/parser/attributes.rb +71 -0
- data/lib/milk_tea/core/parser/blocks.rb +119 -0
- data/lib/milk_tea/core/parser/declarations.rb +749 -0
- data/lib/milk_tea/core/parser/expressions.rb +624 -0
- data/lib/milk_tea/core/parser/recovery.rb +131 -0
- data/lib/milk_tea/core/parser/statements.rb +756 -0
- data/lib/milk_tea/core/parser/types.rb +271 -0
- data/lib/milk_tea/core/parser.rb +400 -0
- data/lib/milk_tea/core/prelude_installer.rb +29 -0
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
- data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
- data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
- data/lib/milk_tea/core/pretty_printer.rb +17 -0
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
- data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
- data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
- data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
- data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
- data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
- data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
- data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
- data/lib/milk_tea/core/token.rb +26 -0
- data/lib/milk_tea/core/token_stream.rb +30 -0
- data/lib/milk_tea/core/types/layout.rb +243 -0
- data/lib/milk_tea/core/types/predicates.rb +609 -0
- data/lib/milk_tea/core/types/registry.rb +83 -0
- data/lib/milk_tea/core/types/types.rb +1696 -0
- data/lib/milk_tea/core/types/visitor.rb +442 -0
- data/lib/milk_tea/core.rb +25 -0
- data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
- data/lib/milk_tea/dap/protocol.rb +58 -0
- data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
- data/lib/milk_tea/dap/server/debug_map.rb +291 -0
- data/lib/milk_tea/dap/server/handlers.rb +374 -0
- data/lib/milk_tea/dap/server/launch.rb +261 -0
- data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
- data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
- data/lib/milk_tea/dap/server/utilities.rb +160 -0
- data/lib/milk_tea/dap/server/wire.rb +55 -0
- data/lib/milk_tea/dap/server.rb +131 -0
- data/lib/milk_tea/dap/session.rb +152 -0
- data/lib/milk_tea/dap.rb +6 -0
- data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
- data/lib/milk_tea/lsp/diagnostics.rb +611 -0
- data/lib/milk_tea/lsp/protocol.rb +104 -0
- data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
- data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
- data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
- data/lib/milk_tea/lsp/server/completion.rb +1099 -0
- data/lib/milk_tea/lsp/server/configuration.rb +167 -0
- data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
- data/lib/milk_tea/lsp/server/definition.rb +779 -0
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
- data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
- data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
- data/lib/milk_tea/lsp/server/formatting.rb +575 -0
- data/lib/milk_tea/lsp/server/hover.rb +1465 -0
- data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
- data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
- data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
- data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
- data/lib/milk_tea/lsp/server/progress.rb +47 -0
- data/lib/milk_tea/lsp/server/references.rb +433 -0
- data/lib/milk_tea/lsp/server/rename.rb +598 -0
- data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
- data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
- data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
- data/lib/milk_tea/lsp/server/utilities.rb +520 -0
- data/lib/milk_tea/lsp/server.rb +415 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
- data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
- data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
- data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
- data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
- data/lib/milk_tea/lsp/workspace/store.rb +154 -0
- data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
- data/lib/milk_tea/lsp/workspace.rb +127 -0
- data/lib/milk_tea/lsp.rb +13 -0
- data/lib/milk_tea/packages/atomic_write.rb +63 -0
- data/lib/milk_tea/packages/dependency_solver.rb +194 -0
- data/lib/milk_tea/packages/graph.rb +139 -0
- data/lib/milk_tea/packages/lock.rb +421 -0
- data/lib/milk_tea/packages/manager_cli.rb +485 -0
- data/lib/milk_tea/packages/manifest.rb +356 -0
- data/lib/milk_tea/packages/manifest_editor.rb +134 -0
- data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
- data/lib/milk_tea/packages/registry_store.rb +420 -0
- data/lib/milk_tea/packages/services.rb +41 -0
- data/lib/milk_tea/packages/source_cache.rb +71 -0
- data/lib/milk_tea/packages/source_fetcher.rb +124 -0
- data/lib/milk_tea/packages/source_resolver.rb +389 -0
- data/lib/milk_tea/packages/version.rb +164 -0
- data/lib/milk_tea/packages.rb +16 -0
- data/lib/milk_tea/tooling/asset_pack.rb +141 -0
- data/lib/milk_tea/tooling/build.rb +1124 -0
- data/lib/milk_tea/tooling/build_cache.rb +240 -0
- data/lib/milk_tea/tooling/cli.rb +2688 -0
- data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
- data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
- data/lib/milk_tea/tooling/debug_map.rb +248 -0
- data/lib/milk_tea/tooling/docs_app.rb +369 -0
- data/lib/milk_tea/tooling/error_formatter.rb +86 -0
- data/lib/milk_tea/tooling/formatter.rb +787 -0
- data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
- data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
- data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
- data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
- data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
- data/lib/milk_tea/tooling/linter/rules.rb +593 -0
- data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
- data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
- data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
- data/lib/milk_tea/tooling/linter.rb +798 -0
- data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
- data/lib/milk_tea/tooling/public/css/docs.css +166 -0
- data/lib/milk_tea/tooling/public/js/docs.js +94 -0
- data/lib/milk_tea/tooling/run.rb +408 -0
- data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
- data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
- data/lib/milk_tea/tooling/views/404.erb +7 -0
- data/lib/milk_tea/tooling/views/index.erb +87 -0
- data/lib/milk_tea/tooling/views/layout.erb +69 -0
- data/lib/milk_tea/tooling/views/module.erb +97 -0
- data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
- data/lib/milk_tea/tooling.rb +20 -0
- data/lib/milk_tea.rb +8 -0
- metadata +426 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class Linter
|
|
5
|
+
module LinterReleaseRules
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# Entry point — called from visit_function and proc expr in full_tier mode.
|
|
9
|
+
def emit_owning_release_warnings(function_or_body)
|
|
10
|
+
body = function_or_body.is_a?(Array) ? function_or_body : function_or_body.body
|
|
11
|
+
return unless body && !body.empty?
|
|
12
|
+
|
|
13
|
+
cfg_available = false
|
|
14
|
+
if @sema_facts
|
|
15
|
+
cfg_available = begin
|
|
16
|
+
analysis = profile_phase("rule.owning_release.cfg") { statement_flow_analysis(body) }
|
|
17
|
+
analysis || false
|
|
18
|
+
rescue StandardError
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if cfg_available
|
|
24
|
+
emit_owning_release_cfg_warnings(cfg_available, body)
|
|
25
|
+
else
|
|
26
|
+
# No CFG — emit struct-type leaks via simple pattern-matching
|
|
27
|
+
leak_names = check_owning_release_leaks(body)
|
|
28
|
+
emit_owning_release_leak_warnings(leak_names)
|
|
29
|
+
end
|
|
30
|
+
# Always run own[T] checks (AST-based, works with or without CFG)
|
|
31
|
+
own_leaks = check_own_release_leaks(body)
|
|
32
|
+
emit_owning_release_leak_warnings(own_leaks)
|
|
33
|
+
|
|
34
|
+
double_names_lines = check_owning_release_double(body)
|
|
35
|
+
emit_owning_release_double_warnings(double_names_lines)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# ── Simple leak detection ──────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
def check_owning_release_leaks(stmts)
|
|
41
|
+
locals = owning_local_bindings(stmts)
|
|
42
|
+
released = all_released_names(stmts)
|
|
43
|
+
locals.reject { |name, _info| released.include?(name) || ownership_transferred?(stmts, name) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Check only own[T] typed leaks (separate from struct-type leaks)
|
|
47
|
+
def check_own_release_leaks(stmts)
|
|
48
|
+
own_locals = own_typed_local_bindings(stmts)
|
|
49
|
+
own_released = all_own_released_names(stmts)
|
|
50
|
+
own_locals.reject { |name, _info| own_released.include?(name) || own_transferred?(stmts, name) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def emit_owning_release_leak_warnings(leak_names)
|
|
54
|
+
leak_names.each do |name, info|
|
|
55
|
+
@warnings << Warning.new(
|
|
56
|
+
path: @path,
|
|
57
|
+
line: info[:line],
|
|
58
|
+
column: info[:column],
|
|
59
|
+
length: name.length,
|
|
60
|
+
code: "owning-release-leak",
|
|
61
|
+
message: "owning binding '#{name}' of type '#{info[:type_name]}' is never released",
|
|
62
|
+
severity: :warning,
|
|
63
|
+
symbol_name: name,
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# ── own[T] typed local detection ──────────────────────────────────
|
|
69
|
+
|
|
70
|
+
def own_typed_local_bindings(stmts)
|
|
71
|
+
result = {}
|
|
72
|
+
_collect_own_typed_locals(stmts, result)
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def _collect_own_typed_locals(stmts, result)
|
|
77
|
+
return if stmts.nil?
|
|
78
|
+
|
|
79
|
+
stmts.each do |stmt|
|
|
80
|
+
case stmt
|
|
81
|
+
when AST::LocalDecl
|
|
82
|
+
type_name = resolve_local_own_pointer_type(stmt)
|
|
83
|
+
if type_name
|
|
84
|
+
result[stmt.name] = {
|
|
85
|
+
line: stmt.line,
|
|
86
|
+
column: stmt.respond_to?(:column) ? stmt.column : nil,
|
|
87
|
+
type_name: type_name,
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
when AST::IfStmt
|
|
91
|
+
stmt.branches.each { |b| _collect_own_typed_locals(b.body, result) }
|
|
92
|
+
_collect_own_typed_locals(stmt.else_body, result) if stmt.else_body
|
|
93
|
+
when AST::WhileStmt
|
|
94
|
+
_collect_own_typed_locals(stmt.body, result)
|
|
95
|
+
when AST::ForStmt
|
|
96
|
+
_collect_own_typed_locals(stmt.body, result)
|
|
97
|
+
when AST::MatchStmt
|
|
98
|
+
stmt.arms.each { |arm| _collect_own_typed_locals(arm.body, result) }
|
|
99
|
+
when AST::UnsafeStmt
|
|
100
|
+
_collect_own_typed_locals(stmt.body, result)
|
|
101
|
+
when AST::DeferStmt
|
|
102
|
+
_collect_own_typed_locals(stmt.body, result) if stmt.body.is_a?(Array)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def resolve_local_own_pointer_type(decl)
|
|
108
|
+
if is_heap_alloc_call?(decl.value)
|
|
109
|
+
return "own[<inferred>]"
|
|
110
|
+
end
|
|
111
|
+
type = resolve_expr_type(decl.value) if decl.value
|
|
112
|
+
return nil unless type
|
|
113
|
+
return nil unless type.is_a?(MilkTea::Types::GenericInstance)
|
|
114
|
+
return nil unless type.name == "own"
|
|
115
|
+
type.to_s
|
|
116
|
+
rescue StandardError
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def is_heap_alloc_call?(expr)
|
|
121
|
+
return false unless expr.is_a?(AST::Call)
|
|
122
|
+
callee = expr.callee
|
|
123
|
+
non_null_method_names = %w[must_alloc must_alloc_aligned
|
|
124
|
+
must_alloc_zeroed must_resize].to_set
|
|
125
|
+
case callee
|
|
126
|
+
when AST::MemberAccess
|
|
127
|
+
callee.receiver.is_a?(AST::Identifier) &&
|
|
128
|
+
non_null_method_names.include?(callee.member)
|
|
129
|
+
when AST::Specialization
|
|
130
|
+
inner = callee.callee
|
|
131
|
+
inner.is_a?(AST::MemberAccess) &&
|
|
132
|
+
inner.receiver.is_a?(AST::Identifier) &&
|
|
133
|
+
non_null_method_names.include?(inner.member)
|
|
134
|
+
else
|
|
135
|
+
false
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# ── own[T] release detection ──────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
def all_own_released_names(stmts)
|
|
142
|
+
result = Set.new
|
|
143
|
+
_collect_own_released_names(stmts, result)
|
|
144
|
+
result
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def _collect_own_released_names(stmts, result)
|
|
148
|
+
return if stmts.nil?
|
|
149
|
+
stmts.each { |s| _collect_own_released_names_in_stmt(s, result) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def _collect_own_released_names_in_stmt(stmt, result)
|
|
153
|
+
case stmt
|
|
154
|
+
when AST::ExpressionStmt
|
|
155
|
+
_collect_heap_release_names(stmt.expression, result)
|
|
156
|
+
when AST::ReturnStmt
|
|
157
|
+
_collect_heap_release_names(stmt.value, result) if stmt.value
|
|
158
|
+
when AST::LocalDecl
|
|
159
|
+
_collect_heap_release_names(stmt.value, result) if stmt.value
|
|
160
|
+
when AST::Assignment
|
|
161
|
+
_collect_heap_release_names(stmt.value, result)
|
|
162
|
+
when AST::IfStmt
|
|
163
|
+
stmt.branches.each { |b| _collect_own_released_names(b.body, result) }
|
|
164
|
+
_collect_own_released_names(stmt.else_body, result) if stmt.else_body
|
|
165
|
+
when AST::WhileStmt
|
|
166
|
+
_collect_own_released_names(stmt.body, result)
|
|
167
|
+
when AST::ForStmt
|
|
168
|
+
_collect_own_released_names(stmt.body, result)
|
|
169
|
+
when AST::MatchStmt
|
|
170
|
+
stmt.arms.each { |arm| _collect_own_released_names(arm.body, result) }
|
|
171
|
+
when AST::UnsafeStmt
|
|
172
|
+
_collect_own_released_names(stmt.body, result)
|
|
173
|
+
when AST::DeferStmt
|
|
174
|
+
if stmt.expression
|
|
175
|
+
_collect_heap_release_names(stmt.expression, result)
|
|
176
|
+
end
|
|
177
|
+
_collect_own_released_names(stmt.body, result) if stmt.body.is_a?(Array)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def _collect_heap_release_names(expr, result)
|
|
182
|
+
return if expr.nil?
|
|
183
|
+
if heap_release_call_on_name?(expr)
|
|
184
|
+
result << heap_release_target_name(expr)
|
|
185
|
+
end
|
|
186
|
+
case expr
|
|
187
|
+
when AST::Call
|
|
188
|
+
expr.arguments.each { |a| _collect_heap_release_names(a.value, result) }
|
|
189
|
+
_collect_heap_release_names(expr.callee, result) if expr.callee.is_a?(AST::Call)
|
|
190
|
+
when AST::BinaryOp
|
|
191
|
+
_collect_heap_release_names(expr.left, result)
|
|
192
|
+
_collect_heap_release_names(expr.right, result)
|
|
193
|
+
when AST::UnaryOp
|
|
194
|
+
_collect_heap_release_names(expr.operand, result)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def heap_release_call_on_name?(expr)
|
|
199
|
+
return false unless expr.is_a?(AST::Call)
|
|
200
|
+
return false unless expr.callee.is_a?(AST::MemberAccess)
|
|
201
|
+
return false unless %w[release release_and_null].include?(expr.callee.member)
|
|
202
|
+
return false unless expr.arguments.length >= 1
|
|
203
|
+
arg = expr.arguments.first
|
|
204
|
+
return false unless arg.is_a?(AST::Argument)
|
|
205
|
+
target_name = heap_release_target_name(expr)
|
|
206
|
+
target_name && !target_name.empty?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def heap_release_target_name(expr)
|
|
210
|
+
return nil unless expr.is_a?(AST::Call)
|
|
211
|
+
return nil unless expr.arguments.length >= 1
|
|
212
|
+
# For multi-arg release (e.g. tracking.release(tracker, p)), find
|
|
213
|
+
# the argument that names the released value by preferring the last
|
|
214
|
+
# direct-identifier argument.
|
|
215
|
+
released = nil
|
|
216
|
+
expr.arguments.each do |arg|
|
|
217
|
+
next unless arg.is_a?(AST::Argument)
|
|
218
|
+
value = arg.value
|
|
219
|
+
value = value.expression if value.is_a?(AST::UnsafeStmt)
|
|
220
|
+
if value.is_a?(AST::Identifier)
|
|
221
|
+
released = value.name
|
|
222
|
+
elsif value.is_a?(AST::Call) && value.callee.is_a?(AST::Identifier) && value.callee.name == "ref_of"
|
|
223
|
+
ref_arg = value.arguments.first
|
|
224
|
+
if ref_arg.is_a?(AST::Argument) && ref_arg.value.is_a?(AST::Identifier)
|
|
225
|
+
released = ref_arg.value.name
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
released
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# ── own[T] ownership transfer ─────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
def own_transferred?(stmts, name)
|
|
235
|
+
_any_stmt?(stmts) do |stmt|
|
|
236
|
+
if stmt.is_a?(AST::ReturnStmt) && stmt.value.is_a?(AST::Identifier) && stmt.value.name == name
|
|
237
|
+
true
|
|
238
|
+
elsif (expr = extract_expr(stmt)) && own_struct_field_transfer?(expr, name)
|
|
239
|
+
true
|
|
240
|
+
else
|
|
241
|
+
false
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def own_struct_field_transfer?(expr, name)
|
|
247
|
+
return false unless expr.is_a?(AST::Call)
|
|
248
|
+
expr.arguments.any? do |arg|
|
|
249
|
+
arg.is_a?(AST::Argument) && arg.value.is_a?(AST::Identifier) && arg.value.name == name
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def owning_local_bindings(stmts)
|
|
254
|
+
result = {}
|
|
255
|
+
_collect_owning_locals(stmts, result)
|
|
256
|
+
result
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def _collect_owning_locals(stmts, result)
|
|
260
|
+
return if stmts.nil?
|
|
261
|
+
|
|
262
|
+
stmts.each do |stmt|
|
|
263
|
+
case stmt
|
|
264
|
+
when AST::LocalDecl
|
|
265
|
+
type_name = resolve_local_owning_type(stmt)
|
|
266
|
+
if type_name
|
|
267
|
+
result[stmt.name] = {
|
|
268
|
+
line: stmt.line,
|
|
269
|
+
column: stmt.respond_to?(:column) ? stmt.column : nil,
|
|
270
|
+
type_name: type_name,
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
when AST::IfStmt
|
|
274
|
+
stmt.branches.each { |b| _collect_owning_locals(b.body, result) }
|
|
275
|
+
_collect_owning_locals(stmt.else_body, result) if stmt.else_body
|
|
276
|
+
when AST::WhileStmt
|
|
277
|
+
_collect_owning_locals(stmt.body, result)
|
|
278
|
+
when AST::ForStmt
|
|
279
|
+
_collect_owning_locals(stmt.body, result)
|
|
280
|
+
when AST::MatchStmt
|
|
281
|
+
stmt.arms.each { |arm| _collect_owning_locals(arm.body, result) }
|
|
282
|
+
when AST::UnsafeStmt
|
|
283
|
+
_collect_owning_locals(stmt.body, result)
|
|
284
|
+
when AST::DeferStmt
|
|
285
|
+
_collect_owning_locals(stmt.body, result) if stmt.body.is_a?(Array)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# ── Simple sequential double-release ─────────────────────────────
|
|
291
|
+
|
|
292
|
+
def check_owning_release_double(stmts)
|
|
293
|
+
result = {}
|
|
294
|
+
_check_double_release_in_seq(stmts, result)
|
|
295
|
+
result
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def emit_owning_release_double_warnings(double_names_lines)
|
|
299
|
+
double_names_lines.each do |name, lines|
|
|
300
|
+
@warnings << Warning.new(
|
|
301
|
+
path: @path,
|
|
302
|
+
line: lines.last,
|
|
303
|
+
column: nil,
|
|
304
|
+
length: nil,
|
|
305
|
+
code: "owning-release-double",
|
|
306
|
+
message: "owning binding '#{name}' may be released more than once",
|
|
307
|
+
severity: :warning,
|
|
308
|
+
symbol_name: name,
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def _check_double_release_in_seq(stmts, result)
|
|
314
|
+
return if stmts.nil?
|
|
315
|
+
|
|
316
|
+
scope_releases = Hash.new { |hash, key| hash[key] = [] }
|
|
317
|
+
|
|
318
|
+
stmts.each do |stmt|
|
|
319
|
+
expr = extract_expr(stmt)
|
|
320
|
+
if expr && release_call_on_binding?(expr)
|
|
321
|
+
name = expr.callee.receiver.name
|
|
322
|
+
scope_releases[name] << extract_line(stmt)
|
|
323
|
+
elsif expr && heap_release_call_on_name?(expr)
|
|
324
|
+
name = heap_release_target_name(expr)
|
|
325
|
+
scope_releases[name] << extract_line(stmt) if name
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
scope_releases.delete(stmt.target.name) if reassigns_owning_binding?(stmt)
|
|
329
|
+
|
|
330
|
+
case stmt
|
|
331
|
+
when AST::UnsafeStmt
|
|
332
|
+
_check_double_release_in_seq(stmt.body, result)
|
|
333
|
+
when AST::DeferStmt
|
|
334
|
+
_check_double_release_in_seq(stmt.body, result) if stmt.body.is_a?(Array)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
scope_releases.each do |name, lines|
|
|
339
|
+
result[name] = lines if lines.length > 1
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# ── Type resolution ────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
def resolve_local_owning_type(decl)
|
|
346
|
+
if decl.type && decl.type.is_a?(AST::TypeRef)
|
|
347
|
+
base = owning_base_name(decl.type.name.to_s)
|
|
348
|
+
return base if owning_type_by_name?(base)
|
|
349
|
+
end
|
|
350
|
+
if decl.value
|
|
351
|
+
type_name = extract_initializer_type(decl.value)
|
|
352
|
+
return type_name if type_name && owning_type_by_name?(type_name)
|
|
353
|
+
end
|
|
354
|
+
nil
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def extract_initializer_type(expr)
|
|
358
|
+
return nil unless expr.is_a?(AST::Call)
|
|
359
|
+
return nil unless expr.arguments.empty?
|
|
360
|
+
|
|
361
|
+
callee = expr.callee
|
|
362
|
+
method_name = case callee
|
|
363
|
+
when AST::MemberAccess then callee.member
|
|
364
|
+
when AST::Specialization
|
|
365
|
+
inner = callee.callee
|
|
366
|
+
inner.is_a?(AST::MemberAccess) ? inner.member : nil
|
|
367
|
+
else nil
|
|
368
|
+
end
|
|
369
|
+
return nil unless %w[create with_capacity empty from_str].include?(method_name)
|
|
370
|
+
|
|
371
|
+
receiver = case callee
|
|
372
|
+
when AST::MemberAccess then callee.receiver
|
|
373
|
+
when AST::Specialization then callee
|
|
374
|
+
else nil
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
owning_type_from_receiver(receiver)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def owning_type_from_receiver(receiver)
|
|
381
|
+
case receiver
|
|
382
|
+
when AST::Identifier then owning_base_name(receiver.name)
|
|
383
|
+
when AST::MemberAccess then owning_base_name(receiver.member)
|
|
384
|
+
when AST::Specialization then owning_type_from_receiver(receiver.callee)
|
|
385
|
+
else nil
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# ── Ownership transfer detection ──────────────────────────────────
|
|
390
|
+
|
|
391
|
+
def ownership_transferred?(stmts, name)
|
|
392
|
+
returned_name?(stmts, name) || struct_field_transfer?(stmts, name)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def returned_name?(stmts, name)
|
|
396
|
+
_any_stmt?(stmts) do |stmt|
|
|
397
|
+
stmt.is_a?(AST::ReturnStmt) &&
|
|
398
|
+
stmt.value.is_a?(AST::Identifier) &&
|
|
399
|
+
stmt.value.name == name
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def struct_field_transfer?(stmts, name)
|
|
404
|
+
_any_stmt_or_expr?(stmts) { |expr| _expr_contains_transfer?(expr, name) }
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def _expr_contains_transfer?(expr, name)
|
|
408
|
+
return false unless expr.is_a?(AST::Call)
|
|
409
|
+
|
|
410
|
+
expr.arguments.any? do |arg|
|
|
411
|
+
next false unless arg.is_a?(AST::Argument) && arg.name
|
|
412
|
+
|
|
413
|
+
if arg.value.is_a?(AST::Identifier) && arg.value.name == name
|
|
414
|
+
true
|
|
415
|
+
elsif arg.value.is_a?(AST::Call)
|
|
416
|
+
_expr_contains_transfer?(arg.value, name)
|
|
417
|
+
else
|
|
418
|
+
false
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# ── Shared AST walkers ────────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
def all_released_names(stmts)
|
|
426
|
+
result = Set.new
|
|
427
|
+
_collect_released_names(stmts, result)
|
|
428
|
+
result
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def _collect_released_names(stmts, result)
|
|
432
|
+
return if stmts.nil?
|
|
433
|
+
stmts.each { |s| _collect_released_names_in_stmt(s, result) }
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def _collect_released_names_in_stmt(stmt, result)
|
|
437
|
+
case stmt
|
|
438
|
+
when AST::ExpressionStmt
|
|
439
|
+
_collect_released_names_in_expr(stmt.expression, result)
|
|
440
|
+
when AST::ReturnStmt
|
|
441
|
+
_collect_released_names_in_expr(stmt.value, result) if stmt.value
|
|
442
|
+
when AST::LocalDecl
|
|
443
|
+
_collect_released_names_in_expr(stmt.value, result) if stmt.value
|
|
444
|
+
when AST::Assignment
|
|
445
|
+
_collect_released_names_in_expr(stmt.value, result)
|
|
446
|
+
when AST::IfStmt
|
|
447
|
+
stmt.branches.each { |b| _collect_released_names(b.body, result) }
|
|
448
|
+
_collect_released_names(stmt.else_body, result) if stmt.else_body
|
|
449
|
+
when AST::WhileStmt
|
|
450
|
+
_collect_released_names(stmt.body, result)
|
|
451
|
+
when AST::ForStmt
|
|
452
|
+
_collect_released_names(stmt.body, result)
|
|
453
|
+
when AST::MatchStmt
|
|
454
|
+
stmt.arms.each { |arm| _collect_released_names(arm.body, result) }
|
|
455
|
+
when AST::UnsafeStmt
|
|
456
|
+
_collect_released_names(stmt.body, result)
|
|
457
|
+
when AST::DeferStmt
|
|
458
|
+
_collect_released_names_in_expr(stmt.expression, result) if stmt.expression
|
|
459
|
+
_collect_released_names(stmt.body, result) if stmt.body.is_a?(Array)
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def _collect_released_names_in_expr(expr, result)
|
|
464
|
+
return if expr.nil?
|
|
465
|
+
|
|
466
|
+
result << expr.callee.receiver.name if release_call_on_binding?(expr)
|
|
467
|
+
|
|
468
|
+
case expr
|
|
469
|
+
when AST::Call
|
|
470
|
+
expr.arguments.each { |a| _collect_released_names_in_expr(a.value, result) }
|
|
471
|
+
_collect_released_names_in_expr(expr.callee, result)
|
|
472
|
+
when AST::MemberAccess
|
|
473
|
+
_collect_released_names_in_expr(expr.receiver, result)
|
|
474
|
+
when AST::BinaryOp
|
|
475
|
+
_collect_released_names_in_expr(expr.left, result)
|
|
476
|
+
_collect_released_names_in_expr(expr.right, result)
|
|
477
|
+
when AST::UnaryOp
|
|
478
|
+
_collect_released_names_in_expr(expr.operand, result)
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def _any_stmt?(stmts, &pred)
|
|
483
|
+
return false if stmts.nil?
|
|
484
|
+
|
|
485
|
+
stmts.any? do |stmt|
|
|
486
|
+
if pred.call(stmt)
|
|
487
|
+
true
|
|
488
|
+
else
|
|
489
|
+
case stmt
|
|
490
|
+
when AST::IfStmt
|
|
491
|
+
stmt.branches.any? { |b| _any_stmt?(b.body, &pred) } ||
|
|
492
|
+
_any_stmt?(stmt.else_body, &pred)
|
|
493
|
+
when AST::WhileStmt then _any_stmt?(stmt.body, &pred)
|
|
494
|
+
when AST::ForStmt then _any_stmt?(stmt.body, &pred)
|
|
495
|
+
when AST::MatchStmt then stmt.arms.any? { |arm| _any_stmt?(arm.body, &pred) }
|
|
496
|
+
when AST::UnsafeStmt then _any_stmt?(stmt.body, &pred)
|
|
497
|
+
when AST::DeferStmt then stmt.body.is_a?(Array) && _any_stmt?(stmt.body, &pred)
|
|
498
|
+
else false
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def _any_stmt_or_expr?(stmts, &pred)
|
|
505
|
+
_any_stmt?(stmts) do |stmt|
|
|
506
|
+
expr = extract_expr(stmt)
|
|
507
|
+
expr && pred.call(expr)
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# ── CFG-based precision analysis ──────────────────────────────────
|
|
512
|
+
|
|
513
|
+
def emit_owning_release_cfg_warnings(cfg_analysis, body)
|
|
514
|
+
graph = cfg_analysis.graph
|
|
515
|
+
reachable_ids = cfg_analysis.reachability.reachable_ids
|
|
516
|
+
exit_kinds = %i[exit return break_exit continue_exit].to_set.freeze
|
|
517
|
+
owning_locals = owning_local_bindings(body)
|
|
518
|
+
return if owning_locals.empty?
|
|
519
|
+
|
|
520
|
+
owning_locals.each do |name, info|
|
|
521
|
+
decl_id = find_decl_node_id(graph, name)
|
|
522
|
+
next unless decl_id && reachable_ids.include?(decl_id)
|
|
523
|
+
|
|
524
|
+
releases, consumers, reassigns = classify_cfg_nodes(graph, reachable_ids, name)
|
|
525
|
+
|
|
526
|
+
unless every_path_to_exit_has_consumer?(graph, decl_id, consumers, exit_kinds, reachable_ids)
|
|
527
|
+
emit_owning_release_leak_warnings({ name => info })
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
releases.each do |rel_id|
|
|
531
|
+
reachable_release = find_reachable_release(graph, rel_id, releases, reassigns, reachable_ids)
|
|
532
|
+
next unless reachable_release
|
|
533
|
+
|
|
534
|
+
node = graph.nodes[reachable_release]
|
|
535
|
+
@warnings << Warning.new(
|
|
536
|
+
path: @path,
|
|
537
|
+
line: node&.line,
|
|
538
|
+
column: nil,
|
|
539
|
+
length: nil,
|
|
540
|
+
code: "owning-release-double",
|
|
541
|
+
message: "owning binding '#{name}' may be released more than once",
|
|
542
|
+
severity: :warning,
|
|
543
|
+
symbol_name: name,
|
|
544
|
+
)
|
|
545
|
+
break
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def find_decl_node_id(graph, name)
|
|
551
|
+
graph.each_node do |node|
|
|
552
|
+
next unless node.kind == :local_decl
|
|
553
|
+
node.writes_info.each do |write|
|
|
554
|
+
return node.id if write[:origin] == :declaration && write[:name] == name
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
nil
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def classify_cfg_nodes(graph, reachable_ids, name)
|
|
561
|
+
releases = Set.new
|
|
562
|
+
consumers = Set.new
|
|
563
|
+
reassigns = Set.new
|
|
564
|
+
|
|
565
|
+
graph.each_node do |node|
|
|
566
|
+
next unless reachable_ids.include?(node.id)
|
|
567
|
+
next unless node.statement
|
|
568
|
+
|
|
569
|
+
is_defer = node.statement.is_a?(AST::DeferStmt)
|
|
570
|
+
|
|
571
|
+
if release_call_in_stmt?(node.statement, name)
|
|
572
|
+
consumers << node.id
|
|
573
|
+
releases << node.id unless is_defer
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
if return_for_name?(node.statement, name) || struct_transfer_in_stmt?(node.statement, name)
|
|
577
|
+
consumers << node.id
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
if node.kind == :assignment
|
|
581
|
+
node.writes_info.each do |write|
|
|
582
|
+
reassigns << node.id if write[:origin] == :assignment && write[:name] == name
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
[releases, consumers, reassigns]
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def release_call_in_stmt?(stmt, name)
|
|
591
|
+
return false unless stmt
|
|
592
|
+
|
|
593
|
+
# DeferStmt: inline form `defer x.release()` or block form `defer: …`
|
|
594
|
+
if stmt.is_a?(AST::DeferStmt)
|
|
595
|
+
return true if stmt.expression && _expr_has_release?(stmt.expression, name)
|
|
596
|
+
return stmt.body.is_a?(Array) && stmt.body.any? { |s| release_call_in_stmt?(s, name) }
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
expr = extract_expr(stmt)
|
|
600
|
+
expr && _expr_has_release?(expr, name)
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def _expr_has_release?(expr, name)
|
|
604
|
+
return false unless expr
|
|
605
|
+
|
|
606
|
+
return true if release_call_on_binding?(expr) && expr.callee.receiver.name == name
|
|
607
|
+
|
|
608
|
+
case expr
|
|
609
|
+
when AST::Call
|
|
610
|
+
expr.arguments.any? { |a| _expr_has_release?(a.value, name) } ||
|
|
611
|
+
_expr_has_release?(expr.callee, name)
|
|
612
|
+
when AST::MemberAccess then _expr_has_release?(expr.receiver, name)
|
|
613
|
+
when AST::BinaryOp
|
|
614
|
+
_expr_has_release?(expr.left, name) || _expr_has_release?(expr.right, name)
|
|
615
|
+
when AST::UnaryOp then _expr_has_release?(expr.operand, name)
|
|
616
|
+
else false
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def return_for_name?(stmt, name)
|
|
621
|
+
stmt.is_a?(AST::ReturnStmt) &&
|
|
622
|
+
stmt.value.is_a?(AST::Identifier) &&
|
|
623
|
+
stmt.value.name == name
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def struct_transfer_in_stmt?(stmt, name)
|
|
627
|
+
expr = extract_expr(stmt)
|
|
628
|
+
expr && _expr_contains_transfer?(expr, name)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def every_path_to_exit_has_consumer?(graph, start_id, consumers, exit_kinds, reachable_ids)
|
|
632
|
+
visited = Set.new
|
|
633
|
+
queue = (graph.nodes[start_id]&.succs || []).dup
|
|
634
|
+
until queue.empty?
|
|
635
|
+
nid = queue.shift
|
|
636
|
+
next if visited.include?(nid)
|
|
637
|
+
visited << nid
|
|
638
|
+
next unless reachable_ids.include?(nid)
|
|
639
|
+
|
|
640
|
+
node = graph.nodes[nid]
|
|
641
|
+
next unless node
|
|
642
|
+
|
|
643
|
+
if consumers.include?(nid)
|
|
644
|
+
nil
|
|
645
|
+
elsif exit_kinds.include?(node.kind)
|
|
646
|
+
return false
|
|
647
|
+
else
|
|
648
|
+
node.succs.each { |s| queue << s }
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
true
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def find_reachable_release(graph, start_id, release_ids, reassign_ids, reachable_ids)
|
|
655
|
+
visited = Set.new
|
|
656
|
+
queue = (graph.nodes[start_id]&.succs || []).dup
|
|
657
|
+
until queue.empty?
|
|
658
|
+
nid = queue.shift
|
|
659
|
+
next if visited.include?(nid)
|
|
660
|
+
visited << nid
|
|
661
|
+
next unless reachable_ids.include?(nid)
|
|
662
|
+
|
|
663
|
+
return nid if release_ids.include?(nid) && nid != start_id
|
|
664
|
+
next if reassign_ids.include?(nid)
|
|
665
|
+
|
|
666
|
+
node = graph.nodes[nid]
|
|
667
|
+
next unless node
|
|
668
|
+
|
|
669
|
+
queue.concat(node.succs)
|
|
670
|
+
end
|
|
671
|
+
nil
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# ── Helpers ───────────────────────────────────────────────────────
|
|
675
|
+
|
|
676
|
+
def release_call_on_binding?(expr)
|
|
677
|
+
return false unless expr.is_a?(AST::Call)
|
|
678
|
+
return false unless expr.callee.is_a?(AST::MemberAccess)
|
|
679
|
+
return false unless expr.callee.member == "release"
|
|
680
|
+
return false unless expr.callee.receiver.is_a?(AST::Identifier)
|
|
681
|
+
return false unless expr.arguments.empty?
|
|
682
|
+
true
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def reassigns_owning_binding?(stmt)
|
|
686
|
+
return false unless stmt.is_a?(AST::Assignment)
|
|
687
|
+
return false unless stmt.target.is_a?(AST::Identifier)
|
|
688
|
+
return false unless stmt.operator == "="
|
|
689
|
+
|
|
690
|
+
@scopes.reverse_each { |scope| return true if scope.key?(stmt.target.name) }
|
|
691
|
+
false
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def extract_expr(stmt)
|
|
695
|
+
case stmt
|
|
696
|
+
when AST::ExpressionStmt then stmt.expression
|
|
697
|
+
when AST::ReturnStmt then stmt.value
|
|
698
|
+
when AST::LocalDecl then stmt.value
|
|
699
|
+
when AST::Assignment then stmt.value
|
|
700
|
+
else nil
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def extract_line(stmt)
|
|
705
|
+
stmt.respond_to?(:line) ? stmt.line : nil
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def owning_base_name(qname)
|
|
709
|
+
last_dot = qname.rindex(".")
|
|
710
|
+
last_dot ? qname[(last_dot + 1)..] : qname
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
def owning_type_by_name?(type_name)
|
|
714
|
+
return false unless type_name
|
|
715
|
+
@owning_type_set ||= build_owning_type_set
|
|
716
|
+
@owning_type_set.include?(type_name)
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
def build_owning_type_set
|
|
720
|
+
set = Set.new
|
|
721
|
+
return set unless @sema_facts
|
|
722
|
+
|
|
723
|
+
all_methods = [@sema_facts.methods]
|
|
724
|
+
(@imported_modules || {}).each_value do |mod_binding|
|
|
725
|
+
all_methods << mod_binding.methods if mod_binding.respond_to?(:methods)
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
all_methods.compact.each do |methods_hash|
|
|
729
|
+
methods_hash.each do |type, methods|
|
|
730
|
+
next unless methods.is_a?(Hash) && methods.key?("release")
|
|
731
|
+
set << type_base_name(type)
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
set
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
def type_base_name(type)
|
|
738
|
+
return nil unless type
|
|
739
|
+
return type.name.to_s if type.respond_to?(:name) && type.name
|
|
740
|
+
type.to_s
|
|
741
|
+
end
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
end
|