<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-07-06T03:06:08+00:00</updated><id>/feed.xml</id><title type="html">HMZ</title><subtitle>Mingzhe Hu</subtitle><entry><title type="html">Semantics Engineering</title><link href="/jekyll/update/2023/07/20/semantics_engineering.html" rel="alternate" type="text/html" title="Semantics Engineering" /><published>2023-07-20T00:00:00+00:00</published><updated>2023-07-20T00:00:00+00:00</updated><id>/jekyll/update/2023/07/20/semantics_engineering</id><content type="html" xml:base="/jekyll/update/2023/07/20/semantics_engineering.html"><![CDATA[<h3 id="theory-of-programming-languages">Theory of programming languages</h3>

<p><em>Calculus</em> is a logic for calculating with the terms of the language.
For example,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>e = x | λ x.e | (e e)
</code></pre></div></div>

<p>Extensions with primitive data:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>e = x | λ x.e | (e e) | tt | ff | (if e e e)
</code></pre></div></div>

<p>External interpretation functions (δ):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(if tt e e') if-tt e
(if ff e e') if-ff e'
</code></pre></div></div>

<p><em>Semantics</em> is a system for determining the value of a program.</p>

<p><em>Reduction</em> is a relation on terms:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>((λ x.e) e') beta e[x = x'] (e with x replaced by e')
((λ x.e) e') beta [e'/x]e (substitution e' for x in e)
</code></pre></div></div>

<p><em>Equational system</em> is defined with three properties:</p>

<p>For any relation R,</p>

<p><em>reflexivity</em>: if <code class="language-plaintext highlighter-rouge">e R e'</code>, then <code class="language-plaintext highlighter-rouge">e e'' R e' e''</code></p>

<p><em>symmetry</em>: if <code class="language-plaintext highlighter-rouge">e R e'</code>, then <code class="language-plaintext highlighter-rouge">e'' e R e'' e'</code></p>

<p><em>transitivity</em>: if <code class="language-plaintext highlighter-rouge">e R e'</code>, then <code class="language-plaintext highlighter-rouge">λ x.e R λ x.e'</code></p>

<p>With an equational system, we can prove such facts as</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>e (Y e) = (Y e)
</code></pre></div></div>

<p>meaning every single term has a <em>fixpoint</em>.</p>

<p><br /></p>

<p>In Plotkin’s theory of programming languages, a semantic is a relation
<em>eval</em> from programs to values:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eval : Program ✕ Value
def e eval v iff e = v
</code></pre></div></div>

<p>We get a <em>specification</em> of an interpreter after proving that eval is a
function.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eval : Program → Value
eval(e) = v
</code></pre></div></div>

<p>Prove that the calculus satisfies a standard reduction property. This gives us a
second semantic.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eval-standard : Program → Value
def eval-standard(e) = v iff e standard reduces to v
</code></pre></div></div>

<p>Curry-Feys’s <em>standard reduction</em> is a strategy for the lambda calculus,
that is, a function that picks the next reducible expression (called
<em>redex</em>) to reduce. Plotkin specifically uses the leftmost-outermost
strategy.</p>

<p>Plotkin adds the <em>truth</em> to the specification.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def e ~ e' iff placing e and e' into any context yields programs that
produce the same observable behavior according to eval
</code></pre></div></div>

<h3 id="redex">Redex</h3>

<p>Redex is a scripting language and set of associated tools
supporting the conception, design, construction, and testing of semantic systems
such as programming languages, type systems, program logics, and program
analyses. As a scripting language, it enables an engineer to create executable
specifications of common semantic elements such as grammars, reduction
relations, judgments, and metafunctions; the basic elements of formal systems.</p>

<h3 id="syntax">Syntax</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; syntax trees
(define-language Lambda
  (e ::= x
         (lambda (x ...) e)
         (e e ...))
  (x ::= variable-not-otherwise-mentioned))

; instances
(define e1 (term y))
(define e2 (term (lambda (y) y)))
(define e3 (term (lambda (x y) y)))
(define e4 (term (,e2 ,e3)))

; a predicate that tests membership
(define lambda? (redex-match? Lambda e))

; language tests formulations
(test-equal (lambda? e1) #true)
(test-equal (lambda? e2) #true)
(test-equal (lambda? e3) #true)
(test-equal (lambda? e4) #true)

(define eb1 (term (lambda (x x) y)))
(define eb2 (term (lambda (x y) 3)))

(test-equal (lambda? eb1) #true)
(test-equal (lambda? eb2) #false)
</code></pre></div></div>

<h3 id="metafunction">Metafunction</h3>

<p>A metafunction is a function on terms of a specific language.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; are the identifiers in the given sequence unique?
; extended Kleene patterns: (lambda (x_!_ ...) e)

(module+ test
  (test-equal (term (unique-vars x y)) #true)
  (test-equal (term (unique-vars x y x)) #false))

(define-metafunction Lambda
  ; a Redex contract with patterns
  unique-vars : x ... -&gt; boolean
  [(unique-vars) #true]
  [(unique-vars x x_1 ... x x_2 ...) #false]
  [(unique-vars x x_1 ...) (unique-vars x_1 ...)])

(module+ test
  (test-results))

; (subtract (x ...) x_1 ...) removes x_1 ... from (x ...)

(module+ test
  (test-equal (term (subtract (x y z x) x z)) (term (y))))

(define-metafunction Lambda
  subtract : (x ...) x ... -&gt; (x ...)
  [(subtract (x ...)) (x ...)]
  [(subtract (x ...) x_1 x_2 ...)
   (subtract (subtract1 (x ...) x_1) x_2 ...)])

(module+ test
  (test-results))

; (subtract1 (x ...) x_1) removes x_1  from (x ...)

(module+ test
  (test-equal (term (subtract1 (x y z x) x)) (term (y z))))

(define-metafunction Lambda
  subtract1 : (x ...) x -&gt; (x ...)
  [(subtract1 (x_1 ... x x_2 ...) x)
   (x_1 ... x_2new ...)
   (where (x_2new ...) (subtract1 (x_2 ...) x))
   (where #false (in x (x_1 ...)))]
  [(subtract1 (x ...) x_1) (x ...)])

(define-metafunction Lambda
  in : x (x ...) -&gt; boolean
  [(in x (x_1 ... x x_2 ...)) #true]
  [(in x (x_1 ...)) #false])

(module+ test
  (test-results))
</code></pre></div></div>

<h3 id="scope">Scope</h3>

<p>To specify the scope, a free-variables function specifies which language
constructs bind and which one don’t.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; (fv e) computes the sequence of free variables of e
; a variable occurrence of x is free in e
; if no (lambda (... x ...) ...) dominates its occurrence

(module+ test
  (test-equal (term (fv x)) (term (x)))
  (test-equal (term (fv (lambda (x) x))) (term ()))
  (test-equal (term (fv (lambda (x) (y z x)))) (term (y z))))

(define-metafunction Lambda
  fv : e -&gt; (x ...)
  [(fv x) (x)]
  [(fv (lambda (x ...) e))
   (subtract (x_e ...) x ...)
   (where (x_e ...) (fv e))]
  [(fv (e_f e_a ...))
   (x_f ... x_a ... ...)
   (where (x_f ...) (fv e_f))
   (where ((x_a ...) ...) ((fv e_a) ...))])
</code></pre></div></div>

<p><em>α equivalence</em> is a realtion that virtually eliminates variables
from phrases and replaces them with arrows to their declarations. In
lambda calculus-based languages, this transformation is often a part of the
compiler, called the <em>static-distance</em> phase.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; (sd e) computes the static distance version of e

(define-extended-language SD Lambda
  (e ::= ....
         (K n n)
         n)
  (n ::= natural))

(define sd1 (term (K 1 1)))
(define sd2 (term 1))

(define SD? (redex-match? SD e))

(module+ test
  (test-equal (SD? sd1) #true)
  (test-equal (SD? sd2) #true))


(define-metafunction SD
  sd : e -&gt; e
  [(sd e_1) (sd/a e_1 ())])

(module+ test
  (test-equal (term (sd/a x ())) (term x))
  (test-equal (term (sd/a x ((y) (z) (x)))) (term (K 2 0)))
  (test-equal (term (sd/a ((lambda (x) x) (lambda (y) y)) ()))
              (term ((lambda () (K 0 0)) (lambda () (K 0 0)))))
  (test-equal (term (sd/a (lambda (x) (x (lambda (y) y))) ()))
              (term (lambda () ((K 0 0) (lambda () (K 0 0))))))
  (test-equal (term (sd/a (lambda (z x) (x (lambda (y) z))) ()))
              (term (lambda () ((K 0 1) (lambda () (K 1 0)))))))

(define-metafunction SD
  sd/a : e ((x ...) ...) -&gt; e
  [(sd/a x ((x_1 ...) ... (x_0 ... x x_2 ...) (x_3 ...) ...))
   ; bound variable
   (K n_rib n_pos)
   (where n_rib ,(length (term ((x_1 ...) ...))))
   (where n_pos ,(length (term (x_0 ...))))
   (where #false (in x (x_1 ... ...)))]
  [(sd/a (lambda (x ...) e_1) (e_rest ...))
   (lambda () (sd/a e_1 ((x ...) e_rest ...)))]
  [(sd/a (e_fun e_arg ...) (e_rib ...))
   ((sd/a e_fun (e_rib ...)) (sd/a e_arg (e_rib ...)) ...)]
  [(sd/a e_1 any)
   ; a free variable is left alone
   e_1])
</code></pre></div></div>

<p>Steps of the last formulation:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   (sd/a (lambda (z x) (x (lambda (y) z))) ())
-&gt; (lambda () (sd/a (x (lambda (y) z)) ((z x))))
-&gt; (lambda () ((sd/a x ((z x))) (sd/a (lambda (y) z) ((z x)))))
-&gt; (lambda () ((K 0 1) (lambda () (sd/a z ((y) (z x))))))
-&gt; (lambda () ((K 0 1) (lambda () (K 1 0))))
</code></pre></div></div>

<p>α equivalence:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; (=α e_1 e_2) determines whether e_1 and e_2 are α equivalent

(module+ test
  (test-equal (term (=α (lambda (x) x) (lambda (y) y))) #true)
  (test-equal (term (=α (lambda (x) (x 1)) (lambda (y) (y 1)))) #true)
  (test-equal (term (=α (lambda (x) x) (lambda (y) z))) #false))

(define-metafunction SD
  =α : e e -&gt; boolean
  [(=α e_1 e_2) ,(equal? (term (sd e_1)) (term (sd e_2)))])

(define (=α/racket x y) (term (=α ,x ,y)))

(module+ test
  (test-results))
</code></pre></div></div>

<h3 id="substitution">Substitution</h3>

<p><em>Substitution</em> is the syntactic equivalent of function application.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; (subst ([e x] ...) e_*) substitutes e ... for x ... in e_* (hygienically)

(module+ test
  (test-equal (term (subst ([1 x][2 y]) x)) 1)
  (test-equal (term (subst ([1 x][2 y]) y)) 2)
  (test-equal (term (subst ([1 x][2 y]) z)) (term z))
  (test-equal (term (subst ([1 x][2 y]) (lambda (z w) (x y))))
              (term (lambda (z w) (1 2))))
  (test-equal (term (subst ([1 x][2 y]) (lambda (z w) (lambda (x) (x y)))))
              (term (lambda (z w) (lambda (x) (x 2))))
              #:equiv =α/racket)
  (test-equal (term (subst ((2 x)) ((lambda (x) (1 x)) x)))
              (term ((lambda (x) (1 x)) 2))
              #:equiv =α/racket))

(define-metafunction Lambda
  subst : ((any x) ...) any -&gt; any
  [(subst [(any_1 x_1) ... (any_x x) (any_2 x_2) ...] x) any_x]
  [(subst [(any_1 x_1) ...]  x) x]
  [(subst [(any_1 x_1) ...]  (lambda (x ...) any_body))
   (lambda (x_new ...)
     (subst ((any_1 x_1) ...)
            (subst-raw ((x_new x) ...) any_body)))
   (where  (x_new ...)  ,(variables-not-in (term any_body) (term (x ...))))]
  [(subst [(any_1 x_1) ...]  (any ...)) ((subst [(any_1 x_1) ...]  any) ...)]
  [(subst [(any_1 x_1) ...]  any_*) any_*])

(define-metafunction Lambda
  subst-raw : ((x x) ...) any -&gt; any
  [(subst-raw ((x_n1 x_o1) ... (x_new x) (x_n2 x_o2) ...) x) x_new]
  [(subst-raw ((x_n1 x_o1) ...)  x) x]
  [(subst-raw ((x_n1 x_o1) ...)  (lambda (x ...) any))
   (lambda (x ...) (subst-raw ((x_n1 x_o1) ...)  any))]
  [(subst-raw [(any_1 x_1) ...]  (any ...))
   ((subst-raw [(any_1 x_1) ...]  any) ...)]
  [(subst-raw [(any_1 x_1) ...]  any_*) any_*])

(module+ test
  (test-results))
</code></pre></div></div>

<h3 id="reduction-and-semantics">Reduction and semantics</h3>

<p>The logical way of generating an equivalence (or reduction) relation over terms
uses through inductive inference rules that make the relation compatible with
all syntactic constructions.</p>

<p>An alternative and equivalent method is to introduce the notion of a
<em>context</em> and to use it to generate the reduction relation (or
equivalence) from the notion of reduction:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-extended-language Lambda-calculus Lambda
  (e ::= .... n)
  (n ::= natural)
  (v ::= (lambda (x ...) e))

  ; a context is an expression with one hole in lieu of a sub-expression
  (C ::=
     hole
     (e ... C e ...)
     (lambda (x_!_ ...) C)))

(define Context? (redex-match? Lambda-calculus C))

(module+ test
  (define C1 (term ((lambda (x y) x) hole 1)))
  (define C2 (term ((lambda (x y) hole) 0 1)))
  (test-equal (Context? C1) #true)
  (test-equal (Context? C2) #true))

(module+ test
  (test-results))
</code></pre></div></div>

<p><em>Reduction relations</em>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; the λβ calculus, reductions only
(module+ test
  ; does the one-step reduction reduce both β redexes?
  (test--&gt; --&gt;β
           #:equiv =α/racket
           (term ((lambda (x) ((lambda (y) y) x)) z))
           (term ((lambda (x) x) z))
           (term ((lambda (y) y) z)))

  ; does the full reduction relation reduce all redexes?
  (test--&gt;&gt; --&gt;β
            (term ((lambda (x y) (x 1 y 2))
                   (lambda (a b c) a)
                   3))
            1))

(define --&gt;β
  (reduction-relation
   Lambda-calculus
   (--&gt; (in-hole C ((lambda (x_1 ..._n) e) e_1 ..._n))
   (in-hole C (subst ([e_1 x_1] ...) e)))))

(traces --&gt;β
        (term ((lambda (x y)
                 ((lambda (f) (f (x 1 y 2)))
                  (lambda (w) 42)))
               ((lambda (x) x) (lambda (a b c) a))
               3)))
</code></pre></div></div>

<p>Defining the <em>call-by-value</em> calculus requires just a small change to the
reduction rule:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define --&gt;βv
  (reduction-relation
   Lambda-calculus
   (--&gt; (in-hole C ((lambda (x_1 ..._n) e) v_1 ..._n))
        (in-hole C (subst ([v_1 x_1] ...) e)))))

(traces --&gt;βv
        (term ((lambda (x y)
                 ((lambda (f) (f (x 1 y 2)))
                  (lambda (w) 42)))
               ((lambda (x) x) (lambda (a b c) a))
               3)))
</code></pre></div></div>

<p><em>Semantics</em>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-extended-language Standard Lambda-calculus
  (v ::= n (lambda (x ...) e))
  (E ::=
     hole
     (v ... E e ...)))

(module+ test
  (define t0
    (term
     ((lambda (x y) (x y))
      ((lambda (x) x) (lambda (x) x))
      ((lambda (x) x) 5))))
  (define t0-one-step
    (term
     ((lambda (x y) (x y))
      (lambda (x) x)
      ((lambda (x) x) 5))))

  ; yields only one term, leftmost-outermost
  (test--&gt; s-&gt;βv t0 t0-one-step)
  ; but the transitive closure drives it to 5
  (test--&gt;&gt; s-&gt;βv t0 5))

(define s-&gt;βv
  (reduction-relation
   Standard
   (--&gt; (in-hole E ((lambda (x_1 ..._n) e) v_1 ..._n))
        (in-hole E (subst ((v_1 x_1) ...) e)))))

(module+ test
  (test-results))
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(module+ test
  (test-equal (term (eval-value ,t0)) 5)
  (test-equal (term (eval-value ,t0-one-step)) 5)

  (define t1
    (term ((lambda (x) x) (lambda (x) x))))
  (test-equal (lambda? t1) #true)
  (test-equal (redex-match? Standard e t1) #true)
  (test-equal (term (eval-value ,t1)) 'closure))

(define-metafunction Standard
  eval-value : e -&gt; v or closure
  [(eval-value e) any_1 (where any_1 (run-value e))])

(define-metafunction Standard
  run-value : e -&gt; v or closure
  [(run-value n) n]
  [(run-value v) closure]
  [(run-value e)
   (run-value e_again)
   ; (v) means that we expect s-&gt;βv to be a function
   (where (e_again) ,(apply-reduction-relation s-&gt;βv (term e)))])

(module+ test
  (test-results))
</code></pre></div></div>

<h3 id="references">References</h3>

<p>[1] Robert Bruce Findler, Casey Klein, Burke Fetscher, and Matthias Felleisen.
(2015) <em>Redex: Practical Semantics Engineering</em>,
<a href="https://docs.racket-lang.org/redex/index.html">https://docs.racket-lang.org/redex/index.html</a>.</p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Theory of programming languages]]></summary></entry><entry><title type="html">基于 Doop 的指针分析与不同的上下文敏感性</title><link href="/jekyll/update/2022/07/22/doop_based_pointer_analyses.html" rel="alternate" type="text/html" title="基于 Doop 的指针分析与不同的上下文敏感性" /><published>2022-07-22T00:00:00+00:00</published><updated>2022-07-22T00:00:00+00:00</updated><id>/jekyll/update/2022/07/22/doop_based_pointer_analyses</id><content type="html" xml:base="/jekyll/update/2022/07/22/doop_based_pointer_analyses.html"><![CDATA[<h2 id="doop-based-pointer-analyses">Doop based pointer analyses</h2>

<ul>
  <li><a href="#doop-based-pointer-analyses">Doop based pointer analyses</a>
    <ul>
      <li><a href="#doop">Doop</a>
        <ul>
          <li><a href="#datalog-与指针分析">Datalog 与指针分析</a></li>
          <li><a href="#指针分析规范">指针分析规范</a></li>
          <li><a href="#优化与评估">优化与评估</a></li>
        </ul>
      </li>
      <li><a href="#bean">Bean</a></li>
      <li><a href="#scaler">Scaler</a></li>
      <li><a href="#其他">其他</a></li>
    </ul>
  </li>
</ul>

<p>今天和大家分享的是指针分析的三个工作，包括 Doop 框架和基于它的两个工作。在 OOPSLA 2009 的文章 <a href="https://dl.acm.org/doi/10.1145/1640089.1640108">Strictly Declarative Specification of Sophisticated Points-to Analyses</a> 中，Yannis Smaragdakis 等人提出了 Doop 这个指针分析框架，截止到 2022 年 7 月这篇文章有 386 的引用量，也是今天主要介绍的工作。另两个工作是南京大学的李樾、谭添老师在博士和博后阶段的工作，分别是和 <a href="https://www.cse.unsw.edu.au/~jingling/">Jingling Xue</a> 合作的发表在 SAS 2016 的 <a href="https://link.springer.com/chapter/10.1007/978-3-662-53413-7_24">Making k-Object-Sensitive Pointer Analysis More Precise with Still k-Limiting</a> (Bean)，和 Anders Møller 与 Yannis Smaragdakis 合作的发表在 FSE 2018 的 <a href="https://dl.acm.org/doi/10.1145/3236024.3236041">Scalability-First Pointer Analysis with Self-Tuning Context-Sensitivity</a> (Scaler)。<a href="https://yuelee.bitbucket.io/">李樾</a>、<a href="https://silverbullettt.bitbucket.io/">谭添</a>和 <a href="https://www.cse.unsw.edu.au/~jingling/">Jingling Xue</a> 一直都在做指针分析，还有一系列的文章（基础指针分析的会议文章包括 PLDI17，OOPSLA18，OOPSLA19，OOPSLA21，ASE21，ECOOP22 等，还有期刊文章以及基于指针分析的应用）。今天简要介绍的是较早的两篇，主要看一下，不同于 Doop 这种比较基础的工作，一个比较小的基础（非应用）创新点是如何考虑的。</p>

<h3 id="doop">Doop</h3>

<p>首先介绍 OOPSLA 2009 Doop 的工作。</p>

<h4 id="datalog-与指针分析">Datalog 与指针分析</h4>

<p>Doop 是一个基于 Datalog 的指针分析。</p>

<p>Datalog 是一个演绎数据库和逻辑编程语言，在数据库层面可以看作支持完整递归的 SQL，即增删改基于规则和事实两部分，例如事实 <code class="language-plaintext highlighter-rouge">parent(bob, alice)</code>，<code class="language-plaintext highlighter-rouge">parent(canon, bob)</code> 和规则 <code class="language-plaintext highlighter-rouge">ancestor(A, B) :- parent(A, B)</code> 可以推出 <code class="language-plaintext highlighter-rouge">ancestor(bob, alice)</code>，<code class="language-plaintext highlighter-rouge">ancestor(canon, bob)</code>，当然 parent 事实本身也在数据库里。我们增加规则 <code class="language-plaintext highlighter-rouge">ancestor(A, B) :- parent(A, C), ancestor(C, B)</code>，可以额外推出 <code class="language-plaintext highlighter-rouge">ancestor(canon, alice)</code>。事实是关系（relation），即谓词（predicate），例如 <code class="language-plaintext highlighter-rouge">parent(bob, alice)</code> 对应命题逻辑“bob 是 alice 的父辈”。规则由规则头、蕴含符号和规则体组成，规则头是关系原子（relational atom），规则体则包含一个或多个子目标（关系原子或算数原子（如 a &gt; 0）），例如规则 <code class="language-plaintext highlighter-rouge">ancestor(A, B) :- parent(A, B)</code> 对应一阶逻辑：任意 A 是 B 的父辈，则 A 是 B 的祖先。Datalog 根据输入的事实推导出所有的关系。在语言层面，Datalog 可以看作没有函数的 Prolog。Datalog 也没有表达高阶逻辑的能力（本质是一阶谓词逻辑中 Horn 子句逻辑的一种受限形式），只允许变量或常量作为谓词的自变元，不允许函数作为谓词的自变元。（对应到集合，命题（零阶）逻辑只有对象（命题），一阶逻辑增加性质，即对象的集合，二阶逻辑则可以表示性质的集合。）</p>

<p>在本文中，关系用大写开头，变量用 ? 开头，蕴含写作 &lt;-。Doop 的输入是 Java 程序（bytecode 形式，部分流敏感利用 Soot Jimple SSA IR），输出 Datalog 关系。例如，<code class="language-plaintext highlighter-rouge">new</code> 语句会生成 <code class="language-plaintext highlighter-rouge">AssignHeapAllocation(?var, ?heap)</code>，赋值语句会生成 <code class="language-plaintext highlighter-rouge">Assign(?from, ?to)</code>。这种映射由语法得到，在此基础上指针分析可以表示为例如规则：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VarPointsTo(?var, ?heap) &lt;- AssignHeapAllocation(?var, ?heap).
VarPointsTo(?to, ?heap) &lt;- Assign(?from, ?to), VarPointsTo(?from, ?heap).
</code></pre></div></div>

<p>这种表示是递归和声明式的，简洁清晰，同时可以表达例如可达性关系和指向关系这种互相依赖的分析（互递归定义）。Datalog 程序的求值可以对应关系代数的 join 和 projection，是一个两层循环，join 循环可以应用相对成熟的数据库优化技术。本文的 Datalog 引擎是商业版，<a href="https://github.com/KnowSciEng/doop">Doop</a> 也提供开源版。商业版（有条件地）支持否定词和函数。</p>

<h4 id="指针分析规范">指针分析规范</h4>

<p>Datalog 可以自然地描述形式化的指针分析逻辑规范。有些规范是优化相关的，例如 Java 类的静态初始化方法的上下文无关处理不会影响指向分析的上下文敏感性，但是可以减少运行时间，这种策略会影响逻辑规范，生成的 Datalog 程序是不等价的。</p>

<p>首先考虑前面的规则的扩展：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VarPointsTo(?ctx, ?var, ?heap) &lt;-
    AssignHeapAllocation(?var, ?heap, ?inmethod),
    CallGraphEdge(_, _, ?ctx, ?inmethod).
VarPointsTo(?toCtx, ?to, ?heap) &lt;- 
    Assign(?fromCtx, ?from, ?toCtx, ?to, ?type),
    VarPointsTo(?fromCtx, ?from, ?heap),
    HeapAllocation:Type[?heap] = ?heaptype,
    AssignCompatible(?type, ?heaptype).
</code></pre></div></div>

<p>这里主要包含以下几个扩展：</p>
<ul>
  <li>上下文敏感：每个变量之前都带有其上下文。</li>
  <li>在线的调用图构造（和指针分析是 Datalog 互递归），此前的工作一般先独立地在 Java 代码中得到调用图，但是没有指针分析的调用图是不精确的，基于不精确的调用图的指向分析也不够精确。</li>
  <li>函数作为关系，函数 <code class="language-plaintext highlighter-rouge">Type[?heap] = ?heaptype</code> 等价于关系 <code class="language-plaintext highlighter-rouge">Type(?heap, ?heaptype)</code>，结合 : 可以表示一组谓词。</li>
  <li>赋值考虑类型系统，即变量不能指向其类型不支持的对象抽象。</li>
</ul>

<p>Doop 的一个特点是支持完整的 Java 语义，且不依赖 Datalog 以外的分析。Doop 建模了 Paddle（<a href="https://dl.acm.org/doi/10.1145/1391984.1391987">Evaluating the Benifits of Context-Sensitive Points-to Analysis Using a BDD-based Implementation</a>）基于 BDD 的指向分析中的语言特性。BDD（二元决策图）是一种可以压缩指向关系集合的数据结构，我们不在这里介绍。对于 Paddle 已有的特性（如终止机制 finalization、特权操作 PrivilegedAction、线程等），Doop 取得更好的精度，同时支持更多特性，如弱引用和引用队列、反射、异常分析（另有一篇 ISSTA09），底层代码的方法调用等。Datalog 声明式方法的好处还在于增加语义扩展不会影响已有的分析，同时分析规范和语言规范也更贴近，例如以下是 Doop 中类型转换检查（类型 A 能否转换为类型 B）的部分代码。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/** If S is an ordinary (nonarray) class, then:
 *     o If T is a class type, then S must be the
 *       same class as T, or a subclass of T.
 */
CheckCast(?s, ?s) &lt;- ClassType(?s).
CheckCast(?s, ?t) &lt;- Subclass(?t, ?s).
/**    o If T is an interface type, then S must
 *       implement interface T.
 */
CheckCast(?s, ?t) &lt;- ClassType(?s),
Superinterface(?t, ?s).
...
</code></pre></div></div>

<h4 id="优化与评估">优化与评估</h4>

<p>声明式的规范把分析逻辑和算法实现分开，优化问题可以表示为等价的（即逻辑规范固定，部分规范考虑了优化）Datalog 程序的程序变换，即 join 的顺序会对性能产生影响。单规则的 join 顺序调整是自动的，规则间的优化需要 Datalog 引擎支持一些非标准的特性，例如支持引入新的数据库索引，同时依赖人工，所以这里我们不做深入。</p>

<p>对比当时 state-of-the-art 的 Java 指向分析框架 Paddle，Doop 取得了更好的精度、速度和完整性。Doop 集成了上下文敏感度可选的不同分析，在使用和 Paddle 同精度（1-call-site sensitive）的条件下，Doop 比其快 15 倍。在采用相关工作中最好的精度时，Doop 也比 Paddle 快 10 倍。Paddle 使用的 BDD 是一种可规约的数据结构，相比于此前用稀疏位向量存储指向集合的方法，BDD 主要是用相对小的时间增加得到了较大的内存减少，但是本文认为声明式的显式表示在时间和空间上都优于 BDD。</p>

<h3 id="bean">Bean</h3>

<p>SAS 2016，Making k-Object-Sensitive Pointer Analysis More Precise with Still k-Limiting。</p>

<p>一般认为，对于指针分析，流敏感对 C/C++ 程序提升更大，上下文敏感对 Java 等面向对象语言提升更大。上下文敏感又有不同的抽象表示和敏感度。</p>

<p><strong>k-call-site sensitive</strong> 抽象的是调用栈，k 限制栈的深度，记录的是位置和上下文。以下面的程序为例：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>n1 = new One(); // o1
n2 = new Two(); // o2
x1 = newX(n1); // 3
x2 = newX(n2); // 4
n = x1.f;

X newX(Number P) {
    X x = new X(); // o8
    x.f = p;
    return x;
}
class X {
    Number f;
}
</code></pre></div></div>

<p>当上下文不敏感时，p 和 n 都是不精确的：</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>n1</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>n2</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>p</td>
      <td>o1, o2</td>
    </tr>
    <tr>
      <td>x</td>
      <td>o8</td>
    </tr>
    <tr>
      <td>x1</td>
      <td>o8</td>
    </tr>
    <tr>
      <td>x2</td>
      <td>o8</td>
    </tr>
    <tr>
      <td>n</td>
      <td>o1, o2</td>
    </tr>
  </tbody>
</table>

<p>当 1-call-site sensitive 时，p 区分调用点变得精确，但是 n 仍然是不精确的：</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>n1</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>n2</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>3:p</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>3:x</td>
      <td>o8</td>
    </tr>
    <tr>
      <td>x1</td>
      <td>o8</td>
    </tr>
    <tr>
      <td>4:p</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>4:x</td>
      <td>o8</td>
    </tr>
    <tr>
      <td>x2</td>
      <td>o8</td>
    </tr>
    <tr>
      <td>o8.f</td>
      <td>o1, o2</td>
    </tr>
    <tr>
      <td>n</td>
      <td>o1, o2</td>
    </tr>
  </tbody>
</table>

<p>这就引出了 <strong>context-sensitive heap</strong>，即不仅变量有上下文，（堆）对象也有上下文，这可以使得 n 也得到精确的结果：</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>n1</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>n2</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>3:p</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>3:x</td>
      <td>3:o8</td>
    </tr>
    <tr>
      <td>x1</td>
      <td>3:o8</td>
    </tr>
    <tr>
      <td>4:p</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>4:x</td>
      <td>4:o8</td>
    </tr>
    <tr>
      <td>x2</td>
      <td>4:o8</td>
    </tr>
    <tr>
      <td>3:o8.f</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>4:o8.f</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>n</td>
      <td>o1</td>
    </tr>
  </tbody>
</table>

<p>变量上下文仅考虑调用位置也有不能解决的问题，比如：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1 = new A(); // o1
a2 = new A(); // o2
b1 = new B(); // o3
b2 = new B(); // o4
a1.set(b1); // 5
a2.set(b2); // 6
x = a1.get();

class A {
    B f;
    void set(B b) {
        doSet(b);
    }
    void doSet(B p) {
        this.f = p; // 12
    }
    B get() {
        return this.f;
    }
}
</code></pre></div></div>

<p>当 1-call-site sensitive 时，x 不能得到精确的结果：</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5:set_this</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>5:b</td>
      <td>o3</td>
    </tr>
    <tr>
      <td>6:set_this</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>6:b</td>
      <td>o4</td>
    </tr>
    <tr>
      <td>12:doSet_this</td>
      <td>o1, o2</td>
    </tr>
    <tr>
      <td>12:p</td>
      <td>o3, o4</td>
    </tr>
    <tr>
      <td>o1.f</td>
      <td>o3, o4</td>
    </tr>
    <tr>
      <td>o2.f</td>
      <td>o3, o4</td>
    </tr>
    <tr>
      <td>x</td>
      <td>o3, o4</td>
    </tr>
  </tbody>
</table>

<p><strong>k-object sensitive</strong> 记录调用对象（而非调用位置）和上下文，从而得到 x 的精确结果：</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>o1:set_this</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>o1:b</td>
      <td>o3</td>
    </tr>
    <tr>
      <td>o2:set_this</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>o2:b</td>
      <td>o4</td>
    </tr>
    <tr>
      <td>o1:doSet_this</td>
      <td>o1</td>
    </tr>
    <tr>
      <td>o1:p</td>
      <td>o3</td>
    </tr>
    <tr>
      <td>o2:doSet_this</td>
      <td>o2</td>
    </tr>
    <tr>
      <td>o2:p</td>
      <td>o4</td>
    </tr>
    <tr>
      <td>o1.f</td>
      <td>o3</td>
    </tr>
    <tr>
      <td>o2.f</td>
      <td>o4</td>
    </tr>
    <tr>
      <td>x</td>
      <td>o3</td>
    </tr>
  </tbody>
</table>

<p>对于指针分析的精度，k-object sensitive 并不理论一定优于 k-call-site sensitive，但一般在面向对象语言中有更好的结果。</p>

<p>k-call-site sensitive 和 k-object sensitive 的变量上下文敏感都可以和 context-sensitive heap 的堆上下文敏感结合在一起。对于 Java 的指针分析，一般认为 2obj+h 是精度和规模性权衡下最好的。</p>

<p>Doop 包含不同上下文敏感度的指针分析变体，例如前面没有给出的 <code class="language-plaintext highlighter-rouge">CallGraphEdge</code> 的规则：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CallGraphEdge(?callerCtx, ?call, ?calleeCtx, ?callee) &lt;-
    VirtualMethodCall:Base[?call] = ?base,
    VirtualMethodCall:SimpleName[?call] = ?name,
    VirtualMethodCall:Descriptor[?call] = ?descriptor,
    VarPointsTo(?callerCtx, ?base, ?heap),
    HeapAllocation:Type[?heap] = ?heaptype,
    MethodLookup[?name, ?descriptor, ?heaptype] = ?callee,
    ?calleeCtx = ?call.
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">?calleeCtx = ?call</code> 可知这是一个 1-call-site sensitive 的例子。</p>

<p>回到这篇文章，针对 2obj 的一个不足，对于程序：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>void main(Object[] args) {
    A a1 = new A(); // A/1
    Object v1 = a1.foo(new Object()); // O/1

    a2 = new A(); // A/2
    Object v2 = a2.foo(new Object()); // O/2
}
class A {
    Object foo(Object v) { // 9
        B b = new B(); // B/1
        return b.bar(v);
    }
}
class B {
    Object bar(Object v) { // 15
        C c = new C(); // C/1
        return c.identity(v);
    }
}
class C {
    Object identity(Object v) { return v; } // 21
}
</code></pre></div></div>

<p>2-object sensitive 得到的 v1 和 v2 是不精确的：</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>a1</td>
      <td>A/1</td>
    </tr>
    <tr>
      <td>a2</td>
      <td>A/2</td>
    </tr>
    <tr>
      <td>[A/1]v9</td>
      <td>O/1</td>
    </tr>
    <tr>
      <td>[A/2]v9</td>
      <td>O/2</td>
    </tr>
    <tr>
      <td>[A/1,B/1]v15</td>
      <td>O/1</td>
    </tr>
    <tr>
      <td>[A/2,B/1]v15</td>
      <td>O/1</td>
    </tr>
    <tr>
      <td>[B/1,C/1]v21</td>
      <td>O/1, O/2</td>
    </tr>
    <tr>
      <td>v1</td>
      <td>O/1, O/2</td>
    </tr>
    <tr>
      <td>v2</td>
      <td>O/1, O/2</td>
    </tr>
  </tbody>
</table>

<p>这种不精确在 3-object sensitive 下可以消除：</p>

<table>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Object</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>a1</td>
      <td>A/1</td>
    </tr>
    <tr>
      <td>a2</td>
      <td>A/2</td>
    </tr>
    <tr>
      <td>[A/1]v9</td>
      <td>O/1</td>
    </tr>
    <tr>
      <td>[A/2]v9</td>
      <td>O/2</td>
    </tr>
    <tr>
      <td>[A/1,B/1]v15</td>
      <td>O/1</td>
    </tr>
    <tr>
      <td>[A/2,B/1]v15</td>
      <td>O/2</td>
    </tr>
    <tr>
      <td>[A/1,B/1,C/1]v21</td>
      <td>O/1</td>
    </tr>
    <tr>
      <td>[A/2,B/1,C/1]v21</td>
      <td>O/2</td>
    </tr>
    <tr>
      <td>v1</td>
      <td>O/1</td>
    </tr>
    <tr>
      <td>v2</td>
      <td>O/2</td>
    </tr>
  </tbody>
</table>

<p>很明显 2obj 丢失了第 3 层的调用信息，但是 3obj 在多数情况下可能并不必要，同时我们可以观察到在 <code class="language-plaintext highlighter-rouge">A/1,B/1,C/1</code> 和 <code class="language-plaintext highlighter-rouge">A/2,B/1,C/1</code> 中，<code class="language-plaintext highlighter-rouge">B/1</code> 是冗余的，它会被 2obj 保留下来却不能区分不同的调用上下文。</p>

<p>本文设计了一种图结构 OAG（Object Allocation Graph），对于上例：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>       A/1
      /   \
O_root     B/1 -- C/1
      \   /
       A/2
</code></pre></div></div>

<p>通过图上的算法去掉冗余的节点 <code class="language-plaintext highlighter-rouge">B/1</code>，使得 2obj 得到 <code class="language-plaintext highlighter-rouge">A/1,C/1</code> 和 <code class="language-plaintext highlighter-rouge">A/2,C/1</code>。</p>

<p>虽然实现是在 Doop 中做的，但是本文没有沿用 Doop 的形式规范描述，其形式化章节很值得学习，并给出了 Bean-2obj 在最坏情况下不弱于 2obj 的证明。</p>

<h3 id="scaler">Scaler</h3>

<p>FSE 2018，Scalability-First Pointer Analysis with Self-Tuning Context-Sensitivity。</p>

<p>除了 call-site sensitive 和 object sensitive，还有 <strong>type sensitive</strong> 记录的是调用对象实例所属的类和上下文。对于这么多不同上下文敏感性的 Java 的指针分析，本文发现在一般情况下有：精度 object &gt; type &gt; call-site &gt; insensitive，效率 insensitive &gt; type &gt; object &gt; call-site。而且 2type 和 2obj 都存在分析超时的情况，即规模性较差。</p>

<p>本文的思路就是对每个方法使用不同的敏感性。通过一个上下文不敏感的指向分析即可得到 OAG，从而预估指向边和上下文的乘积，按照预置的上下文敏感变体排列（精度上升，规模性下降），如 {1type, 2type, 2obj}，根据用户给定的规模性度量阈值（保存指向关系的内存占用），选择精度最高的上下文敏感性。</p>

<h3 id="其他">其他</h3>

<p>Java 有几乎是通用的基准测试集 DaCapo，其他相对新的语言（如 JavaScript、Python、Go）有吗？相对于爬很多库，基准测试集除了可以大量减少实验评估中的非技术工作量，也可以方便不同工作的横向比较和复现。</p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Doop based pointer analyses Doop based pointer analyses Doop Datalog 与指针分析 指针分析规范 优化与评估 Bean Scaler 其他 今天和大家分享的是指针分析的三个工作，包括 Doop 框架和基于它的两个工作。在 OOPSLA 2009 的文章 Strictly Declarative Specification of Sophisticated Points-to Analyses 中，Yannis Smaragdakis 等人提出了 Doop 这个指针分析框架，截止到 2022 年 7 月这篇文章有 386 的引用量，也是今天主要介绍的工作。另两个工作是南京大学的李樾、谭添老师在博士和博后阶段的工作，分别是和 Jingling Xue 合作的发表在 SAS 2016 的 Making k-Object-Sensitive Pointer Analysis More Precise with Still k-Limiting (Bean)，和 Anders Møller 与 Yannis Smaragdakis 合作的发表在 FSE 2018 的 Scalability-First Pointer Analysis with Self-Tuning Context-Sensitivity (Scaler)。李樾、谭添和 Jingling Xue 一直都在做指针分析，还有一系列的文章（基础指针分析的会议文章包括 PLDI17，OOPSLA18，OOPSLA19，OOPSLA21，ASE21，ECOOP22 等，还有期刊文章以及基于指针分析的应用）。今天简要介绍的是较早的两篇，主要看一下，不同于 Doop 这种比较基础的工作，一个比较小的基础（非应用）创新点是如何考虑的。 Doop 首先介绍 OOPSLA 2009 Doop 的工作。 Datalog 与指针分析 Doop 是一个基于 Datalog 的指针分析。 Datalog 是一个演绎数据库和逻辑编程语言，在数据库层面可以看作支持完整递归的 SQL，即增删改基于规则和事实两部分，例如事实 parent(bob, alice)，parent(canon, bob) 和规则 ancestor(A, B) :- parent(A, B) 可以推出 ancestor(bob, alice)，ancestor(canon, bob)，当然 parent 事实本身也在数据库里。我们增加规则 ancestor(A, B) :- parent(A, C), ancestor(C, B)，可以额外推出 ancestor(canon, alice)。事实是关系（relation），即谓词（predicate），例如 parent(bob, alice) 对应命题逻辑“bob 是 alice 的父辈”。规则由规则头、蕴含符号和规则体组成，规则头是关系原子（relational atom），规则体则包含一个或多个子目标（关系原子或算数原子（如 a &gt; 0）），例如规则 ancestor(A, B) :- parent(A, B) 对应一阶逻辑：任意 A 是 B 的父辈，则 A 是 B 的祖先。Datalog 根据输入的事实推导出所有的关系。在语言层面，Datalog 可以看作没有函数的 Prolog。Datalog 也没有表达高阶逻辑的能力（本质是一阶谓词逻辑中 Horn 子句逻辑的一种受限形式），只允许变量或常量作为谓词的自变元，不允许函数作为谓词的自变元。（对应到集合，命题（零阶）逻辑只有对象（命题），一阶逻辑增加性质，即对象的集合，二阶逻辑则可以表示性质的集合。） 在本文中，关系用大写开头，变量用 ? 开头，蕴含写作 &lt;-。Doop 的输入是 Java 程序（bytecode 形式，部分流敏感利用 Soot Jimple SSA IR），输出 Datalog 关系。例如，new 语句会生成 AssignHeapAllocation(?var, ?heap)，赋值语句会生成 Assign(?from, ?to)。这种映射由语法得到，在此基础上指针分析可以表示为例如规则： VarPointsTo(?var, ?heap) &lt;- AssignHeapAllocation(?var, ?heap). VarPointsTo(?to, ?heap) &lt;- Assign(?from, ?to), VarPointsTo(?from, ?heap). 这种表示是递归和声明式的，简洁清晰，同时可以表达例如可达性关系和指向关系这种互相依赖的分析（互递归定义）。Datalog 程序的求值可以对应关系代数的 join 和 projection，是一个两层循环，join 循环可以应用相对成熟的数据库优化技术。本文的 Datalog 引擎是商业版，Doop 也提供开源版。商业版（有条件地）支持否定词和函数。 指针分析规范 Datalog 可以自然地描述形式化的指针分析逻辑规范。有些规范是优化相关的，例如 Java 类的静态初始化方法的上下文无关处理不会影响指向分析的上下文敏感性，但是可以减少运行时间，这种策略会影响逻辑规范，生成的 Datalog 程序是不等价的。 首先考虑前面的规则的扩展： VarPointsTo(?ctx, ?var, ?heap) &lt;- AssignHeapAllocation(?var, ?heap, ?inmethod), CallGraphEdge(_, _, ?ctx, ?inmethod). VarPointsTo(?toCtx, ?to, ?heap) &lt;- Assign(?fromCtx, ?from, ?toCtx, ?to, ?type), VarPointsTo(?fromCtx, ?from, ?heap), HeapAllocation:Type[?heap] = ?heaptype, AssignCompatible(?type, ?heaptype). 这里主要包含以下几个扩展： 上下文敏感：每个变量之前都带有其上下文。 在线的调用图构造（和指针分析是 Datalog 互递归），此前的工作一般先独立地在 Java 代码中得到调用图，但是没有指针分析的调用图是不精确的，基于不精确的调用图的指向分析也不够精确。 函数作为关系，函数 Type[?heap] = ?heaptype 等价于关系 Type(?heap, ?heaptype)，结合 : 可以表示一组谓词。 赋值考虑类型系统，即变量不能指向其类型不支持的对象抽象。 Doop 的一个特点是支持完整的 Java 语义，且不依赖 Datalog 以外的分析。Doop 建模了 Paddle（Evaluating the Benifits of Context-Sensitive Points-to Analysis Using a BDD-based Implementation）基于 BDD 的指向分析中的语言特性。BDD（二元决策图）是一种可以压缩指向关系集合的数据结构，我们不在这里介绍。对于 Paddle 已有的特性（如终止机制 finalization、特权操作 PrivilegedAction、线程等），Doop 取得更好的精度，同时支持更多特性，如弱引用和引用队列、反射、异常分析（另有一篇 ISSTA09），底层代码的方法调用等。Datalog 声明式方法的好处还在于增加语义扩展不会影响已有的分析，同时分析规范和语言规范也更贴近，例如以下是 Doop 中类型转换检查（类型 A 能否转换为类型 B）的部分代码。 /** If S is an ordinary (nonarray) class, then: * o If T is a class type, then S must be the * same class as T, or a subclass of T. */ CheckCast(?s, ?s) &lt;- ClassType(?s). CheckCast(?s, ?t) &lt;- Subclass(?t, ?s). /** o If T is an interface type, then S must * implement interface T. */ CheckCast(?s, ?t) &lt;- ClassType(?s), Superinterface(?t, ?s). ... 优化与评估 声明式的规范把分析逻辑和算法实现分开，优化问题可以表示为等价的（即逻辑规范固定，部分规范考虑了优化）Datalog 程序的程序变换，即 join 的顺序会对性能产生影响。单规则的 join 顺序调整是自动的，规则间的优化需要 Datalog 引擎支持一些非标准的特性，例如支持引入新的数据库索引，同时依赖人工，所以这里我们不做深入。 对比当时 state-of-the-art 的 Java 指向分析框架 Paddle，Doop 取得了更好的精度、速度和完整性。Doop 集成了上下文敏感度可选的不同分析，在使用和 Paddle 同精度（1-call-site sensitive）的条件下，Doop 比其快 15 倍。在采用相关工作中最好的精度时，Doop 也比 Paddle 快 10 倍。Paddle 使用的 BDD 是一种可规约的数据结构，相比于此前用稀疏位向量存储指向集合的方法，BDD 主要是用相对小的时间增加得到了较大的内存减少，但是本文认为声明式的显式表示在时间和空间上都优于 BDD。 Bean SAS 2016，Making k-Object-Sensitive Pointer Analysis More Precise with Still k-Limiting。 一般认为，对于指针分析，流敏感对 C/C++ 程序提升更大，上下文敏感对 Java 等面向对象语言提升更大。上下文敏感又有不同的抽象表示和敏感度。 k-call-site sensitive 抽象的是调用栈，k 限制栈的深度，记录的是位置和上下文。以下面的程序为例： n1 = new One(); // o1 n2 = new Two(); // o2 x1 = newX(n1); // 3 x2 = newX(n2); // 4 n = x1.f; X newX(Number P) { X x = new X(); // o8 x.f = p; return x; } class X { Number f; } 当上下文不敏感时，p 和 n 都是不精确的： Variable Object n1 o1 n2 o2 p o1, o2 x o8 x1 o8 x2 o8 n o1, o2 当 1-call-site sensitive 时，p 区分调用点变得精确，但是 n 仍然是不精确的： Variable Object n1 o1 n2 o2 3:p o1 3:x o8 x1 o8 4:p o2 4:x o8 x2 o8 o8.f o1, o2 n o1, o2 这就引出了 context-sensitive heap，即不仅变量有上下文，（堆）对象也有上下文，这可以使得 n 也得到精确的结果： Variable Object n1 o1 n2 o2 3:p o1 3:x 3:o8 x1 3:o8 4:p o2 4:x 4:o8 x2 4:o8 3:o8.f o1 4:o8.f o2 n o1 变量上下文仅考虑调用位置也有不能解决的问题，比如： a1 = new A(); // o1 a2 = new A(); // o2 b1 = new B(); // o3 b2 = new B(); // o4 a1.set(b1); // 5 a2.set(b2); // 6 x = a1.get(); class A { B f; void set(B b) { doSet(b); } void doSet(B p) { this.f = p; // 12 } B get() { return this.f; } } 当 1-call-site sensitive 时，x 不能得到精确的结果： Variable Object 5:set_this o1 5:b o3 6:set_this o2 6:b o4 12:doSet_this o1, o2 12:p o3, o4 o1.f o3, o4 o2.f o3, o4 x o3, o4 k-object sensitive 记录调用对象（而非调用位置）和上下文，从而得到 x 的精确结果： Variable Object o1:set_this o1 o1:b o3 o2:set_this o2 o2:b o4 o1:doSet_this o1 o1:p o3 o2:doSet_this o2 o2:p o4 o1.f o3 o2.f o4 x o3 对于指针分析的精度，k-object sensitive 并不理论一定优于 k-call-site sensitive，但一般在面向对象语言中有更好的结果。 k-call-site sensitive 和 k-object sensitive 的变量上下文敏感都可以和 context-sensitive heap 的堆上下文敏感结合在一起。对于 Java 的指针分析，一般认为 2obj+h 是精度和规模性权衡下最好的。 Doop 包含不同上下文敏感度的指针分析变体，例如前面没有给出的 CallGraphEdge 的规则： CallGraphEdge(?callerCtx, ?call, ?calleeCtx, ?callee) &lt;- VirtualMethodCall:Base[?call] = ?base, VirtualMethodCall:SimpleName[?call] = ?name, VirtualMethodCall:Descriptor[?call] = ?descriptor, VarPointsTo(?callerCtx, ?base, ?heap), HeapAllocation:Type[?heap] = ?heaptype, MethodLookup[?name, ?descriptor, ?heaptype] = ?callee, ?calleeCtx = ?call. ?calleeCtx = ?call 可知这是一个 1-call-site sensitive 的例子。 回到这篇文章，针对 2obj 的一个不足，对于程序： void main(Object[] args) { A a1 = new A(); // A/1 Object v1 = a1.foo(new Object()); // O/1 a2 = new A(); // A/2 Object v2 = a2.foo(new Object()); // O/2 } class A { Object foo(Object v) { // 9 B b = new B(); // B/1 return b.bar(v); } } class B { Object bar(Object v) { // 15 C c = new C(); // C/1 return c.identity(v); } } class C { Object identity(Object v) { return v; } // 21 } 2-object sensitive 得到的 v1 和 v2 是不精确的： Variable Object a1 A/1 a2 A/2 [A/1]v9 O/1 [A/2]v9 O/2 [A/1,B/1]v15 O/1 [A/2,B/1]v15 O/1 [B/1,C/1]v21 O/1, O/2 v1 O/1, O/2 v2 O/1, O/2 这种不精确在 3-object sensitive 下可以消除： Variable Object a1 A/1 a2 A/2 [A/1]v9 O/1 [A/2]v9 O/2 [A/1,B/1]v15 O/1 [A/2,B/1]v15 O/2 [A/1,B/1,C/1]v21 O/1 [A/2,B/1,C/1]v21 O/2 v1 O/1 v2 O/2 很明显 2obj 丢失了第 3 层的调用信息，但是 3obj 在多数情况下可能并不必要，同时我们可以观察到在 A/1,B/1,C/1 和 A/2,B/1,C/1 中，B/1 是冗余的，它会被 2obj 保留下来却不能区分不同的调用上下文。 本文设计了一种图结构 OAG（Object Allocation Graph），对于上例： A/1 / \ O_root B/1 -- C/1 \ / A/2 通过图上的算法去掉冗余的节点 B/1，使得 2obj 得到 A/1,C/1 和 A/2,C/1。 虽然实现是在 Doop 中做的，但是本文没有沿用 Doop 的形式规范描述，其形式化章节很值得学习，并给出了 Bean-2obj 在最坏情况下不弱于 2obj 的证明。 Scaler FSE 2018，Scalability-First Pointer Analysis with Self-Tuning Context-Sensitivity。 除了 call-site sensitive 和 object sensitive，还有 type sensitive 记录的是调用对象实例所属的类和上下文。对于这么多不同上下文敏感性的 Java 的指针分析，本文发现在一般情况下有：精度 object &gt; type &gt; call-site &gt; insensitive，效率 insensitive &gt; type &gt; object &gt; call-site。而且 2type 和 2obj 都存在分析超时的情况，即规模性较差。 本文的思路就是对每个方法使用不同的敏感性。通过一个上下文不敏感的指向分析即可得到 OAG，从而预估指向边和上下文的乘积，按照预置的上下文敏感变体排列（精度上升，规模性下降），如 {1type, 2type, 2obj}，根据用户给定的规模性度量阈值（保存指向关系的内存占用），选择精度最高的上下文敏感性。 其他 Java 有几乎是通用的基准测试集 DaCapo，其他相对新的语言（如 JavaScript、Python、Go）有吗？相对于爬很多库，基准测试集除了可以大量减少实验评估中的非技术工作量，也可以方便不同工作的横向比较和复现。]]></summary></entry><entry><title type="html">JavaScript 通过 wasm 调用 C/C++ 外部函数</title><link href="/jekyll/update/2021/01/14/js_wasm_c.html" rel="alternate" type="text/html" title="JavaScript 通过 wasm 调用 C/C++ 外部函数" /><published>2021-01-14T00:00:00+00:00</published><updated>2021-01-14T00:00:00+00:00</updated><id>/jekyll/update/2021/01/14/js_wasm_c</id><content type="html" xml:base="/jekyll/update/2021/01/14/js_wasm_c.html"><![CDATA[<p>本文主要包括以下内容：</p>

<ul>
  <li>JS 通过 wasm 调用 C/C++ 的方法和原理</li>
  <li>JS 外部函数调用的类型错误隐患</li>
  <li>JS/C 内存管理：单向透明的内存模型</li>
</ul>

<p>JS 调用 C/C++ 的一种方式是通过 WebAssembly，假设有 C++ 文件 <code class="language-plaintext highlighter-rouge">function.cpp</code> 如下：</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;math.h&gt;</span><span class="cp">
</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="p">{</span>

<span class="kt">int</span> <span class="n">int_sqrt</span><span class="p">(</span><span class="kt">int</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
<span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<p>由于 C/C++ 函数名映射到 JS 端时默认是在前面加一个下划线，所以使用 <code class="language-plaintext highlighter-rouge">extern "C"</code> 避免 C++ 的名称重整（name mangling）。</p>

<p>使用 <a href="https://github.com/emscripten-core/emscripten">emscripten</a> 编译（<a href="https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code">文档</a>）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>emcc <span class="k">function</span>.cpp <span class="nt">-o</span> <span class="k">function</span>.html <span class="nt">-s</span> <span class="nv">EXPORTED_FUNCTIONS</span><span class="o">=</span><span class="s1">'["_int_sqrt"]'</span> <span class="nt">-s</span> <span class="nv">EXPORTED_RUNTIME_METHODS</span><span class="o">=</span><span class="s1">'["ccall","cwrap"]'</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">EXPORTED_FUNCTIONS</code> 指明需要暴露的对象，<code class="language-plaintext highlighter-rouge">EXPORTED_RUNTIME_METHODS</code> 指明除直接调用外，还想要使用运行时函数 <code class="language-plaintext highlighter-rouge">ccall</code> 和 <code class="language-plaintext highlighter-rouge">cwrap</code>。</p>

<p>编译后生成了一个 <code class="language-plaintext highlighter-rouge">.js</code> 文件，一个 <code class="language-plaintext highlighter-rouge">.wasm</code> 文件和一个 <code class="language-plaintext highlighter-rouge">.html</code> 文件。<code class="language-plaintext highlighter-rouge">function.js</code> 中有这样的胶水代码：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createExportWrapper</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">fixedasm</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">displayName</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">asm</span> <span class="o">=</span> <span class="nx">fixedasm</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">fixedasm</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">asm</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">[</span><span class="dl">'</span><span class="s1">asm</span><span class="dl">'</span><span class="p">];</span>
    <span class="p">}</span>
    <span class="nx">assert</span><span class="p">(</span><span class="nx">runtimeInitialized</span><span class="p">,</span> <span class="dl">'</span><span class="s1">native function `</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">displayName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">` called before runtime initialization</span><span class="dl">'</span><span class="p">);</span>
    <span class="nx">assert</span><span class="p">(</span><span class="o">!</span><span class="nx">runtimeExited</span><span class="p">,</span> <span class="dl">'</span><span class="s1">native function `</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">displayName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">` called after runtime exit (use NO_EXIT_RUNTIME to keep it alive after main() exits)</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">asm</span><span class="p">[</span><span class="nx">name</span><span class="p">])</span> <span class="p">{</span>
      <span class="nx">assert</span><span class="p">(</span><span class="nx">asm</span><span class="p">[</span><span class="nx">name</span><span class="p">],</span> <span class="dl">'</span><span class="s1">exported native function `</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">displayName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">` not found</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">asm</span><span class="p">[</span><span class="nx">name</span><span class="p">].</span><span class="nx">apply</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span>
  <span class="p">};</span>
<span class="p">}</span>

<span class="cm">/** @type {function(...*):?} */</span>
<span class="kd">var</span> <span class="nx">_int_sqrt</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">[</span><span class="dl">"</span><span class="s2">_int_sqrt</span><span class="dl">"</span><span class="p">]</span> <span class="o">=</span> <span class="nx">createExportWrapper</span><span class="p">(</span><span class="dl">"</span><span class="s2">int_sqrt</span><span class="dl">"</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p>其中 <code class="language-plaintext highlighter-rouge">Module</code> 是 Emscripten 运行时的全局对象，根据名字 <code class="language-plaintext highlighter-rouge">int_sqrt</code> 去由 C++ 编译得到的 <code class="language-plaintext highlighter-rouge">function.wasm</code> 中找对应的对象并执行：</p>

<pre><code class="language-wat">(func (;1;) (type 0) (param i32) (result i32)
    (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 f64 f64 f64)
    global.get 0
    local.set 1
    i32.const 16
    local.set 2
    local.get 1
    local.get 2
    i32.sub
    local.set 3
    local.get 3
    global.set 0
    local.get 3
    local.get 0
    i32.store offset=12
    local.get 3
    i32.load offset=12
    local.set 4
    local.get 4
    call 2
    local.set 13
    local.get 13
    f64.abs
    local.set 14
    f64.const 0x1p+31 (;=2.14748e+09;)
    local.set 15
    local.get 14
    local.get 15
    f64.lt
    local.set 5
    local.get 5
    i32.eqz
    local.set 6
    block  ;; label = @1
      block  ;; label = @2
        local.get 6
        br_if 0 (;@2;)
        local.get 13
        i32.trunc_f64_s
        local.set 7
        local.get 7
        local.set 8
        br 1 (;@1;)
      end
      i32.const -2147483648
      local.set 9
      local.get 9
      local.set 8
    end
    local.get 8
    local.set 10
    i32.const 16
    local.set 11
    local.get 3
    local.get 11
    i32.add
    local.set 12
    local.get 12
    global.set 0
    local.get 10
    return)

(export "int_sqrt" (func 1))
</code></pre>

<p>所以在 JS 端直接调用就是 <code class="language-plaintext highlighter-rouge">_int_sqrt(10)</code> 或 <code class="language-plaintext highlighter-rouge">Module._int_sqrt(10)</code>，这种方式更快，但是需要自己保证传入参数的类型和 wasm 实现相匹配（如此例中的 <code class="language-plaintext highlighter-rouge">i32</code>）。也可以使用 <code class="language-plaintext highlighter-rouge">ccall</code> 调用：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">ccall</span><span class="p">(</span><span class="dl">'</span><span class="s1">int_sqrt</span><span class="dl">'</span><span class="p">,</span> <span class="c1">// C function name</span>
                          <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">,</span>   <span class="c1">// return type</span>
                          <span class="p">[</span><span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">],</span> <span class="c1">// argument types</span>
                          <span class="p">[</span><span class="mi">10</span><span class="p">]</span>        <span class="c1">// arguments</span>
<span class="p">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ccall</code> 实现如下：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// C calling interface.</span>
<span class="cm">/** @param {string|null=} returnType
    @param {Array=} argTypes
    @param {Arguments|Array=} args
    @param {Object=} opts */</span>
<span class="kd">function</span> <span class="nx">ccall</span><span class="p">(</span><span class="nx">ident</span><span class="p">,</span> <span class="nx">returnType</span><span class="p">,</span> <span class="nx">argTypes</span><span class="p">,</span> <span class="nx">args</span><span class="p">,</span> <span class="nx">opts</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// For fast lookup of conversion functions</span>
  <span class="kd">var</span> <span class="nx">toC</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">var</span> <span class="nx">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">str</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">str</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">str</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// null string</span>
        <span class="c1">// at most 4 bytes per UTF-8 code point, +1 for the trailing '\0'</span>
        <span class="kd">var</span> <span class="nx">len</span> <span class="o">=</span> <span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
        <span class="nx">ret</span> <span class="o">=</span> <span class="nx">stackAlloc</span><span class="p">(</span><span class="nx">len</span><span class="p">);</span>
        <span class="nx">stringToUTF8</span><span class="p">(</span><span class="nx">str</span><span class="p">,</span> <span class="nx">ret</span><span class="p">,</span> <span class="nx">len</span><span class="p">);</span>
      <span class="p">}</span>
      <span class="k">return</span> <span class="nx">ret</span><span class="p">;</span>
    <span class="p">},</span>
    <span class="dl">'</span><span class="s1">array</span><span class="dl">'</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">var</span> <span class="nx">ret</span> <span class="o">=</span> <span class="nx">stackAlloc</span><span class="p">(</span><span class="nx">arr</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
      <span class="nx">writeArrayToMemory</span><span class="p">(</span><span class="nx">arr</span><span class="p">,</span> <span class="nx">ret</span><span class="p">);</span>
      <span class="k">return</span> <span class="nx">ret</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">};</span>

  <span class="kd">function</span> <span class="nx">convertReturnValue</span><span class="p">(</span><span class="nx">ret</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">returnType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="k">return</span> <span class="nx">UTF8ToString</span><span class="p">(</span><span class="nx">ret</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">returnType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">boolean</span><span class="dl">'</span><span class="p">)</span> <span class="k">return</span> <span class="nb">Boolean</span><span class="p">(</span><span class="nx">ret</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">ret</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="kd">var</span> <span class="nx">func</span> <span class="o">=</span> <span class="nx">getCFunc</span><span class="p">(</span><span class="nx">ident</span><span class="p">);</span>
  <span class="kd">var</span> <span class="nx">cArgs</span> <span class="o">=</span> <span class="p">[];</span>
  <span class="kd">var</span> <span class="nx">stack</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="nx">assert</span><span class="p">(</span><span class="nx">returnType</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">array</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Return type should not be "array".</span><span class="dl">'</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">args</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">var</span> <span class="nx">converter</span> <span class="o">=</span> <span class="nx">toC</span><span class="p">[</span><span class="nx">argTypes</span><span class="p">[</span><span class="nx">i</span><span class="p">]];</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">converter</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">stack</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="nx">stack</span> <span class="o">=</span> <span class="nx">stackSave</span><span class="p">();</span>
        <span class="nx">cArgs</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">converter</span><span class="p">(</span><span class="nx">args</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">cArgs</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">args</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="kd">var</span> <span class="nx">ret</span> <span class="o">=</span> <span class="nx">func</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">cArgs</span><span class="p">);</span>

  <span class="nx">ret</span> <span class="o">=</span> <span class="nx">convertReturnValue</span><span class="p">(</span><span class="nx">ret</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">stack</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="nx">stackRestore</span><span class="p">(</span><span class="nx">stack</span><span class="p">);</span>
  <span class="k">return</span> <span class="nx">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">writeArrayToMemory</span><span class="p">(</span><span class="nx">array</span><span class="p">,</span> <span class="nx">buffer</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">assert</span><span class="p">(</span><span class="nx">array</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">,</span> <span class="dl">'</span><span class="s1">writeArrayToMemory array must have a length (should be an array or typed array)</span><span class="dl">'</span><span class="p">)</span>
  <span class="nx">HEAP8</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">array</span><span class="p">,</span> <span class="nx">buffer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>给出了入参为 <code class="language-plaintext highlighter-rouge">string</code> 和 <code class="language-plaintext highlighter-rouge">array</code> 类型时分配 JS 堆的方式，外部函数 <code class="language-plaintext highlighter-rouge">stackAlloc</code> 计算存储位置：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** @type {function(...*):?} */</span>
<span class="kd">var</span> <span class="nx">stackAlloc</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">[</span><span class="dl">"</span><span class="s2">stackAlloc</span><span class="dl">"</span><span class="p">]</span> <span class="o">=</span> <span class="nx">createExportWrapper</span><span class="p">(</span><span class="dl">"</span><span class="s2">stackAlloc</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>

<p>其 wasm 实现如下：</p>

<pre><code class="language-wat">(func (;5;) (type 0) (param i32) (result i32)
  (local i32 i32)
  global.get 0
  local.get 0
  i32.sub
  i32.const -16
  i32.and
  local.tee 1
  global.set 0
  local.get 1)

(export "stackAlloc" (func 5))
</code></pre>

<p>这被称作<a href="https://www.cntofu.com/book/150/zh/ch2-c-js/ch2-03-mem-model.md">单向透明的内存模型</a>，即 C/C++ 编译得到的 wasm 的运行时堆和运行时栈全部在 JS 堆（<code class="language-plaintext highlighter-rouge">Module.buffer</code>）上，JS 环境中的其他对象无法被 wasm 直接访问。</p>

<p><code class="language-plaintext highlighter-rouge">cwrap</code> 是对 <code class="language-plaintext highlighter-rouge">ccall</code> 的封装，方便多次调用：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">int_sqrt</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">cwrap</span><span class="p">(</span><span class="dl">'</span><span class="s1">int_sqrt</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">]);</span>
<span class="nx">int_sqrt</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">cwrap</code> 实现如下：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** @param {string=} returnType
    @param {Array=} argTypes
    @param {Object=} opts */</span>
<span class="kd">function</span> <span class="nx">cwrap</span><span class="p">(</span><span class="nx">ident</span><span class="p">,</span> <span class="nx">returnType</span><span class="p">,</span> <span class="nx">argTypes</span><span class="p">,</span> <span class="nx">opts</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">ccall</span><span class="p">(</span><span class="nx">ident</span><span class="p">,</span> <span class="nx">returnType</span><span class="p">,</span> <span class="nx">argTypes</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">,</span> <span class="nx">opts</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>此外我们可以看到不管是直接调用还是通过 <code class="language-plaintext highlighter-rouge">ccall</code> 调用，对于参数个数都没有检查，同时对于非 <code class="language-plaintext highlighter-rouge">string</code> 和 <code class="language-plaintext highlighter-rouge">array</code> 类型的参数也没有类型检查（<code class="language-plaintext highlighter-rouge">string</code> 和 <code class="language-plaintext highlighter-rouge">array</code> 类型的检查机制需要进一步考察内部的函数调用）。</p>

<p>此例接收一个整型参数，我们构造如下的错误：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">ccall</span><span class="p">(</span><span class="dl">'</span><span class="s1">int_sqrt</span><span class="dl">'</span><span class="p">,</span>
                          <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">,</span>
                          <span class="p">[</span><span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">],</span>
                          <span class="p">[</span><span class="dl">'</span><span class="s1">10</span><span class="dl">'</span><span class="p">]</span>
<span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span> <span class="c1">// 1</span>

<span class="nx">int_sqrt</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">cwrap</span><span class="p">(</span><span class="dl">'</span><span class="s1">int_sqrt</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">number</span><span class="dl">'</span><span class="p">]);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">int_sqrt</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">5</span><span class="p">));</span> <span class="c1">// 2</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">_int_sqrt</span><span class="p">(</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">));</span> <span class="c1">// 3</span>
</code></pre></div></div>

<p>程序点 1 输出 3（10 的平方根取整），这与 JS 的弱类型是一致的；程序点 2 输出 3 是因为没有参数个数检查，直接忽略了第二位置开始的所有参数；程序点 3 输出 0。三者都没有报错。</p>

<p>我们进一步探究一下内存模型。</p>

<p>设计 C++ 程序如下：</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifndef EM_PORT_API
#	if defined(__EMSCRIPTEN__)
#		include &lt;emscripten.h&gt;
#		if defined(__cplusplus)
#			define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
#		else
#			define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
#		endif
#	else
#		if defined(__cplusplus)
#			define EM_PORT_API(rettype) extern "C" rettype
#		else
#			define EM_PORT_API(rettype) rettype
#		endif
#	endif
#endif
</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="n">g_int</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="kt">double</span> <span class="n">g_double</span> <span class="o">=</span> <span class="mf">3.1415926</span><span class="p">;</span>

<span class="n">EM_PORT_API</span><span class="p">(</span><span class="kt">int</span><span class="o">*</span><span class="p">)</span> <span class="n">get_int_ptr</span><span class="p">()</span> <span class="p">{</span>
	<span class="k">return</span> <span class="o">&amp;</span><span class="n">g_int</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">EM_PORT_API</span><span class="p">(</span><span class="kt">double</span><span class="o">*</span><span class="p">)</span> <span class="n">get_double_ptr</span><span class="p">()</span> <span class="p">{</span>
	<span class="k">return</span> <span class="o">&amp;</span><span class="n">g_double</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">EM_PORT_API</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="n">print_data</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"C{g_int:%d}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">g_int</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"C{g_double:%lf}</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">g_double</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>JS 调用和操作如下：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Module</span><span class="p">.</span><span class="nx">onRuntimeInitialized</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">=== orginal C value ===</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">Module</span><span class="p">.</span><span class="nx">_print_data</span><span class="p">();</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">=== read C memory from JS side ===</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">var</span> <span class="nx">int_ptr</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">_get_int_ptr</span><span class="p">();</span>
  <span class="kd">var</span> <span class="nx">int_value</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">HEAP32</span><span class="p">[</span><span class="nx">int_ptr</span> <span class="o">&gt;&gt;</span> <span class="mi">2</span><span class="p">];</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">JS{int_value:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">int_value</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">}</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">var</span> <span class="nx">double_ptr</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">_get_double_ptr</span><span class="p">();</span>
  <span class="kd">var</span> <span class="nx">double_value</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">HEAPF64</span><span class="p">[</span><span class="nx">double_ptr</span> <span class="o">&gt;&gt;</span> <span class="mi">3</span><span class="p">];</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">JS{double_value:</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">double_value</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">}</span><span class="dl">"</span><span class="p">);</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">=== write C memory from JS side ===</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">Module</span><span class="p">.</span><span class="nx">HEAP32</span><span class="p">[</span><span class="nx">int_ptr</span> <span class="o">&gt;&gt;</span> <span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mi">13</span><span class="p">;</span>
  <span class="nx">Module</span><span class="p">.</span><span class="nx">HEAPF64</span><span class="p">[</span><span class="nx">double_ptr</span> <span class="o">&gt;&gt;</span> <span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mf">123456.789</span><span class="p">;</span>
  <span class="nx">Module</span><span class="p">.</span><span class="nx">_print_data</span><span class="p">();</span>
<span class="p">};</span>
</code></pre></div></div>

<p>输出为：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=== orginal C value ===
C{g_int:42}
C{g_double:3.141593}
=== read C memory from JS side ===
JS{int_value:42}
JS{double_value:3.1415926}
=== write C memory from JS side ===
C{g_int:13}
C{g_double:123456.789000}
</code></pre></div></div>

<p>可以看到，在 JS 中正确读取了 C/C++（wasm）的内存数据；JS 中写入的数据，在 wasm 中亦能正确获取。</p>

<p>需要注意的是，<code class="language-plaintext highlighter-rouge">Module.buffer</code> 是一个 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 对象，是保存二进制数据的一维数组，无法直接访问，必须通过某种类型的 TypedArray 方可对其进行读写。可以理解为 <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> 是实际存储数据的容器，在其上创建的 TypedArray 则是把该容器当作某种类型的数组来使用。常用对应关系如下表：</p>

<table>
  <thead>
    <tr>
      <th>Object</th>
      <th>TypedArray</th>
      <th>C datatype</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Module.HEAP8</td>
      <td>Int8Array</td>
      <td>int8</td>
    </tr>
    <tr>
      <td>Module.HEAP32</td>
      <td>Int32Array</td>
      <td>int32</td>
    </tr>
    <tr>
      <td>Module.HEAPU16</td>
      <td>Uint16Array</td>
      <td>uint16</td>
    </tr>
    <tr>
      <td>Module.HEAPF64</td>
      <td>Float64Array</td>
      <td>double</td>
    </tr>
  </tbody>
</table>

<p>所以在上例中，在 JS 中通过各种类型的 HEAP 对象视图访问 wasm 的内存数据时，地址必须对齐。</p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[本文主要包括以下内容：]]></summary></entry><entry><title type="html">静态分析中头文件的处理</title><link href="/jekyll/update/2020/07/14/static_analysis_headers.html" rel="alternate" type="text/html" title="静态分析中头文件的处理" /><published>2020-07-14T00:00:00+00:00</published><updated>2020-07-14T00:00:00+00:00</updated><id>/jekyll/update/2020/07/14/static_analysis_headers</id><content type="html" xml:base="/jekyll/update/2020/07/14/static_analysis_headers.html"><![CDATA[<p>为了使源码可编译，首先需要预处理器（preprocessor）处理 <code class="language-plaintext highlighter-rouge">#include</code>、<code class="language-plaintext highlighter-rouge">#define</code> 等命令（directive）并移除注释。但是常常会有一些头文件找不到或 <code class="language-plaintext highlighter-rouge">typedef</code> 未知的奇怪问题。</p>

<h3 id="一个预处理的简单例子">一个预处理的简单例子</h3>

<p>例如，我们要编译 <a href="https://github.com/python-ldap/python-ldap">python-ldap</a> 的 C 扩展模块。作为 Python 项目整体打包时，我们只需要执行 <code class="language-plaintext highlighter-rouge">python setup.py install</code>，而从 <code class="language-plaintext highlighter-rouge">setup.py</code> 知道扩展模块包含的源码和头文件之后，直接调用 C 编译器单独预处理这部分 C 代码却不成功：</p>

<p>以 gcc 为例（clang 和 cpp 命令类似）</p>

<p><code class="language-plaintext highlighter-rouge">gcc -E &lt;file.c&gt;</code></p>

<p>首先一个问题就是 <code class="language-plaintext highlighter-rouge">'Python.h' file not found</code>，这是 Python 外部函数接口 <a href="https://docs.python.org/3/c-api/index.html#c-api-index">Python/C API</a> 定义的入口，而 C 编译器并不知道 Python 头文件的位置。所以我们用 <code class="language-plaintext highlighter-rouge">-I &lt;dir&gt;</code> 参数指定一个 include 搜索路径：</p>

<p><code class="language-plaintext highlighter-rouge">gcc -E -I&lt;3.6/include/python3.6m&gt; file.c</code></p>

<p>具体路径可以用 <code class="language-plaintext highlighter-rouge">find / -name 'Python.h'</code> 搜索，同理补全更多的搜索路径之后就可以通过编译器的预处理阶段了。</p>

<h3 id="解析预处理的输出">解析预处理的输出</h3>

<p>预处理不是目的，一般还需要转换到某些中间形式才能进行静态分析，以使用 <a href="https://github.com/eliben/pycparser">pycparser</a>（或 <a href="https://clang.llvm.org/doxygen/group__CINDEX.html">libclang</a>）生成 AST 为例，在解析（parse）上一步得到的预处理后的代码时，往往又会产生新的报错，例如系统头文件 <code class="language-plaintext highlighter-rouge">&lt;MacOSX.sdk/usr/include/i386/_types.h&gt;</code> 或 <code class="language-plaintext highlighter-rouge">&lt;MacOSX.sdk/usr/include/lber.h&gt;</code> 中一些 <code class="language-plaintext highlighter-rouge">typedef</code> 未知，报错形如 <code class="language-plaintext highlighter-rouge">before: __attribute__</code> 或 <code class="language-plaintext highlighter-rouge">before: __OSX_AVAILABLE_BUT_DEPRECATED_MSG</code>。这里有两个问题，其一是这些头文件并不在我们的搜索路径中，这是因为 gcc 有一些预置的系统头文件目录，使用编译参数 <code class="language-plaintext highlighter-rouge">-nostdinc</code> 可以禁用这种行为。其二是既然预处理器没有报错，为什么其输出无法解析，这是因为 pycparser 不支持 GCC 等编译器扩展，比如 GNU-specific <code class="language-plaintext highlighter-rouge">__attribute__</code>，为此我们可以使用 <code class="language-plaintext highlighter-rouge">-D</code> 参数补充这块定义，比如 <code class="language-plaintext highlighter-rouge">-D'__attribute__(x)='</code>。</p>

<p>pycparser 使用 <code class="language-plaintext highlighter-rouge">fake_libc_include</code> 处理一些标准 C 库头文件，其中给出一些 <code class="language-plaintext highlighter-rouge">#define</code> 和 <code class="language-plaintext highlighter-rouge">typedef</code> 的最小化定义：</p>

<p><code class="language-plaintext highlighter-rouge">typedef int T;</code></p>

<p>即解析器（parser）只需要知道存在类型 <code class="language-plaintext highlighter-rouge">T</code>，而不在乎该类型的具体实现（语义），比如 <code class="language-plaintext highlighter-rouge">T</code> 可能是一个接受结构体数组的函数指针。</p>

<p>pycparser 支持完整的 C99 语言，如果目标程序只依赖<strong>标准 C 库头文件</strong>（standard C library headers，C runtime，如 <code class="language-plaintext highlighter-rouge">stdio.h</code>），那么 <code class="language-plaintext highlighter-rouge">-I&lt;fake_libc_include&gt;</code> 一般就够了。但是如前例所示，目标程序可能还依赖<strong>系统头文件</strong>（system headers，如 <code class="language-plaintext highlighter-rouge">_types.h</code>）和<strong>其他库的头文件</strong>（other library headers，如 <code class="language-plaintext highlighter-rouge">Python.h</code>），这些就需要通过编译参数补充，这些补充定义也可以是最小化的。一般来说，对于我们关心的部分（假设是目标程序和 Python/C API）应该提供完整定义，而其他部分就可以使用最小化定义，这样既可以省去大量繁琐工作，也可以减少解析时间和 AST 体积。</p>

<h3 id="一个复杂的例子">一个复杂的例子</h3>

<p>我们尝试预处理并解析 Python 源码中 <code class="language-plaintext highlighter-rouge">Modules</code> 目录下 C 语言实现的标准库，按照以上策略补充了一系列参数之后依旧存在两个问题，其一是一些定义和平台产生了冲突（如 <a href="https://github.com/python/cpython/blob/master/Include/pyport.h#L743">definition wrong for platform</a>）。其二是补充定义的量过大而无法穷尽，一般是存在大量系统相关的定义（如 <a href="https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=vs-2019">__declspec</a>、<a href="https://stackoverflow.com/questions/26456510/what-does-asm-volatile-do-in-c">__asm__</a>），这里其实已经变成了一个交叉编译（cross compilation）的问题（TODO）。</p>

<p>一个简单的替代解决方法是先在本地机器上执行构建（build）Python 的配置脚本：</p>

<p><code class="language-plaintext highlighter-rouge">./configure --with-pydebug</code></p>

<p>即产生平台相关的 <code class="language-plaintext highlighter-rouge">pyconfig.h</code> 来解决上述问题，之后重复以上策略配置少量参数就可以得到目标代码的 AST 了。</p>

<h3 id="一个小工具">一个小工具</h3>

<p>一方面，有一些通用的编译参数，也有一些目标项目特定的，对于同一项目希望可以实现参数的复用，同时不直接修改而是复用 <code class="language-plaintext highlighter-rouge">fake_libc_include</code>；另一方面，希望配置的过程可以遵循一定的流程，避免输入非常长的命令，并且不和分析部分代码耦合。因此，基于 pycparser 和 yaml 实现了一个编译参数配置工具，可以自动解析配置文件中的编译参数，动态构建 <code class="language-plaintext highlighter-rouge">pycparser.parse_file()</code> 的参数列表。</p>

<p>主程序 <code class="language-plaintext highlighter-rouge">preprocess</code> 除目标文件路径外还接受一个可选参数。缺省时仅加载配置文件 <code class="language-plaintext highlighter-rouge">pp_config.yml</code> 中 <code class="language-plaintext highlighter-rouge">all</code> 对象存储的通用参数，指定 <code class="language-plaintext highlighter-rouge">&lt;project_name&gt;</code> 时则会加载项目特定参数。调试过程就是反复执行主程序，根据报错信息增量地修改配置文件中对应对象的参数条目。调试成功后输出 C 目标程序的 AST，编译参数也自动在配置文件中保留了下来。</p>

<h3 id="参考">参考</h3>

<ol>
  <li><a href="https://github.com/eliben/pycparser">pycparser</a></li>
  <li><a href="https://eli.thegreenplace.net/2015/on-parsing-c-type-declarations-and-fake-headers">On parsing C, type declarations and fake headers</a></li>
  <li><a href="https://stackoverflow.com/questions/33763611/how-to-preprocess-a-c-source-code-for-pycparser">How to preprocess a C source code for pycparser</a></li>
  <li><a href="https://blog.csdn.net/benkaoya/article/details/52368638">用 __attribute__((deprecated)) 管理过时的代码</a></li>
  <li><a href="https://www.jianshu.com/p/29eb7b5c8b2d">__attribute__ 总结</a></li>
  <li><a href="https://bugs.python.org/issue1023838">Include/pyport.h: Bad LONG_BIT assumption on non-glibc sys</a></li>
  <li><a href="https://github.com/tpoechtrager/osxcross/commit/f4b0948abd9cad576d2def3d1cd52b9ef956ef52">OSXCross: Fix building GCC with 10.15 SDK, __OSX_AVAILABLE_BUT_DEPRECATED_MSG</a></li>
  <li><a href="https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=vs-2019">Microsoft-specific modifiers: __declspec</a></li>
  <li><a href="https://stackoverflow.com/questions/26456510/what-does-asm-volatile-do-in-c">What does __asm__ __volatile__ do in C</a></li>
  <li>Build Python from source: <a href="https://devguide.python.org">1</a>, <a href="https://devguide.python.org/setup/#compiling">2</a>, <a href="https://devguide.python.org/setup/#build-dependencies">3</a></li>
  <li><a href="http://www.ruanyifeng.com/blog/2016/07/yaml.html">yaml</a></li>
</ol>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[为了使源码可编译，首先需要预处理器（preprocessor）处理 #include、#define 等命令（directive）并移除注释。但是常常会有一些头文件找不到或 typedef 未知的奇怪问题。]]></summary></entry><entry><title type="html">Learn Emacs in Y Minutes</title><link href="/jekyll/update/2020/06/29/learn_emacs.html" rel="alternate" type="text/html" title="Learn Emacs in Y Minutes" /><published>2020-06-29T00:00:00+00:00</published><updated>2020-06-29T00:00:00+00:00</updated><id>/jekyll/update/2020/06/29/learn_emacs</id><content type="html" xml:base="/jekyll/update/2020/06/29/learn_emacs.html"><![CDATA[<p><a href="https://www.gnu.org/software/emacs/refcards/pdf/refcard.pdf">GNU Emacs Reference Card</a></p>

<p>同时按下 <code class="language-plaintext highlighter-rouge">control</code> 和 <code class="language-plaintext highlighter-rouge">chr</code> 记作 <code class="language-plaintext highlighter-rouge">C-&lt;chr&gt;</code>（一般用于字符、行），同时按下 <code class="language-plaintext highlighter-rouge">meta</code> (<code class="language-plaintext highlighter-rouge">alt</code>, <code class="language-plaintext highlighter-rouge">option</code>) 和 <code class="language-plaintext highlighter-rouge">chr</code> 记作 <code class="language-plaintext highlighter-rouge">M-&lt;chr&gt;</code>（一般用于单词、句子、段落）。</p>

<h3 id="启动关闭模式">启动/关闭/模式</h3>

<ul>
  <li>按 <code class="language-plaintext highlighter-rouge">q</code> 关闭欢迎界面</li>
  <li>打开内置教程 <code class="language-plaintext highlighter-rouge">C-h t</code></li>
  <li>关闭 <code class="language-plaintext highlighter-rouge">C-x C-c</code></li>
  <li>挂起 <code class="language-plaintext highlighter-rouge">C-z</code></li>
  <li>终止执行、清除输入 <code class="language-plaintext highlighter-rouge">C-g</code></li>
  <li>有些命令需要二次确认，按 <code class="language-plaintext highlighter-rouge">n</code> 取消，按 <code class="language-plaintext highlighter-rouge">&lt;SPC&gt;</code>（空格）继续</li>
  <li>使用人类语言的文本模式 <code class="language-plaintext highlighter-rouge">M-x text-mode</code></li>
  <li>显示当前模式文档 <code class="language-plaintext highlighter-rouge">C-h m</code></li>
  <li>在新窗口中显示命令文档 <code class="language-plaintext highlighter-rouge">C-h k ...</code></li>
  <li>命令的简短描述 <code class="language-plaintext highlighter-rouge">C-h c ...</code></li>
  <li>以关键字搜索命令 <code class="language-plaintext highlighter-rouge">C-h a ...</code></li>
  <li>在 scratch 中求值 Lisp 表达式 <code class="language-plaintext highlighter-rouge">C-j</code></li>
</ul>

<h3 id="窗口光标移动">窗口/光标移动</h3>

<ul>
  <li>下移一屏 <code class="language-plaintext highlighter-rouge">C-v</code>（上移一屏 <code class="language-plaintext highlighter-rouge">M-v</code>）</li>
  <li>光标所在行移至屏幕中间 <code class="language-plaintext highlighter-rouge">C-l</code>，再按一次至屏幕顶端，再按一次至屏幕底端</li>
  <li>上移（Previous）<code class="language-plaintext highlighter-rouge">C-p</code></li>
  <li>下移（Next）<code class="language-plaintext highlighter-rouge">C-n</code></li>
  <li>左移（Backward）<code class="language-plaintext highlighter-rouge">C-b</code>（按词左移 <code class="language-plaintext highlighter-rouge">M-b</code>）</li>
  <li>右移（Forward）<code class="language-plaintext highlighter-rouge">C-f</code>（按词左移 <code class="language-plaintext highlighter-rouge">M-f</code>）</li>
  <li>移至行首 <code class="language-plaintext highlighter-rouge">C-a</code>（句首 <code class="language-plaintext highlighter-rouge">M-a</code>）</li>
  <li>移至行尾 <code class="language-plaintext highlighter-rouge">C-e</code>（句末 <code class="language-plaintext highlighter-rouge">M-e</code>）</li>
  <li>移至最首行 <code class="language-plaintext highlighter-rouge">M-&lt;</code></li>
  <li>移至最末行 <code class="language-plaintext highlighter-rouge">M-&gt;</code></li>
  <li>重复命令 <code class="language-plaintext highlighter-rouge">C-u n ...</code></li>
  <li>关闭其他窗口 <code class="language-plaintext highlighter-rouge">C-x 1</code></li>
  <li>设置换行宽度 <code class="language-plaintext highlighter-rouge">C-x f ...</code></li>
  <li>前向搜索 <code class="language-plaintext highlighter-rouge">C-s ...</code>（后向 <code class="language-plaintext highlighter-rouge">C-r ...</code>）</li>
  <li>窗口一分为二 <code class="language-plaintext highlighter-rouge">C-x 2</code></li>
  <li>在新窗口打开文件 <code class="language-plaintext highlighter-rouge">C-x 4 C-f ...</code></li>
  <li>副窗口下移一屏 <code class="language-plaintext highlighter-rouge">C-M-v</code></li>
  <li>进入其他窗口 <code class="language-plaintext highlighter-rouge">C-x o</code></li>
  <li>选中 <code class="language-plaintext highlighter-rouge">C-@</code></li>
</ul>

<h3 id="编辑">编辑</h3>

<ul>
  <li>删除 <code class="language-plaintext highlighter-rouge">&lt;DEL&gt;</code>（按词删除<code class="language-plaintext highlighter-rouge">M-&lt;DEL&gt;</code>）</li>
  <li>向右删除字符 <code class="language-plaintext highlighter-rouge">C-d</code>（按词向右删除 <code class="language-plaintext highlighter-rouge">M-d</code>）</li>
  <li>删至行尾 <code class="language-plaintext highlighter-rouge">C-k</code>（句尾 <code class="language-plaintext highlighter-rouge">M-k</code>）</li>
  <li>粘贴最近删除内容（yank）<code class="language-plaintext highlighter-rouge">C-y</code>（替换为更早删除 <code class="language-plaintext highlighter-rouge">M-y</code>）</li>
  <li>撤销 <code class="language-plaintext highlighter-rouge">C-/</code></li>
  <li>大写光标至单词结束的字符 <code class="language-plaintext highlighter-rouge">M-u</code>（小写 <code class="language-plaintext highlighter-rouge">M-l</code>，首字母大写 <code class="language-plaintext highlighter-rouge">M-c</code>）</li>
  <li>交换左右单词 <code class="language-plaintext highlighter-rouge">M-t</code>（交换左右字符 <code class="language-plaintext highlighter-rouge">C-t</code>）</li>
  <li>替换 <code class="language-plaintext highlighter-rouge">M-x replace-string ... ...</code></li>
  <li>剪切 <code class="language-plaintext highlighter-rouge">C-w</code></li>
  <li>复制 <code class="language-plaintext highlighter-rouge">M-w</code></li>
</ul>

<h3 id="文件缓存">文件/缓存</h3>

<ul>
  <li>打开文件 <code class="language-plaintext highlighter-rouge">C-x C-f ...</code></li>
  <li>保存 <code class="language-plaintext highlighter-rouge">C-x C-s</code></li>
  <li>显示缓存列表 <code class="language-plaintext highlighter-rouge">C-x C-b</code></li>
  <li>打开缓存 <code class="language-plaintext highlighter-rouge">C-x b ...</code></li>
  <li>关闭缓存 <code class="language-plaintext highlighter-rouge">C-x k</code></li>
  <li>保存缓存 <code class="language-plaintext highlighter-rouge">C-x s</code></li>
  <li>恢复自动保存文件 <code class="language-plaintext highlighter-rouge">M-x recover-file</code></li>
</ul>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[GNU Emacs Reference Card]]></summary></entry><entry><title type="html">Python Call by What</title><link href="/jekyll/update/2020/06/14/python_call_by_what.html" rel="alternate" type="text/html" title="Python Call by What" /><published>2020-06-14T00:00:00+00:00</published><updated>2020-06-14T00:00:00+00:00</updated><id>/jekyll/update/2020/06/14/python_call_by_what</id><content type="html" xml:base="/jekyll/update/2020/06/14/python_call_by_what.html"><![CDATA[<p>以下是几种常见的求值策略（或称调用模型、参数传递惯例、规约策略）在 FOLDOC (<a href="http://foldoc.org">Free On-Line Dictionary Of Computing</a>) 中的定义：</p>

<blockquote>
  <p>Call by value (CBV): An evaluation strategy where arguments are evaluated before the function or procedure is entered. Only the values of the arguments are passed and changes to the arguments within the called procedure have no effect on the actual arguments as seen by the caller.</p>
</blockquote>

<blockquote>
  <p>Call by reference (CBR): An argument passing convention where the address of an argument variable is passed to a function or procedure, as opposed to passing the value of the argument expression. Execution of the function or procedure may have side-effects on the actual argument as seen by the caller.</p>
</blockquote>

<blockquote>
  <p>Call by name (CBN): An argument passing convention where argument expressions are passed unevaluated. This is usually implemented by passing a pointer to a thunk - some code which will return the value of the argument and an environment giving the values of its free variables.</p>
</blockquote>

<p>CBV 比 CBN 更加高效，但存在无限数据结构或递归函数时也更可能出现不终止的情况。此外，call by need = CBN + graph reduction，常用于函数式语言，可以避免同一表达式的重复求值。</p>

<p>Python 的调用模型是 CBV 还是 CBR 一直争论不休，首先根据 Fredrik Lundh（Python 核心开发者，曾获 2003 年 <a href="https://www.python.org/community/awards/frank-willison/">Frank Willison Award</a> — Python 社区年度杰出贡献奖）的<a href="http://effbot.org/zone/call-by-object.htm">文章</a>我们给出结论，Python 采用 <a href="http://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-561.pdf">CLU</a> 语言 call by sharing（或称 call by object）的调用模型。</p>

<h3 id="python-objects">Python Objects</h3>

<p>所有的 Python 对象都有：</p>

<ul>
  <li>一个唯一的身份标识：一个用 <code class="language-plaintext highlighter-rouge">id(obj)</code> 返回的整型，在 CPython 中就是 <code class="language-plaintext highlighter-rouge">obj</code> 的地址（<a href="https://docs.python.org/3/reference/datamodel.html#objects-values-and-types">The Python Language Reference - Data model - Objects, values and types</a>）。</li>
  <li>一个类型：用 <code class="language-plaintext highlighter-rouge">type(obj)</code> 返回。</li>
  <li>一些内容（或称值、状态）。</li>
</ul>

<p>身份标识无法更改；类型以类型对象表示，也无法更改（CPython 2.2 及以后在<a href="https://docs.python.org/3/whatsnew/2.2.html#peps-252-and-253-type-and-class-changes">极少数特殊情形</a>下可以更改）；一些对象允许改变其内容（身份标识和类型不变）。</p>

<p>Python 对象可能有：</p>

<ul>
  <li>0 或多个方法（由类型对象提供）</li>
  <li>0 或多个名称</li>
</ul>

<p>对象可能没有方法或只有允许读取内容的方法（immutable），也可能有允许改变内容的方法（mutable，time-varying behavior）。</p>

<p>名称并不属于对象本身（对象不知道自己的名称），名称存在于命名空间（模块/实例/函数）中，或者说命名空间是名称关联对象引用的字典。以赋值为例，赋值语句修改命名空间，而非对象：<code class="language-plaintext highlighter-rouge">name = 10</code> 把名称 <code class="language-plaintext highlighter-rouge">name</code> 加入局部命名空间，并指向一个整型对象，该整型对象包含值 <code class="language-plaintext highlighter-rouge">10</code>。继续执行 <code class="language-plaintext highlighter-rouge">name = 20</code> 覆盖名称 <code class="language-plaintext highlighter-rouge">name</code>，指向一个整型包含值 <code class="language-plaintext highlighter-rouge">20</code> 对象，原本的 <code class="language-plaintext highlighter-rouge">10</code> 对象不受任何影响。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">name</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">name</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>首先把名称 <code class="language-plaintext highlighter-rouge">name</code> 加入局部命名空间，指向一个空列表对象，这步会修改命名空间；然后调用该对象的方法修改列表对象的内容，这步不操作命名空间，也不操作整型对象。</p>

<p>区别于赋值语句，属性赋值（attribute assignment）<code class="language-plaintext highlighter-rouge">name.attr</code> 是方法 <code class="language-plaintext highlighter-rouge">__setattr__</code>/<code class="language-plaintext highlighter-rouge">__getattr__</code> 的语法糖；项引用（item reference）<code class="language-plaintext highlighter-rouge">name[index]</code> 是方法 <code class="language-plaintext highlighter-rouge">__setitem__</code>/<code class="language-plaintext highlighter-rouge">__getitem__</code> 的语法糖。</p>

<h3 id="call-by-sharing-cbs">Call By Sharing (CBS)</h3>

<p>Liskov 在 <a href="https://dl.acm.org/doi/book/10.5555/889832">CLU Reference Manual</a> 中给出的定义：</p>

<blockquote>
  <p>We call the argument passing technique <em>call by sharing</em>, because the argument objects are shared between the caller and the called routine.  This technique does not correspond to most traditional argument passing techniques (it is similar to argument passing in LISP).  In particular it is not call by value because mutations of arguments performed by the called routine will be visible to the caller. And it is not call by reference because access is not given to the variables of the caller, but merely to certain objects.</p>
</blockquote>

<p>即被调用者对参数的修改对调用者而言是可见的，所以不是 CBV；传入的也不是对象的地址值，形参和实参共享同一个对象，而非实参对应对象的值（内容）、形参对应对象的地址，所以也不是 CBR。</p>

<p>注意变量这个概念，变量在多数语言中也是一个对象，它包含值；而在 CLU 和 Python 中，变量只是一个指向对象的名称，这像指针，但和指针的区别是本身不能被其他变量指向。多个变量分享对象，变量不能指向变量，但是对象可以指向对象，如一个实例包含多个组件，这是一种逻辑而非物理上的包含，不同实例也可以指向同一组件对象。对象也可以指向其自身，这样，在没有显式引用类型的情况下也可以定义递归数据类型和共享数据对象了。</p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[以下是几种常见的求值策略（或称调用模型、参数传递惯例、规约策略）在 FOLDOC (Free On-Line Dictionary Of Computing) 中的定义：]]></summary></entry><entry><title type="html">简单地从类型系统的角度看编程语言</title><link href="/jekyll/update/2020/04/06/pl_type_system_view.html" rel="alternate" type="text/html" title="简单地从类型系统的角度看编程语言" /><published>2020-04-06T00:00:00+00:00</published><updated>2020-04-06T00:00:00+00:00</updated><id>/jekyll/update/2020/04/06/pl_type_system_view</id><content type="html" xml:base="/jekyll/update/2020/04/06/pl_type_system_view.html"><![CDATA[<p>我们经常会听到这样的讨论，“动态语言不适合大型项目”，“xx 语言不是类型安全的”，诸如此类。但对于这些概念我们真的清楚吗？</p>

<p>一般来说，从类型系统的角度，我们有三个相互独立的概念：</p>

<ul>
  <li>强类型/弱类型</li>
  <li>静态定型/动态定型</li>
  <li>类型安全/非类型安全</li>
</ul>

<p>下面我们非形式化地讨论一下这几个概念。</p>

<h3 id="强类型弱类型">强类型/弱类型</h3>

<p>强类型语言，即在该编程语言中，没有隐性的数据类型转换。</p>

<p>一个典型的例子如下：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a = '2'
b = a - 1
</code></pre></div></div>

<p>在 JavaScript，b 的值为 1；而在 Python 中，则会产生错误 <code class="language-plaintext highlighter-rouge">TypeError: unsupported operand type(s) for -: 'str' and 'int'</code>。</p>

<p>JavaScript 的顺利执行，归因于编译器（解释器）隐性地完成了类型转换的操作，对应的 Python 代码应该像下面这样：</p>

<pre><code class="language-Python">a = '2'
b = int(a) - 1
</code></pre>

<p>但是一个语言是强类型还是弱类型有时是有争议的，因为哪些转换是允许的并没有一个很严格的界定。考虑下面的 Python 代码：</p>

<pre><code class="language-Python">a = 2
b = a - 1.0
</code></pre>

<p>其中也存在 int 到 float 的隐性类型转换，但是可以顺利执行。</p>

<p>这样的隐性类型转换往往就是允许的，因为也是符合数学直觉的（减法运算的操作数是数值，而非字符串）。</p>

<h3 id="静态定型动态定型">静态定型/动态定型</h3>

<p>静态定型，在编译时进行类型检查；动态定型，在运行时进行类型检查。这一概念往往比较清楚，因为静态定型依赖于类型标注，比如前面的例子在 C 语言中就必须写成：</p>

<pre><code class="language-C">int a = 2;
int b = a - 1;
</code></pre>

<h3 id="类型安全非类型安全">类型安全/非类型安全</h3>

<p>类型安全，即不会出现逃逸出该编程语言类型规则的情况。这个概念比较抽象，有时会被混淆。所谓逃逸出类型规则，典型的例子是 C 语言的类型双关（type punning）。</p>

<pre><code class="language-C">bool is_negative(float x) {
    unsigned int *ui = (unsigned int *)&amp;x;
    return *ui &amp; 0x80000000;
}
</code></pre>

<p>在使用 IEEE 754 浮点数标准的条件下，利用指针类型转换实现类型双关来获取浮点数的符号位做整型计算，可以比以下简单的浮点计算得到更优的性能。</p>

<pre><code class="language-C">bool is_negative(float x) {
    return x &lt; 0.0;
}
</code></pre>

<p>注意负零（10000000）时的区别。</p>

<p>类型双关也可以用 union 来实现：</p>

<pre><code class="language-C">bool is_negative(float x) {
    union {
        unsigned int ui;
        float d;
    } my_union = { .d = x };
    return my_union.ui &amp; 0x80000000;
}
</code></pre>

<p>非类型安全往往还和未定义行为（undefined behavior）联系在一起，常常出现在没有显式错误处理的语言中。对应的典型漏洞利用（security exploits）如 stack smashing attack (<a href="https://en.wikipedia.org/wiki/Stack_buffer_overflow">stack buffer overflow</a>) 和 format string attack (<a href="https://en.wikipedia.org/wiki/Uncontrolled_format_string">uncontrolled format string</a>)。</p>

<p>同样需要注意的是，设计上的类型安全不等于真正的类型安全，主要出于以下原因：</p>

<ul>
  <li>对于绝大多数语言，很难做到完整的形式化证明（参考 <a href="https://dl.acm.org/doi/10.1145/503502.503505">Featherweight Java</a>）</li>
  <li>语言在实现上可能存在 bug</li>
  <li>链接库可能使用其他语言</li>
</ul>

<p>现在，基本清楚这三个概念在说些什么了，我们就来看看一些常见的编程语言分别属于哪一类。</p>

<table>
  <thead>
    <tr>
      <th>Programming Language</th>
      <th>Strong/Weak Type</th>
      <th>Static/Dynamic Typing</th>
      <th>Type Safety/Type Unsafety</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>C</td>
      <td>Strong (?)</td>
      <td>Static</td>
      <td>Unsafety</td>
    </tr>
    <tr>
      <td>Java</td>
      <td>Strong (?)</td>
      <td>Static</td>
      <td>Safety</td>
    </tr>
    <tr>
      <td>Python</td>
      <td>Strong (?)</td>
      <td>Dynamic</td>
      <td>Safety</td>
    </tr>
    <tr>
      <td>JavaScript</td>
      <td>Weak</td>
      <td>Dynamic</td>
      <td>Unsafety</td>
    </tr>
    <tr>
      <td>Haskell</td>
      <td>Strong</td>
      <td>Static</td>
      <td>Safety</td>
    </tr>
  </tbody>
</table>

<p>? : 对于哪些隐性类型转换是可接受的存在争议</p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[我们经常会听到这样的讨论，“动态语言不适合大型项目”，“xx 语言不是类型安全的”，诸如此类。但对于这些概念我们真的清楚吗？]]></summary></entry><entry><title type="html">Python Bytecode Disassembler</title><link href="/jekyll/update/2020/02/11/python_bytecode_disassembler.html" rel="alternate" type="text/html" title="Python Bytecode Disassembler" /><published>2020-02-11T00:00:00+00:00</published><updated>2020-02-11T00:00:00+00:00</updated><id>/jekyll/update/2020/02/11/python_bytecode_disassembler</id><content type="html" xml:base="/jekyll/update/2020/02/11/python_bytecode_disassembler.html"><![CDATA[<h3 id="python-bytecode">Python Bytecode</h3>

<h4 id="code-object">Code Object</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt;&gt; def func(x):
...     x *= -1
...     return x
&gt;&gt;&gt; func.__code__
&lt;code object func at 0x10cdadc00, file "&lt;stdin&gt;", line 1&gt;
&gt;&gt;&gt; func.__code__.co_code
b'|\x00d\x019\x00}\x00|\x00S\x00’
&gt;&gt;&gt; import dis
&gt;&gt;&gt; dis.dis(func.__code__.co_code)
          0 LOAD_FAST                0 (0)
          2 LOAD_CONST               1 (1)
          4 INPLACE_MULTIPLY
          6 STORE_FAST               0 (0)
          8 LOAD_FAST                0 (0)
         10 RETURN_VALUE
&gt;&gt;&gt; func.__code__.co_consts
(None, -1)
</code></pre></div></div>

<h4 id="pyc-file">.pyc File</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt;&gt; import py_compile
&gt;&gt;&gt; py_compile.compile("test.py")
'__pycache__/test.cpython-37.pyc'
</code></pre></div></div>

<p>.pyc files are independent of platform, but very sensitive to Python versions.</p>

<p><strong>&lt; 3.3</strong> [1]</p>

<ul>
  <li>4B magic number versioning the bytecode and pyc format</li>
  <li>4B modification timestamp</li>
  <li>Marshalled code object (<code class="language-plaintext highlighter-rouge">co_code</code>)</li>
</ul>

<p><strong>[3.3, 3.7)</strong>, since PEP 3147 – PYC Repository Directories [2]</p>

<ul>
  <li>4B magic number</li>
  <li>4B modification timestamp</li>
  <li>4B file size</li>
  <li>Marshalled code object</li>
</ul>

<p><strong>&gt;= 3.7</strong>, since PEP 552 – Deterministic pycs [3]</p>

<ul>
  <li>4B magic number</li>
  <li>4B bit field</li>
  <li>…</li>
</ul>

<p>If the bit field is 0, the pyc is a traditional <strong>timestamp-based pyc</strong>. I.e., the third and forth words will be the timestamp and file size respectively, and invalidation will be done by comparing the metadata of the source file with that in the header.</p>

<ul>
  <li>…</li>
  <li>4B modification timestamp</li>
  <li>4B file size</li>
  <li>Marshalled code object</li>
</ul>

<p>If the lowest bit of the bit field is set, the pyc is a <strong>hash-based pyc</strong>. We call the second lowest bit the check_source flag. Following the bit field is a 64-bit hash of the source file.</p>

<ul>
  <li>…</li>
  <li>8B hash of the source file</li>
  <li>Marshalled code object</li>
</ul>

<p><img src="/figs/pycformat.png" alt="pyc-format.png" /></p>

<p><strong>bytes to long</strong></p>

<p>E.g., the 4B modification timestamp is <code class="language-plaintext highlighter-rouge">b’/0\xd5]’</code>. 4 bytes are <code class="language-plaintext highlighter-rouge">/</code>, <code class="language-plaintext highlighter-rouge">0</code>, <code class="language-plaintext highlighter-rouge">\xd5</code>, <code class="language-plaintext highlighter-rouge">]</code> orderly, each of which maps to decimal <code class="language-plaintext highlighter-rouge">47</code>, <code class="language-plaintext highlighter-rouge">48</code>, <code class="language-plaintext highlighter-rouge">213</code>, <code class="language-plaintext highlighter-rouge">93</code> in extended ASCII table [4]. Thus the timestamp is <code class="language-plaintext highlighter-rouge">47 + 48 &lt;&lt; 8 + 213 &lt;&lt; 16 + 93 &lt;&lt; 24 = 1574252591</code>, which is <code class="language-plaintext highlighter-rouge">Wed Nov 20 20:23:11 2019</code>.</p>

<p>Bytes to decimal long can be done with module <code class="language-plaintext highlighter-rouge">struct</code> [5]:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">struct</span><span class="p">.</span><span class="n">unpack</span><span class="p">(</span><span class="s">'&lt;L'</span><span class="p">,</span> <span class="n">b</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div></div>

<h3 id="disassembler-for-all-python-versions">Disassembler for All Python Versions</h3>

<p>Source code: <a href="https://github.com/Elaphurus/disav">disav</a> [11]</p>

<p><strong>CPython version</strong> 4B magic number contains:</p>

<ul>
  <li>two bytes version: unique in each version of the Python interpreter</li>
  <li>two bytes of 0d0a: carriage return (CR) and line feed (LF), will change when a .pyc file is processed as text (copy corruption)</li>
</ul>

<p>E.g., <code class="language-plaintext highlighter-rouge">420d = 66 + 13 &lt;&lt; 8 = 3394</code>, <code class="language-plaintext highlighter-rouge">3394</code> maps to <code class="language-plaintext highlighter-rouge">Python 3.7b5</code>, key-value table can be found <code class="language-plaintext highlighter-rouge">cpython/Lib/importlib/_bootstrap_external.py</code> [6]. We implement the table mapping referring to google/pytype [7] (add 3210 and Python 3.8, 3.9).</p>

<p><strong>Bytecode</strong> Body of a .pyc file is just the output of <code class="language-plaintext highlighter-rouge">marshal.dump</code> of the code object that results from compiling the source file. After dealing with <code class="language-plaintext highlighter-rouge">marshal</code> [8] and <code class="language-plaintext highlighter-rouge">dis</code> [9] module, the byte codes are nicely disassembled and presented symbolically. We can further dump the attributes of <code class="language-plaintext highlighter-rouge">code</code> type referring to the document of <code class="language-plaintext highlighter-rouge">inspect</code> module [10].</p>

<h3 id="references">References</h3>

<ol>
  <li>The structure of .pyc files, https://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html</li>
  <li>PEP 3147 – PYC Repository Directories, https://www.python.org/dev/peps/pep-3147/</li>
  <li>PEP 552 – Deterministic pycs, https://www.python.org/dev/peps/pep-0552/</li>
  <li>ASCII Code - The extended ASCII table, https://www.ascii-code.com</li>
  <li>struct — Interpret bytes as packed binary data, https://docs.python.org/3.8/library/struct.html</li>
  <li>cpython/Lib/importlib/_bootstrap_external.py, https://github.com/python/cpython/blob/master/Lib/importlib/_bootstrap_external.py</li>
  <li>google/pytype/pytype/pyc/magic.py, https://github.com/google/pytype/blob/master/pytype/pyc/magic.py</li>
  <li>marshal, https://docs.python.org/3.8/library/marshal.html</li>
  <li>dis, https://docs.python.org/3.8/library/dis.html</li>
  <li>inspect — Inspect live objects, https://docs.python.org/3.7/library/inspect.html#types-and-members</li>
  <li>disav, https://github.com/Elaphurus/disav</li>
</ol>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Python Bytecode]]></summary></entry></feed>