Skip to content
Snippets Groups Projects
Commit fbc77baf authored by Julien Signoles's avatar Julien Signoles
Browse files

[E-ACSL] fixed bug #1478 when monitored global variables have initializers

parent 8d4a45fa
No related branches found
No related tags found
No related merge requests found
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
# E-ACSL: the Whole E-ACSL plug-in # E-ACSL: the Whole E-ACSL plug-in
############################################################################### ###############################################################################
-* E-ACSL [2013/09/04] Fix bug when monitored global variables have
initializers (bts #1478).
-* E-ACSL [2013/09/04] Fix bug when mixing -e-acsl-prepare and -* E-ACSL [2013/09/04] Fix bug when mixing -e-acsl-prepare and
running E-ACSL in another project (bts #!1473). running E-ACSL in another project (bts #!1473).
-* E-ACSL [2013/06/26] Fix crash with typedef on pointer types. -* E-ACSL [2013/06/26] Fix crash with typedef on pointer types.
......
...@@ -203,6 +203,67 @@ module rec Transfer ...@@ -203,6 +203,67 @@ module rec Transfer
let ty = Cil.typeOf e in let ty = Cil.typeOf e in
is_ptr_or_array ty is_ptr_or_array ty
let rec base_addr_node = function
| Lval lv | AddrOf lv | StartOf lv ->
(match lv with
| Var vi, _ -> Some vi
| Mem e, _ -> base_addr e)
| BinOp((PlusPI | IndexPI | MinusPI), e1, e2, _) ->
if is_ptr_or_array_exp e1 then base_addr e1
else begin
assert (is_ptr_or_array_exp e2);
base_addr e2
end
| Info(e, _) | CastE(_, e) -> base_addr e
| BinOp((MinusPP | PlusA | MinusA | Mult | Div | Mod |Shiftlt | Shiftrt
| Lt | Gt | Le | Ge | Eq | Ne | BAnd | BXor | BOr | LAnd | LOr),
_, _, _)
| UnOp _ | Const _ | SizeOf _ | SizeOfE _ | SizeOfStr _ | AlignOf _
| AlignOfE _ ->
None
and base_addr e = base_addr_node e.enode
let extend_to_expr always state lhost e =
let add_vi state vi =
if is_ptr_or_array_exp e && (always || Varinfo.Hptset.mem vi state) then
match base_addr e with
| None -> state
| Some vi_e ->
Options.feedback ~level:4 ~dkey
"monitoring %a from %a."
Printer.pp_varinfo vi_e
Printer.pp_lval (lhost, NoOffset);
Varinfo.Hptset.add vi_e state
else
state
in
match lhost with
| Var vi -> add_vi state vi
| Mem e ->
match base_addr e with
| None -> Kernel.fatal "no base address for %a" Printer.pp_exp e
| Some vi -> add_vi state vi
(* if [e] contains an address from a base, then also monitor the host *)
let rec extend_from_addr state lv e = match e.enode with
| AddrOf(lhost, _) ->
extend_to_expr true state lhost (Cil.new_exp ~loc:e.eloc (Lval lv)),
true
| BinOp((PlusPI | IndexPI | MinusPI), e1, e2, _) ->
if is_ptr_or_array_exp e1 then extend_from_addr state lv e1
else begin
assert (is_ptr_or_array_exp e2);
extend_from_addr state lv e2
end
| CastE(_, e) | Info(e, _) -> extend_from_addr state lv e
| _ -> state, false
let handle_assignment state (lhost, _ as lv) e =
(* if [e] contains an address from a base, then also monitor the host *)
let state, always = extend_from_addr state lv e in
extend_to_expr always state lhost e
let rec register_term_lval kf varinfos (thost, _) = let rec register_term_lval kf varinfos (thost, _) =
let add_vi kf vi = let add_vi kf vi =
Options.feedback ~level:4 ~dkey "monitoring %a from annotation of %a." Options.feedback ~level:4 ~dkey "monitoring %a from annotation of %a."
...@@ -309,6 +370,18 @@ module rec Transfer ...@@ -309,6 +370,18 @@ module rec Transfer
(); ();
!state_ref !state_ref
let register_initializers state =
let rec do_init vi init state = match init with
| SingleInit e -> handle_assignment state (Var vi, NoOffset) e
| CompoundInit(_, l) ->
List.fold_left (fun state (_, init) -> do_init vi init state) state l
in
let do_one vi init state = match init.init with
| None -> state
| Some init -> do_init vi init state
in
Globals.Vars.fold_in_file_order do_one state
(** The (backwards) transfer function for a branch. The [(Cil.CurrentLoc.get (** The (backwards) transfer function for a branch. The [(Cil.CurrentLoc.get
())] is set before calling this. If it returns None, then we have some ())] is set before calling this. If it returns None, then we have some
default handling. Otherwise, the returned data is the data before the default handling. Otherwise, the returned data is the data before the
...@@ -358,75 +431,29 @@ module rec Transfer ...@@ -358,75 +431,29 @@ module rec Transfer
else else
state state
in in
let state =
(* take initializers into account *)
if is_first then
let main, lib = Globals.entry_point () in
if Kernel_function.equal kf main && not lib then
register_initializers state
else
state
else
state
in
Some state) Some state)
let rec base_addr_node = function
| Lval lv | AddrOf lv | StartOf lv ->
(match lv with
| Var vi, _ -> Some vi
| Mem e, _ -> base_addr e)
| BinOp((PlusPI | IndexPI | MinusPI), e1, e2, _) ->
if is_ptr_or_array_exp e1 then base_addr e1
else begin
assert (is_ptr_or_array_exp e2);
base_addr e2
end
| Info(e, _) | CastE(_, e) -> base_addr e
| BinOp((MinusPP | PlusA | MinusA | Mult | Div | Mod |Shiftlt | Shiftrt
| Lt | Gt | Le | Ge | Eq | Ne | BAnd | BXor | BOr | LAnd | LOr),
_, _, _)
| UnOp _ | Const _ | SizeOf _ | SizeOfE _ | SizeOfStr _ | AlignOf _
| AlignOfE _ ->
None
and base_addr e = base_addr_node e.enode
(** The (backwards) transfer function for an instruction. The (** The (backwards) transfer function for an instruction. The
[(Cil.CurrentLoc.get ())] is set before calling this. If it returns [(Cil.CurrentLoc.get ())] is set before calling this. If it returns
None, then we have some default handling. Otherwise, the returned data is None, then we have some default handling. Otherwise, the returned data is
the data before the branch (not considering the exception handlers) *) the data before the branch (not considering the exception handlers) *)
let doInstr _stmt instr state = let doInstr _stmt instr state =
let state = Env.default_varinfos state in let state = Env.default_varinfos state in
let extend_to_expr always state lhost e =
let add_vi state vi =
if is_ptr_or_array_exp e && (always || Varinfo.Hptset.mem vi state)
then
match base_addr e with
| None -> state
| Some vi_e ->
Options.feedback ~level:4 ~dkey
"monitoring %a from %a."
Printer.pp_varinfo vi_e
Printer.pp_lval (lhost, NoOffset);
Varinfo.Hptset.add vi_e state
else
state
in
match lhost with
| Var vi -> add_vi state vi
| Mem e ->
match base_addr e with
| None -> Kernel.fatal "no base address for %a" Printer.pp_exp e
| Some vi -> add_vi state vi
in
match instr with match instr with
| Set((lhost, _) as lv, e, _) -> | Set(lv, e, _) ->
(* if [e] contains an address from a base, then also monitor the host *) let state = handle_assignment state lv e in
let rec extend_from_addr state e = match e.enode with Dataflow.Done (Some state)
| AddrOf(lhost, _) ->
extend_to_expr true state lhost (Cil.new_exp ~loc:e.eloc (Lval lv)),
true
| BinOp((PlusPI | IndexPI | MinusPI), e1, e2, _) ->
if is_ptr_or_array_exp e1 then extend_from_addr state e1
else begin
assert (is_ptr_or_array_exp e2);
extend_from_addr state e2
end
| CastE(_, e) | Info(e, _) -> extend_from_addr state e
| _ -> state, false
in
let state, always = extend_from_addr state e in
Dataflow.Done (Some (extend_to_expr always state lhost e))
| Call(result, f_exp, l, _) -> | Call(result, f_exp, l, _) ->
(match f_exp.enode with (match f_exp.enode with
| Lval(Var vi, NoOffset) -> | Lval(Var vi, NoOffset) ->
......
/* run.config
COMMENT: bts #1478 about wrong detection of initializers in pre-analysis
EXECNOW: LOG gen_bts1478.c BIN gen_bts1478.out @frama-c@ -e-acsl-share ./share/e-acsl ./tests/e-acsl-runtime/bts1478.c -e-acsl -then-on e-acsl -print -ocode ./tests/e-acsl-runtime/result/gen_bts1478.c > /dev/null && ./gcc_test.sh bts1478
EXECNOW: LOG gen_bts14782.c BIN gen_bts14782.out @frama-c@ -e-acsl-share ./share/e-acsl ./tests/e-acsl-runtime/bts1478.c -e-acsl-gmp-only -e-acsl -then-on e-acsl -print -ocode ./tests/e-acsl-runtime/result/gen_bts14782.c > /dev/null && ./gcc_test.sh bts14782
*/
int global_i = 0;
int* global_i_ptr = &global_i;
/*@ requires global_i == 0;
requires \valid(global_i_ptr);
requires global_i_ptr == &global_i; */
void loop(void) { }
int main(void) {
loop();
return 0;
}
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/e_acsl.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/e_acsl_gmp_types.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/e_acsl_gmp.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/memory_model/e_acsl_mmodel_api.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/memory_model/e_acsl_bittree.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/memory_model/e_acsl_mmodel.h"
[kernel] preprocessing with "gcc -C -E -I. -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc tests/e-acsl-runtime/bts1478.c"
[e-acsl] beginning translation.
[e-acsl] translation done in project "e-acsl".
[value] Analyzing a complete application starting at main
[value] Computing initial state
[value] Initial state computed
[value] Values of globals at initialization
__fc_random_counter ∈ {0}
__fc_rand_max ∈ {32767}
__fc_heap_status ∈ [--..--]
__memory_size ∈ [--..--]
global_i ∈ {0}
global_i_ptr ∈ {{ &global_i }}
[value] using specification for function __store_block
tests/e-acsl-runtime/bts1478.c:10:[value] Function __e_acsl_loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:11:[value] Function __e_acsl_loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:12:[value] Function __e_acsl_loop: precondition got status valid.
[value] using specification for function __gmpz_init_set_si
FRAMAC_SHARE/e-acsl/e_acsl_gmp.h:62:[value] Function __gmpz_init_set_si: precondition got status valid.
FRAMAC_SHARE/e-acsl/e_acsl_gmp.h:64:[value] Function __gmpz_init_set_si: postcondition got status valid.
FRAMAC_SHARE/e-acsl/e_acsl_gmp.h:65:[value] Function __gmpz_init_set_si: postcondition got status unknown.
[value] using specification for function __gmpz_cmp
FRAMAC_SHARE/e-acsl/e_acsl_gmp.h:124:[value] Function __gmpz_cmp: precondition got status valid.
FRAMAC_SHARE/e-acsl/e_acsl_gmp.h:125:[value] Function __gmpz_cmp: precondition got status valid.
[value] using specification for function e_acsl_assert
FRAMAC_SHARE/e-acsl/e_acsl.h:34:[value] Function e_acsl_assert: precondition got status unknown.
[value] using specification for function __valid
FRAMAC_SHARE/e-acsl/e_acsl.h:34:[value] Function e_acsl_assert: precondition got status valid.
[value] using specification for function __gmpz_clear
FRAMAC_SHARE/e-acsl/e_acsl_gmp.h:114:[value] Function __gmpz_clear: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:10:[value] Function loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:11:[value] Function loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:12:[value] Function loop: precondition got status valid.
[value] using specification for function __delete_block
[kernel] warning: Neither code nor specification for function __e_acsl_memory_clean, generating default assigns from the prototype
[value] using specification for function __e_acsl_memory_clean
[value] done for function main
[value] ====== VALUES COMPUTED ======
[value] Values at end of function __e_acsl_memory_init:
[value] Values at end of function loop:
[value] Values at end of function __e_acsl_loop:
[value] Values at end of function main:
__retres ∈ {0}
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/e_acsl.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/e_acsl_gmp_types.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/e_acsl_gmp.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/memory_model/e_acsl_mmodel_api.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/memory_model/e_acsl_bittree.h"
[kernel] preprocessing with "gcc -C -E -I. -IFRAMAC_SHARE/e-acsl -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc FRAMAC_SHARE/e-acsl/memory_model/e_acsl_mmodel.h"
[kernel] preprocessing with "gcc -C -E -I. -DE_ACSL_MACHDEP=x86_32 -IFRAMAC_SHARE/libc tests/e-acsl-runtime/bts1478.c"
[e-acsl] beginning translation.
[e-acsl] translation done in project "e-acsl".
[value] Analyzing a complete application starting at main
[value] Computing initial state
[value] Initial state computed
[value] Values of globals at initialization
__fc_random_counter ∈ {0}
__fc_rand_max ∈ {32767}
__fc_heap_status ∈ [--..--]
__memory_size ∈ [--..--]
global_i ∈ {0}
global_i_ptr ∈ {{ &global_i }}
[value] using specification for function __store_block
tests/e-acsl-runtime/bts1478.c:10:[value] Function __e_acsl_loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:11:[value] Function __e_acsl_loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:12:[value] Function __e_acsl_loop: precondition got status valid.
[value] using specification for function e_acsl_assert
FRAMAC_SHARE/e-acsl/e_acsl.h:34:[value] Function e_acsl_assert: precondition got status valid.
[value] using specification for function __valid
FRAMAC_SHARE/e-acsl/e_acsl.h:34:[value] Function e_acsl_assert: precondition got status unknown.
tests/e-acsl-runtime/bts1478.c:10:[value] Function loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:11:[value] Function loop: precondition got status valid.
tests/e-acsl-runtime/bts1478.c:12:[value] Function loop: precondition got status valid.
[value] using specification for function __delete_block
[kernel] warning: Neither code nor specification for function __e_acsl_memory_clean, generating default assigns from the prototype
[value] using specification for function __e_acsl_memory_clean
[value] done for function main
[value] ====== VALUES COMPUTED ======
[value] Values at end of function __e_acsl_memory_init:
[value] Values at end of function loop:
[value] Values at end of function __e_acsl_loop:
[value] Values at end of function main:
__retres ∈ {0}
/* Generated by Frama-C */
struct __anonstruct___mpz_struct_1 {
int _mp_alloc ;
int _mp_size ;
unsigned long *_mp_d ;
};
typedef struct __anonstruct___mpz_struct_1 __mpz_struct;
typedef __mpz_struct ( __attribute__((__FC_BUILTIN__)) mpz_t)[1];
typedef unsigned int size_t;
/*@ requires predicate ≢ 0;
assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) void e_acsl_assert(int predicate,
char *kind,
char *fct,
char *pred_txt,
int line);
/*@
model __mpz_struct { ℤ n };
*/
int __fc_random_counter __attribute__((__unused__));
unsigned long const __fc_rand_max = (unsigned long)32767;
/*@ ghost extern int __fc_heap_status; */
/*@
axiomatic
dynamic_allocation {
predicate is_allocable{L}(size_t n)
reads __fc_heap_status;
}
*/
/*@ assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) void *__store_block(void *ptr,
size_t size);
/*@ assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) void __delete_block(void *ptr);
/*@ assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) int __valid(void *ptr, size_t size);
extern __attribute__((__FC_BUILTIN__)) void __e_acsl_memory_clean(void);
extern size_t __memory_size;
/*@
predicate diffSize{L1, L2}(ℤ i) =
\at(__memory_size,L1)-\at(__memory_size,L2) ≡ i;
*/
int global_i = 0;
int *global_i_ptr = & global_i;
/*@ requires global_i ≡ 0;
requires \valid(global_i_ptr);
requires global_i_ptr ≡ &global_i;
*/
void loop(void)
{
return;
}
/*@ requires global_i ≡ 0;
requires \valid(global_i_ptr);
requires global_i_ptr ≡ &global_i;
*/
void __e_acsl_loop(void)
{
{
int __e_acsl_valid;
e_acsl_assert(global_i == 0,(char *)"Precondition",(char *)"loop",
(char *)"global_i == 0",10);
__e_acsl_valid = __valid((void *)global_i_ptr,sizeof(int));
e_acsl_assert(__e_acsl_valid,(char *)"Precondition",(char *)"loop",
(char *)"\\valid(global_i_ptr)",11);
e_acsl_assert(global_i_ptr == & global_i,(char *)"Precondition",
(char *)"loop",(char *)"global_i_ptr == &global_i",12);
loop();
}
return;
}
void __e_acsl_memory_init(void)
{
__store_block((void *)(& global_i_ptr),4U);
__store_block((void *)(& global_i),4U);
return;
}
int main(void)
{
int __retres;
__e_acsl_memory_init();
__e_acsl_loop();
__retres = 0;
__delete_block((void *)(& global_i_ptr));
__delete_block((void *)(& global_i));
__e_acsl_memory_clean();
return __retres;
}
/* Generated by Frama-C */
struct __anonstruct___mpz_struct_1 {
int _mp_alloc ;
int _mp_size ;
unsigned long *_mp_d ;
};
typedef struct __anonstruct___mpz_struct_1 __mpz_struct;
typedef __mpz_struct ( __attribute__((__FC_BUILTIN__)) mpz_t)[1];
typedef unsigned int size_t;
/*@ requires predicate ≢ 0;
assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) void e_acsl_assert(int predicate,
char *kind,
char *fct,
char *pred_txt,
int line);
/*@
model __mpz_struct { ℤ n };
*/
int __fc_random_counter __attribute__((__unused__));
unsigned long const __fc_rand_max = (unsigned long)32767;
/*@ ghost extern int __fc_heap_status; */
/*@
axiomatic
dynamic_allocation {
predicate is_allocable{L}(size_t n)
reads __fc_heap_status;
}
*/
/*@ requires ¬\initialized(z);
ensures \valid(\old(z));
ensures \initialized(\old(z));
assigns *z;
assigns *z \from n;
allocates \old(z);
*/
extern __attribute__((__FC_BUILTIN__)) void __gmpz_init_set_si(__mpz_struct * /*[1]*/ z,
long n);
/*@ requires \valid(x);
assigns *x; */
extern __attribute__((__FC_BUILTIN__)) void __gmpz_clear(__mpz_struct * /*[1]*/ x);
/*@ requires \valid(z1);
requires \valid(z2);
assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) int __gmpz_cmp(__mpz_struct const * /*[1]*/ z1,
__mpz_struct const * /*[1]*/ z2);
/*@ assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) void *__store_block(void *ptr,
size_t size);
/*@ assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) void __delete_block(void *ptr);
/*@ assigns \nothing; */
extern __attribute__((__FC_BUILTIN__)) int __valid(void *ptr, size_t size);
extern __attribute__((__FC_BUILTIN__)) void __e_acsl_memory_clean(void);
extern size_t __memory_size;
/*@
predicate diffSize{L1, L2}(ℤ i) =
\at(__memory_size,L1)-\at(__memory_size,L2) ≡ i;
*/
int global_i = 0;
int *global_i_ptr = & global_i;
/*@ requires global_i ≡ 0;
requires \valid(global_i_ptr);
requires global_i_ptr ≡ &global_i;
*/
void loop(void)
{
return;
}
/*@ requires global_i ≡ 0;
requires \valid(global_i_ptr);
requires global_i_ptr ≡ &global_i;
*/
void __e_acsl_loop(void)
{
{
mpz_t __e_acsl_global_i;
mpz_t __e_acsl;
int __e_acsl_eq;
int __e_acsl_valid;
__gmpz_init_set_si(__e_acsl_global_i,(long)global_i);
__gmpz_init_set_si(__e_acsl,(long)0);
__e_acsl_eq = __gmpz_cmp((__mpz_struct const *)(__e_acsl_global_i),
(__mpz_struct const *)(__e_acsl));
e_acsl_assert(__e_acsl_eq == 0,(char *)"Precondition",(char *)"loop",
(char *)"global_i == 0",10);
__e_acsl_valid = __valid((void *)global_i_ptr,sizeof(int));
e_acsl_assert(__e_acsl_valid,(char *)"Precondition",(char *)"loop",
(char *)"\\valid(global_i_ptr)",11);
e_acsl_assert(global_i_ptr == & global_i,(char *)"Precondition",
(char *)"loop",(char *)"global_i_ptr == &global_i",12);
__gmpz_clear(__e_acsl_global_i);
__gmpz_clear(__e_acsl);
loop();
}
return;
}
void __e_acsl_memory_init(void)
{
__store_block((void *)(& global_i_ptr),4U);
__store_block((void *)(& global_i),4U);
return;
}
int main(void)
{
int __retres;
__e_acsl_memory_init();
__e_acsl_loop();
__retres = 0;
__delete_block((void *)(& global_i_ptr));
__delete_block((void *)(& global_i));
__e_acsl_memory_clean();
return __retres;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment