Summary: TOPL properties are essentially automata, which specify a bad pattern. This commit is just a parser for them. Reviewed By: jvillard Differential Revision: D14477671 fbshipit-source-id: c38a8ef37master
parent
0a9c77e779
commit
8bf65086e3
@ -0,0 +1,30 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2019-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
module L = Logging
|
||||||
|
|
||||||
|
let initialized = ref false
|
||||||
|
|
||||||
|
let properties = ref []
|
||||||
|
|
||||||
|
let parse topl_file =
|
||||||
|
let f ch =
|
||||||
|
let lexbuf = Lexing.from_channel ch in
|
||||||
|
try ToplParser.properties (ToplLexer.token ()) lexbuf with ToplParser.Error ->
|
||||||
|
let Lexing.({pos_lnum; pos_bol; pos_cnum; _}) = Lexing.lexeme_start_p lexbuf in
|
||||||
|
let col = pos_cnum - pos_bol + 1 in
|
||||||
|
L.(die UserError) "@[%s:%d:%d: topl parse error@]@\n@?" topl_file pos_lnum col
|
||||||
|
in
|
||||||
|
try In_channel.with_file topl_file ~f with Sys_error msg ->
|
||||||
|
L.(die UserError) "@[topl:%s: %s@]@\n@?" topl_file msg
|
||||||
|
|
||||||
|
|
||||||
|
let init () =
|
||||||
|
if not !initialized then (
|
||||||
|
properties := List.concat_map ~f:parse Config.topl_properties ;
|
||||||
|
initialized := true )
|
@ -0,0 +1,11 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2019-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
|
||||||
|
val init : unit -> unit
|
||||||
|
(** Parse properties, mentioned by [Config.topl_properties]. Does this only once. *)
|
@ -0,0 +1,33 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2019-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
|
||||||
|
type register_name = string
|
||||||
|
|
||||||
|
(** TODO: use Const.t *)
|
||||||
|
type constant = string
|
||||||
|
|
||||||
|
type value_pattern =
|
||||||
|
| Ignore
|
||||||
|
| SaveInRegister of register_name
|
||||||
|
| EqualToRegister of register_name
|
||||||
|
| EqualToConstant of constant
|
||||||
|
|
||||||
|
(** a regular expression *)
|
||||||
|
type procedure_name_pattern = string
|
||||||
|
|
||||||
|
type label =
|
||||||
|
{ return: value_pattern
|
||||||
|
; procedure_name: procedure_name_pattern
|
||||||
|
; arguments: value_pattern list option }
|
||||||
|
|
||||||
|
type vertex = string
|
||||||
|
|
||||||
|
type transition = {source: vertex; target: vertex; label: label}
|
||||||
|
|
||||||
|
type t = {name: string; message: string option; prefixes: string list; transitions: transition list}
|
@ -0,0 +1,76 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) 2019-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
{
|
||||||
|
open !IStd
|
||||||
|
open ToplParser
|
||||||
|
|
||||||
|
module L = Logging
|
||||||
|
|
||||||
|
let new_line x y lexbuf =
|
||||||
|
let m = x |> String.filter ~f:(Char.equal '\n') |> String.length in
|
||||||
|
let n = y |> String.length in
|
||||||
|
let open Lexing in
|
||||||
|
let lcp = lexbuf.lex_curr_p in
|
||||||
|
lexbuf.lex_curr_p <-
|
||||||
|
{ lcp with pos_lnum = lcp.pos_lnum + m ; pos_bol = lcp.pos_cnum - n } ;
|
||||||
|
(INDENT n)
|
||||||
|
|
||||||
|
let quoted = Str.regexp "\\\\\\(.\\)"
|
||||||
|
let unquote x = Str.global_replace quoted "\\1" x
|
||||||
|
|
||||||
|
(* We open Caml, because ocamllex generates code that uses Array.make,
|
||||||
|
which is not available in Core. Ideally, this should go away. *)
|
||||||
|
open! Caml
|
||||||
|
}
|
||||||
|
|
||||||
|
let id_head = ['a'-'z' 'A'-'Z']
|
||||||
|
let id_tail = ['a'-'z' 'A'-'Z' '0'-'9']*
|
||||||
|
|
||||||
|
rule raw_token = parse
|
||||||
|
| '\t' { raise Error }
|
||||||
|
| ((' '* ("//" [^ '\n']*)? '\n')+ as x) (' '* as y) { new_line x y lexbuf }
|
||||||
|
| ' '+ { raw_token lexbuf }
|
||||||
|
| "->" { ARROW }
|
||||||
|
| '=' { ASGN }
|
||||||
|
| ':' { COLON }
|
||||||
|
| ',' { COMMA }
|
||||||
|
| '(' { LP }
|
||||||
|
| ')' { RP }
|
||||||
|
| '*' { STAR }
|
||||||
|
| '<' (([^ '<' '>' '\n' '\\'] | ('\\' _))* as x) '>' { CONSTANT (unquote x) }
|
||||||
|
| '"' ([^ '"' '\n']* as x) '"' { STRING x }
|
||||||
|
| "prefix" { PREFIX }
|
||||||
|
| "property" { PROPERTY }
|
||||||
|
| "message" { MESSAGE }
|
||||||
|
| id_head id_tail as id { ID id }
|
||||||
|
| eof { EOF }
|
||||||
|
| _ { raise Error }
|
||||||
|
|
||||||
|
{
|
||||||
|
let token () =
|
||||||
|
let indents = ref [0] in
|
||||||
|
let scheduled_rc = ref 0 in
|
||||||
|
let last_indent () = match !indents with
|
||||||
|
| x :: _ -> x
|
||||||
|
| [] -> L.(die InternalError) "ToplLexer.indents should be nonempty"
|
||||||
|
in
|
||||||
|
let add_indent n = indents := n :: !indents in
|
||||||
|
let rec drop_to_indent n = match !indents with
|
||||||
|
| x :: xs when x > n -> (incr scheduled_rc; indents := xs; drop_to_indent n)
|
||||||
|
| x :: _ when x < n -> raise Error (* bad indentation *)
|
||||||
|
| _ -> ()
|
||||||
|
in
|
||||||
|
let rec step lexbuf =
|
||||||
|
if !scheduled_rc > 0 then (decr scheduled_rc; RC)
|
||||||
|
else match raw_token lexbuf with
|
||||||
|
| INDENT n when n > last_indent () -> (add_indent n; LC)
|
||||||
|
| INDENT n when n < last_indent () -> (drop_to_indent n; step lexbuf)
|
||||||
|
| INDENT _ -> step lexbuf
|
||||||
|
| t -> t
|
||||||
|
in
|
||||||
|
step
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
%{
|
||||||
|
open !IStd
|
||||||
|
|
||||||
|
let is_guard i = Char.is_lowercase i.[0]
|
||||||
|
|
||||||
|
let normalize_id i = String.uncapitalize i
|
||||||
|
|
||||||
|
let value_pattern_of_id i =
|
||||||
|
assert (String.length i > 0) ;
|
||||||
|
let j = normalize_id i in
|
||||||
|
if is_guard i then ToplAst.EqualToRegister j else ToplAst.SaveInRegister j
|
||||||
|
%}
|
||||||
|
|
||||||
|
%token <int> INDENT (* The lexer uses this token only internally. *)
|
||||||
|
%token <string> CONSTANT
|
||||||
|
%token <string> ID
|
||||||
|
%token <string> STRING
|
||||||
|
%token ARROW
|
||||||
|
%token ASGN
|
||||||
|
%token COLON
|
||||||
|
%token COMMA
|
||||||
|
%token EOF
|
||||||
|
%token LC
|
||||||
|
%token LP
|
||||||
|
%token MESSAGE
|
||||||
|
%token PREFIX
|
||||||
|
%token PROPERTY
|
||||||
|
%token RC
|
||||||
|
%token RP
|
||||||
|
%token STAR
|
||||||
|
|
||||||
|
%start <ToplAst.t list> properties
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
properties: ps=one_property* EOF { ps }
|
||||||
|
|
||||||
|
one_property:
|
||||||
|
PROPERTY name=ID LC message=message? prefixes=prefix* transitions=transition* RC
|
||||||
|
{ ToplAst.{name; message; prefixes; transitions} }
|
||||||
|
|
||||||
|
message: MESSAGE s=STRING { s }
|
||||||
|
|
||||||
|
prefix: PREFIX c=CONSTANT { c }
|
||||||
|
|
||||||
|
transition:
|
||||||
|
source=ID ARROW target=ID COLON label=label
|
||||||
|
{ ToplAst.{source; target; label} }
|
||||||
|
|
||||||
|
label:
|
||||||
|
return=value_pattern ASGN c=call_pattern
|
||||||
|
{ let procedure_name, arguments = c in ToplAst.{return; procedure_name; arguments} }
|
||||||
|
| c=call_pattern
|
||||||
|
{ let procedure_name, arguments = c in ToplAst.{return=Ignore; procedure_name; arguments} }
|
||||||
|
|
||||||
|
call_pattern: p=procedure_pattern a=arguments_pattern? { (p, a) }
|
||||||
|
|
||||||
|
procedure_pattern:
|
||||||
|
i=ID { i }
|
||||||
|
| c=CONSTANT { c }
|
||||||
|
| STAR { ".*" }
|
||||||
|
|
||||||
|
arguments_pattern: LP a=separated_list(COMMA, value_pattern) RP { a }
|
||||||
|
|
||||||
|
value_pattern:
|
||||||
|
i=ID { value_pattern_of_id i }
|
||||||
|
| c=CONSTANT { ToplAst.EqualToConstant c }
|
||||||
|
| STAR { ToplAst.Ignore }
|
||||||
|
|
||||||
|
%%
|
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (c) 2019-present, Facebook, Inc.
|
||||||
|
#
|
||||||
|
# This source code is licensed under the MIT license found in the
|
||||||
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
|
TESTS_DIR = ../../..
|
||||||
|
|
||||||
|
INFER_OPTIONS = --topl-properties tomcat.topl --biabduction-only
|
||||||
|
INFERPRINT_OPTIONS = --issues-tests
|
||||||
|
|
||||||
|
SOURCES = $(wildcard *.java)
|
||||||
|
|
||||||
|
include $(TESTS_DIR)/javac.make
|
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
class TomcatFail {
|
||||||
|
void f() {}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
property ForwardUncommitted
|
||||||
|
message "A ServletResponse was forwarded before being committed."
|
||||||
|
// TODO assume InterleavedResponse_Weak
|
||||||
|
prefix <javax.servlet>
|
||||||
|
prefix <javax.servlet.{ServletOutputStream,ServletResponse}>
|
||||||
|
prefix <java.io.PrintWriter>
|
||||||
|
start -> start: *
|
||||||
|
start -> tracking: R = <ServletResponse.\<init\>>
|
||||||
|
tracking -> ok: flushBuffer(r)
|
||||||
|
tracking -> gotWriter: W = getWriter(r)
|
||||||
|
gotWriter -> ok: flush(w)
|
||||||
|
gotWrite -> ok: flushBuffer(r)
|
||||||
|
tracking -> gotStream: S = getOutputStream(r)
|
||||||
|
gotStream -> ok: flush(s)
|
||||||
|
gotStream -> ok: flushBuffer(r)
|
||||||
|
tracking -> error: <RequestDispatcher.forward>(*, *, r)
|
||||||
|
gotWriter -> error: <RequestDispatcher.forward>(*, *, r)
|
||||||
|
gotStream -> error: <RequestDispatcher.forward>(*, *, r)
|
||||||
|
|
||||||
|
// The documentation of ServletResponse.getOutputStream says that "either this
|
||||||
|
// method getWriter may be called to write to the body, not both." So,
|
||||||
|
// technically, the property is InterleavedResponse1. However, this property is
|
||||||
|
// broken, which is why we also have the weaker version InterleavedResponse2.
|
||||||
|
property InterleavedResponse1
|
||||||
|
message "A ServletResponse was asked for both a writer and a stream."
|
||||||
|
prefix <javax.servlet.ServletResponse>
|
||||||
|
start -> start: *
|
||||||
|
start -> gotWriter: W = getWriter(R)
|
||||||
|
start -> gotStream: S = getOutputStream(R)
|
||||||
|
gotWriter -> error: getOutputStream(r)
|
||||||
|
gotStream -> error: getWriter(r)
|
||||||
|
|
||||||
|
property InterleavedResponse2
|
||||||
|
// vertex names: w = got writer; W = used writer; similarly for s, S
|
||||||
|
message "Incompatible methods for putting data into a response were used."
|
||||||
|
prefix <javax.servlet.ServletResponse>
|
||||||
|
start -> start: *
|
||||||
|
start -> w: W = getWriter(R)
|
||||||
|
start -> s: S = getOutputStream(R)
|
||||||
|
w -> sw: S = getOutputStream(r)
|
||||||
|
s -> sw: W = getWriter(r)
|
||||||
|
w -> W: *(w)
|
||||||
|
sw -> sW: *(w)
|
||||||
|
s -> S: *(s)
|
||||||
|
sw -> Sw: *(s)
|
||||||
|
W -> sW: S = getOutputStream(r)
|
||||||
|
S -> Sw: W = getWriter(r)
|
||||||
|
sW -> error: *(s)
|
||||||
|
Sw -> error: *(w)
|
Loading…
Reference in new issue